diff options
Diffstat (limited to '')
134 files changed, 37340 insertions, 0 deletions
diff --git a/contrib/README b/contrib/README new file mode 100644 index 0000000..b9bb1f7 --- /dev/null +++ b/contrib/README @@ -0,0 +1,66 @@ +This directory contains contributed scripts, tools, libraries, 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 + + - queryperf/ + + A DNS query performance testing tool + + - perftcpdns/ + + A performance testing tool for DNS over TCP + + - 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 + + - 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 deprecated eventually.) + + - sdb/ + + SDB drivers: another mechanism for accessing external data + sources. (These are no longer actively maintained and are + expected to be deprecated eventually.) + + - idn/ + + Contains source for 'idnkit', which provides support for + Internationalized Domain Name processing. + + - dnsperf-2.1.0.0-1/ + - dnsperf-patches/ + + DNS server performance testing tools, like 'queryperf' but more + advanced: 'dnsperf' focuses on authoritative server performance + and 'resperf' on recursive server performance. The patch that + adds support for EDNS Client Subnet can be found in dnsperf-patches + directory. + +Formerly, there was more software included in this directory, but we +have removed it in favour of using canonical upstream locations. You +can find the links to the nslint, query-loc and zkt, and other software +we find useful but do not necessarily support, at: + + https://www.isc.org/community/tools/ diff --git a/contrib/dane/mkdane.sh b/contrib/dane/mkdane.sh new file mode 100755 index 0000000..fbae4aa --- /dev/null +++ b/contrib/dane/mkdane.sh @@ -0,0 +1,137 @@ +#!/bin/sh +# Copyright (C) 2010, 2012 Internet Systems Consortium, Inc. ("ISC") +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. +# +# Generate a DNS RR from an x.509 certificate +# Currently only supports TLSA, but can be extended to support +# other DANE types such as SMIMEA in the future. +# +# Requires: openssl + +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..ac1d68f --- /dev/null +++ b/contrib/dlz/bin/dlzbdb/Makefile.in @@ -0,0 +1,62 @@ +# Copyright (C) 1998-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/. + +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@ + +DEPLIBS = ${ISCDEPLIBS} + +LIBS = ${ISCLIBS} ${DLZLIBS} @LIBS@ + +TARGETS = dlzbdb + +SRCS = dlzbdb.c + +#MANPAGES = + +#HTMLPAGES = + +#MANOBJS = ${MANPAGES} ${HTMLPAGES} + +@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} + +doc man:: ${MANOBJS} + +#docclean manclean maintainer-clean:: +# rm -f ${MANOBJS} + +clean distclean maintainer-clean:: + rm -f ${TARGETS} + +installdirs: + $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${sbindir} +# $(SHELL) ${top_srcdir}/mkinstalldirs ${DESTDIR}${mandir}/man8 + +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..634d0f9 --- /dev/null +++ b/contrib/dlz/bin/dlzbdb/dlzbdb.c @@ -0,0 +1,1264 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Copyright (C) 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 <config.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.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> + +#include <db.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 trys 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 paramters 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 paramters 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 *tmp; + char *left; + char *right; + int result=0; + + UNUSED(dbp); + UNUSED(pkey); + + /* Allocate memory to use in parsing the string */ + tmp = right = malloc(pdata->size + 1); + + /* verify memory was allocated */ + if (right == NULL) { + result = BDBparseErr; + goto getzone_cleanup; + } + + /* copy data string into newly allocated memory */ + strncpy(right, pdata->data, pdata->size); + right[pdata->size] = '\0'; + + /* split string at the first space */ + left = isc_string_separate(&right, " "); + + /* copy string for "zone" secondary index */ + skey->data = strdup(left); + if (skey->data == NULL) { + result = BDBparseErr; + goto getzone_cleanup; + } + /* set required values for BDB */ + skey->size = strlen(skey->data); + skey->flags = DB_DBT_APPMALLOC; + + getzone_cleanup: + + /* cleanup memory */ + if (tmp != NULL) + free(tmp); + + return result; +} + +/*% + * BDB callback to create host secondary index + */ + +int +gethost(DB *dbp, const DBT *pkey, const DBT *pdata, DBT *skey) { + char *tmp; + char *left; + char *right; + int result=0; + + UNUSED(dbp); + UNUSED(pkey); + + /* allocate memory to use in parsing the string */ + tmp = right = malloc(pdata->size + 1); + + /* verify memory was allocated */ + if (tmp == NULL) { + result = BDBparseErr; + goto gethost_cleanup; + } + + /* copy data string into newly allocated memory */ + strncpy(right, pdata->data, pdata->size); + right[pdata->size] = '\0'; + + /* we don't care about left string. */ + /* memory of left string will be freed when tmp is freed. */ + isc_string_separate(&right, " "); + + /* verify right still has some characters left */ + if (right == NULL) { + result = BDBparseErr; + goto gethost_cleanup; + } + + /* get "host" from data string */ + left = isc_string_separate(&right, " "); + /* copy string for "host" secondary index */ + skey->data = strdup(left); + if (skey->data == NULL) { + result = BDBparseErr; + goto gethost_cleanup; + } + /* set required values for BDB */ + skey->size = strlen(skey->data); + skey->flags = DB_DBT_APPMALLOC; + + gethost_cleanup: + + /* cleanup memory */ + if (tmp != NULL) + free(tmp); + + return result; +} + +/*% + * 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 == true) { + 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 == true) { + 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 == true) { + /* 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 */ + result = isc_mem_create(0, 0, &lex_mctx); + if (result != ISC_R_SUCCESS) { + fprintf(stderr, "unexpected error creating lexer\n"); + return result; + } + + /* 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 alread 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 == true) { + 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 == true) { + 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 == true) { + 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 == true) { + /* 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 == false) { + 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 + diff --git a/contrib/dlz/config.dlz.in b/contrib/dlz/config.dlz.in new file mode 100644 index 0000000..47525af --- /dev/null +++ b/contrib/dlz/config.dlz.in @@ -0,0 +1,508 @@ +# Copyright (C) 2005 Internet Systems Consortium, Inc. ("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 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 nonexistant 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..f68bd9e --- /dev/null +++ b/contrib/dlz/drivers/dlz_bdb_driver.c @@ -0,0 +1,800 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Copyright (C) 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 <config.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include <dns/log.h> +#include <dns/sdlz.h> +#include <dns/result.h> + +#include <isc/mem.h> +#include <isc/print.h> +#include <isc/result.h> +#include <isc/util.h> + +#include <named/globals.h> + +#include <dlz/dlz_bdb_driver.h> + +#include <db.h> + +static dns_sdlzimplementation_t *dlz_bdb = NULL; + +/* should the bdb driver use threads. */ +#ifdef ISC_PLATFORM_USETHREADS +#define bdb_threads DB_THREAD +#else +#define bdb_threads 0 +#endif + +/* 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 formated 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 postive 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_put(mctx, db, sizeof(bdb_instance_t)); + isc_mem_detach(&mctx); + } +} + +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(ns_g_mctx, sizeof(bdb_instance_t)); + if (db == NULL) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Could not allocate memory for " + "database instance object."); + return (ISC_R_NOMEMORY); + } + memset(db, 0, sizeof(bdb_instance_t)); + + /* attach to the memory context */ + isc_mem_attach(ns_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, + ns_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 diff --git a/contrib/dlz/drivers/dlz_bdbhpt_driver.c b/contrib/dlz/drivers/dlz_bdbhpt_driver.c new file mode 100644 index 0000000..8fbdf7f --- /dev/null +++ b/contrib/dlz/drivers/dlz_bdbhpt_driver.c @@ -0,0 +1,865 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Copyright (C) 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 <config.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include <dns/log.h> +#include <dns/sdlz.h> +#include <dns/result.h> + +#include <isc/mem.h> +#include <isc/print.h> +#include <isc/result.h> +#include <isc/util.h> + +#include <named/globals.h> + +#include <dlz/dlz_bdbhpt_driver.h> + +#include <db.h> + +static dns_sdlzimplementation_t *dlz_bdbhpt = NULL; + +/* should the bdb driver use threads. */ +#ifdef ISC_PLATFORM_USETHREADS +#define bdbhpt_threads DB_THREAD +#else +#define bdbhpt_threads 0 +#endif + +/* 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 formated 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 postive 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_put(mctx, db, sizeof(bdbhpt_instance_t)); + isc_mem_detach(&mctx); + } +} + +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 futher lookups because the zone doesn't match the + * first time. + */ + key.data = bdbhpt_strrev(key.data); + key.size = strlen(key.data); + + switch(db->zone->get(db->zone, NULL, &key, &data, 0)) { + case DB_NOTFOUND: + result = ISC_R_NOTFOUND; + break; + case 0: + result = ISC_R_SUCCESS; + break; + default: + result = ISC_R_FAILURE; + } + + /* free any memory duplicate string in the key field */ + if (key.data != NULL) + free(key.data); + + /* free any memory allocated to the data field. */ + if (data.data != NULL) + free(data.data); + + return result; +} + +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); + + if (keyStr != NULL) + 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 saftey - 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(ns_g_mctx, sizeof(bdbhpt_instance_t)); + if (db == NULL) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Could not allocate memory for " + "database instance object."); + return (ISC_R_NOMEMORY); + } + memset(db, 0, sizeof(bdbhpt_instance_t)); + + /* attach to the memory context */ + isc_mem_attach(ns_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, + ns_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 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..4e42d80 --- /dev/null +++ b/contrib/dlz/drivers/dlz_drivers.c @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2005 Internet Systems Consortium, Inc. ("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 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. + */ + + +/*! \file */ + +#include <config.h> + +#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_POSTGRES +#include <dlz/dlz_postgres_driver.h> +#endif + +#ifdef DLZ_MYSQL +#include <dlz/dlz_mysql_driver.h> +#endif + +#ifdef DLZ_FILESYSTEM +#include <dlz/dlz_filesystem_driver.h> +#endif + +#ifdef DLZ_BDB +#include <dlz/dlz_bdb_driver.h> +#include <dlz/dlz_bdbhpt_driver.h> +#endif + +#ifdef DLZ_LDAP +#include <dlz/dlz_ldap_driver.h> +#endif + +#ifdef DLZ_ODBC +#include <dlz/dlz_odbc_driver.h> +#endif + +/*% + * 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_POSTGRES + result = dlz_postgres_init(); + if (result != ISC_R_SUCCESS) + return (result); +#endif + +#ifdef DLZ_MYSQL + result = dlz_mysql_init(); + if (result != ISC_R_SUCCESS) + return (result); +#endif + +#ifdef DLZ_FILESYSTEM + result = dlz_fs_init(); + if (result != ISC_R_SUCCESS) + return (result); +#endif + +#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_LDAP + result = dlz_ldap_init(); + if (result != ISC_R_SUCCESS) + return (result); +#endif + +#ifdef DLZ_ODBC + result = dlz_odbc_init(); + if (result != ISC_R_SUCCESS) + return (result); +#endif + + return (result); +} + +/*% + * Call shutdown functions for all relevant DLZ drivers. + */ + +void +dlz_drivers_clear(void) { + +#ifdef DLZ_STUB + dlz_stub_clear(); +#endif + +#ifdef DLZ_POSTGRES + dlz_postgres_clear(); +#endif + +#ifdef DLZ_MYSQL + dlz_mysql_clear(); +#endif + +#ifdef DLZ_FILESYSTEM + dlz_fs_clear(); +#endif + +#ifdef DLZ_BDB + dlz_bdb_clear(); + dlz_bdbhpt_clear(); +#endif + +#ifdef DLZ_LDAP + dlz_ldap_clear(); +#endif + +#ifdef DLZ_ODBC + dlz_odbc_clear(); +#endif + +} diff --git a/contrib/dlz/drivers/dlz_filesystem_driver.c b/contrib/dlz/drivers/dlz_filesystem_driver.c new file mode 100644 index 0000000..c460d98 --- /dev/null +++ b/contrib/dlz/drivers/dlz_filesystem_driver.c @@ -0,0 +1,1067 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Copyright (C) 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 <config.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include <sys/stat.h> + +#include <dns/log.h> +#include <dns/sdlz.h> +#include <dns/result.h> + +#include <isc/dir.h> +#include <isc/mem.h> +#include <isc/platform.h> +#include <isc/print.h> +#include <isc/result.h> +#include <isc/util.h> + +#include <named/globals.h> + +#include <dlz/dlz_filesystem_driver.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[ISC_DIR_PATHMAX]; + 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. */ + else if (input[i-1] == '.') + return (false); + /* '.' is not allowed as last char */ + if (i == len) + 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 = isc_mem_strdup(ns_g_mctx, 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; + } + + isc_mem_free(ns_g_mctx, 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; + + /* 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(ns_g_mctx , pathsize * sizeof(char)); + if (tmpPath == NULL) { + /* write error message */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, 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); + if ((result = create_path_helper(tmpPath, host, + cd)) != 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) + isc_mem_free(ns_g_mctx, tmpPath); + + /* free tmpPath memory */ + return (result); +} + +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[ISC_DIR_PATHMAX + ISC_DIR_NAMEMAX]; + int astPos; + struct stat sb; + isc_result_t result = ISC_R_FAILURE; + char *endp; + char *type; + char *ttlStr; + char *data; + char host[ISC_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) + > ISC_DIR_NAMEMAX) + continue; + strcat(host, tmpPtr + 1); + strcat(host, "."); + tmpPtr[0] = '\0'; + } + if ((strlen(host) + + strlen(tmpString) + 1) + <= ISC_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 (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) + 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 */ + 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(ns_g_mctx, + sizeof(dir_entry_t)); + if (direntry == NULL) + return (ISC_R_NOMEMORY); + 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 == false) { + 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 postive 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(ns_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(ns_g_mctx, sizeof(dlist_t)); + if (dir_list == NULL) { + result = ISC_R_NOTFOUND; + goto complete_allnds; + } + + /* initialize list */ + ISC_LIST_INIT(*dir_list); + + if (create_path(zone, NULL, NULL, cd, &basepath) != ISC_R_SUCCESS) { + return (ISC_R_NOTFOUND); + } + + /* remove path separator at end of path so stat works properly */ + basepathlen = strlen(basepath); + + if (stat(basepath, &sb) != 0) { + result = ISC_R_NOTFOUND; + goto complete_allnds; + } + + if ((sb.st_mode & S_IFDIR) == 0) { + result = ISC_R_NOTFOUND; + goto complete_allnds; + } + + /* initialize and open directory */ + 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: + if (dir_list != NULL) { + /* 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(ns_g_mctx, dir_entry, sizeof(dir_entry_t)); + dir_entry = next_de; + } /* end while */ + isc_mem_put(ns_g_mctx, dir_list, sizeof(dlist_t)); + } + + if (basepath != NULL) + isc_mem_free(ns_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(ns_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(ns_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(ns_g_mctx, sizeof(config_data_t)); + if (cd == NULL) + goto no_mem; + + /* zero the memory */ + memset(cd, 0, sizeof(config_data_t)); + + cd->pathsep = pathsep; + + /* get and store our base directory */ + cd->basedir = isc_mem_strdup(ns_g_mctx, argv[1]); + if (cd->basedir == NULL) + goto no_mem; + cd->basedirsize = strlen(cd->basedir); + + /* get and store our data sub-dir */ + cd->datadir = isc_mem_strdup(ns_g_mctx, argv[2]); + if (cd->datadir == NULL) + goto no_mem; + cd->datadirsize = strlen(cd->datadir); + + /* get and store our zone xfr sub-dir */ + cd->xfrdir = isc_mem_strdup(ns_g_mctx, 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) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Directory split count must be zero (0) " + "or a postive number"); + } + + /* get and store our separator character */ + cd->separator = *argv[5]; + + /* attach config data to memory context */ + isc_mem_attach(ns_g_mctx, &cd->mctx); + + /* pass back config data */ + *dbdata = cd; + + /* return success */ + return (ISC_R_SUCCESS); + + /* handle no memory error */ + no_mem: + + /* if we allocated a config data object clean it up */ + if (cd != NULL) + fs_destroy(NULL, cd); + + /* write error message */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Filesystem driver unable to " + "allocate memory for config data."); + + /* return error */ + return (ISC_R_NOMEMORY); +} + +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(ns_g_mctx, cd->basedir); + + if (cd->datadir != NULL) + isc_mem_free(ns_g_mctx, cd->datadir); + + if (cd->xfrdir != NULL) + isc_mem_free(ns_g_mctx, cd->xfrdir); + + /* hold memory context to use later */ + mctx = cd->mctx; + + /* free config data memory */ + isc_mem_put(mctx, cd, sizeof(config_data_t)); + + /* detach memory from context */ + isc_mem_detach(&mctx); +} + +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, + ns_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 diff --git a/contrib/dlz/drivers/dlz_ldap_driver.c b/contrib/dlz/drivers/dlz_ldap_driver.c new file mode 100644 index 0000000..cf153a9 --- /dev/null +++ b/contrib/dlz/drivers/dlz_ldap_driver.c @@ -0,0 +1,1341 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + * + * The development of Dynamically Loadable Zones (DLZ) for BIND 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Copyright (C) 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 <config.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include <dns/log.h> +#include <dns/sdlz.h> +#include <dns/result.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 <named/globals.h> + +#include <dlz/sdlz_helper.h> +#include <dlz/dlz_ldap_driver.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 everthing needed by this "instance" of the LDAP + * driver remember, the driver code is only loaded once, but may have + * many separate instances. + */ + +typedef struct { +#ifdef ISC_PLATFORM_USETHREADS + db_list_t *db; /*%< handle to a list of DB */ +#else + dbinstance_t *db; /*%< handle to db */ +#endif + int method; /*%< security authentication method */ + char *user; /*%< who is authenticating */ + char *cred; /*%< password for simple authentication method */ + int protocol; /*%< LDAP communication protocol version */ + char *hosts; /*%< LDAP server hosts */ +} 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); +} + +#ifdef ISC_PLATFORM_USETHREADS + + +/*% + * 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(ns_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); +} +#endif /* ISC_PLATFORM_USETHREADS */ + +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(ns_g_mctx, len + 1); + if (data == NULL) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "LDAP driver unable to allocate memory " + "while processing results"); + result = ISC_R_FAILURE; + goto cleanup; + } + + /* + * Make sure data is null termed at the beginning so + * we can check if any data was stored to it later. + */ + data[0] = '\0'; + + /* reset j to re-use below */ + j = 0; + + /* loop through the attributes in the order specified. */ + while (attribute != NULL) { + /* get the list of values for this attribute. */ + vals = ldap_get_values(dbc, entry, attribute); + + /* skip empty attributes. */ + if (vals == NULL || vals[0] == NULL) { + /* increment attibute pointer */ + attribute = attrs[++i]; + /* start loop over */ + continue; + } + + /* + * j initially = 0. Increment j each time we + * set a field that way next loop will set + * next field. + */ + switch(j) { + case 0: + j++; + /* + * convert text to int, make sure it + * worked right + */ + ttl = strtol(vals[0], &endp, 10); + if (*endp != '\0' || ttl < 0) { + isc_log_write(dns_lctx, + DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, + ISC_LOG_ERROR, + "LDAP driver ttl must " + "be a postive number"); + goto cleanup; + } + break; + case 1: + j++; + type = isc_mem_strdup(ns_g_mctx, vals[0]); + break; + case 2: + j++; + if (allnodes) + host = isc_mem_strdup(ns_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 attibute 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(ns_g_mctx, type); + isc_mem_free(ns_g_mctx, data); + if (host != NULL) + isc_mem_free(ns_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(ns_g_mctx, host); + if (type != NULL) + isc_mem_free(ns_g_mctx, type); + if (data != NULL) + isc_mem_free(ns_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 */ +#ifdef ISC_PLATFORM_USETHREADS + + /* find an available DBI from the list */ + dbi = ldap_find_avail_conn((db_list_t *) + ((ldap_instance_t *)dbdata)->db); + +#else /* ISC_PLATFORM_USETHREADS */ + + /* + * only 1 DBI - no need to lock instance lock either + * only 1 thread in the whole process, no possible contention. + */ + dbi = (dbinstance_t *) ((ldap_instance_t *)dbdata)->db; + +#endif /* ISC_PLATFORM_USETHREADS */ + + /* 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(ns_g_mctx, zone); + if (dbi->zone == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + } else { + dbi->zone = NULL; + } + if (record != NULL) { + dbi->record = isc_mem_strdup(ns_g_mctx, record); + if (dbi->record == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + } else { + dbi->record = NULL; + } + if (client != NULL) { + dbi->client = isc_mem_strdup(ns_g_mctx, 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(ns_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(ns_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(ns_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(ns_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(ns_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 syncronously */ + ldap_result = ldap_search_s((LDAP *) dbi->dbconn, + ldap_url->lud_dn, + ldap_url->lud_scope, + ldap_url->lud_filter, + ldap_url->lud_attrs, 0, &ldap_msg); + + /* + * check return code. No such object is ok, just + * didn't find what we wanted + */ + switch(ldap_result) { + case LDAP_NO_SUCH_OBJECT: + 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(ns_g_mctx, dbi->zone); + if (dbi->record != NULL) + isc_mem_free(ns_g_mctx, dbi->record); + if (dbi->client != NULL) + isc_mem_free(ns_g_mctx, dbi->client); + +#ifdef ISC_PLATFORM_USETHREADS + + /* release the lock so another thread can use this dbi */ + isc_mutex_unlock(&dbi->instance_lock); + +#endif /* ISC_PLATFORM_USETHREADS */ + + /* release query string */ + if (querystring != NULL) + isc_mem_free(ns_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; + +#ifdef ISC_PLATFORM_USETHREADS + /* if multi-threaded, we need a few extra variables. */ + int dbcount; + char *endp; +/* db_list_t *dblist = NULL; */ + int i; + +#endif /* ISC_PLATFORM_USETHREADS */ + + UNUSED(dlzname); + UNUSED(driverarg); + +#ifdef ISC_PLATFORM_USETHREADS + /* 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"); +#else /* ISC_PLATFORM_USETHREADS */ + /* if debugging, let user know we are single threaded. */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(1), + "LDAP driver running single threaded"); +#endif /* ISC_PLATFORM_USETHREADS */ + + 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); + } + + /* multithreaded build can have multiple DB connections */ +#ifdef ISC_PLATFORM_USETHREADS + + /* 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); + } +#endif + + /* 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); + case 11: + result = dlz_ldap_checkURL(argv[10], 3, "all nodes"); + if (result != ISC_R_SUCCESS) + return (result); + case 10: + if (strlen(argv[9]) > 0) { + result = dlz_ldap_checkURL(argv[9], 3, "authority"); + if (result != ISC_R_SUCCESS) + return (result); + } + 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(ns_g_mctx, sizeof(ldap_instance_t)); + if (ldap_inst == NULL) + return (ISC_R_NOMEMORY); + 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(ns_g_mctx, argv[6]); + if (ldap_inst->hosts == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + ldap_inst->user = isc_mem_strdup(ns_g_mctx, argv[4]); + if (ldap_inst->user == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + ldap_inst->cred = isc_mem_strdup(ns_g_mctx, argv[5]); + if (ldap_inst->cred == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + +#ifdef ISC_PLATFORM_USETHREADS + /* allocate memory for database connection list */ + ldap_inst->db = isc_mem_get(ns_g_mctx, sizeof(db_list_t)); + if (ldap_inst->db == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + /* 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++) { + +#endif /* ISC_PLATFORM_USETHREADS */ + + /* how many queries were passed in from config file? */ + switch(argc) { + case 9: + result = build_sqldbinstance(ns_g_mctx, NULL, NULL, + NULL, argv[7], argv[8], + NULL, &dbi); + break; + case 10: + result = build_sqldbinstance(ns_g_mctx, NULL, NULL, + argv[9], argv[7], argv[8], + NULL, &dbi); + break; + case 11: + result = build_sqldbinstance(ns_g_mctx, argv[10], NULL, + argv[9], argv[7], argv[8], + NULL, &dbi); + break; + case 12: + result = build_sqldbinstance(ns_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; + } + +#ifdef ISC_PLATFORM_USETHREADS + /* when multithreaded, build a list of DBI's */ + ISC_LINK_INIT(dbi, link); + ISC_LIST_APPEND(*(ldap_inst->db), dbi, link); +#else + /* + * when single threaded, hold onto the one connection + * instance. + */ + ldap_inst->db = dbi; + +#endif + /* 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: +#ifdef ISC_PLATFORM_USETHREADS + 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); +#else + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "LDAP driver could not allocate memory " + "for connection"); +#endif + 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: +#ifdef ISC_PLATFORM_USETHREADS + 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); +#else + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "LDAP driver could not " + "bind connection to server."); +#endif + 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) */ + + +#ifdef ISC_PLATFORM_USETHREADS + + /* set DBI = null for next loop through. */ + dbi = NULL; + } /* end for loop */ + +#endif /* ISC_PLATFORM_USETHREADS */ + + + /* 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) { +#ifdef ISC_PLATFORM_USETHREADS + /* cleanup the list of DBI's */ + ldap_destroy_dblist((db_list_t *) + ((ldap_instance_t *)dbdata)->db); + +#else /* ISC_PLATFORM_USETHREADS */ + if (((ldap_instance_t *)dbdata)->db->dbconn != NULL) + ldap_unbind_s((LDAP *) + ((ldap_instance_t *)dbdata)->db->dbconn); + + /* destroy single DB instance */ + destroy_sqldbinstance(((ldap_instance_t *)dbdata)->db); +#endif /* ISC_PLATFORM_USETHREADS */ + + if (((ldap_instance_t *)dbdata)->hosts != NULL) + isc_mem_free(ns_g_mctx, + ((ldap_instance_t *)dbdata)->hosts); + + if (((ldap_instance_t *)dbdata)->user != NULL) + isc_mem_free(ns_g_mctx, + ((ldap_instance_t *)dbdata)->user); + + if (((ldap_instance_t *)dbdata)->cred != NULL) + isc_mem_free(ns_g_mctx, + ((ldap_instance_t *)dbdata)->cred); + + isc_mem_put(ns_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, + ns_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 diff --git a/contrib/dlz/drivers/dlz_mysql_driver.c b/contrib/dlz/drivers/dlz_mysql_driver.c new file mode 100644 index 0000000..e7ac52b --- /dev/null +++ b/contrib/dlz/drivers/dlz_mysql_driver.c @@ -0,0 +1,1079 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Copyright (C) 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 <config.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include <dns/log.h> +#include <dns/sdlz.h> +#include <dns/result.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 <named/globals.h> + +#include <dlz/sdlz_helper.h> +#include <dlz/dlz_mysql_driver.h> + +#include <mysql.h> + +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(ns_g_mctx ,(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 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) { + result = ISC_R_FAILURE; + goto cleanup; + } + + /* 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(ns_g_mctx, dbi->allnodes_q); + break; + case ALLOWXFR: + querystring = build_querystring(ns_g_mctx, dbi->allowxfr_q); + break; + case AUTHORITY: + querystring = build_querystring(ns_g_mctx, dbi->authority_q); + break; + case FINDZONE: + querystring = build_querystring(ns_g_mctx, dbi->findzone_q); + break; + case COUNTZONE: + querystring = build_querystring(ns_g_mctx, dbi->countzone_q); + break; + case LOOKUP: + querystring = build_querystring(ns_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 */ + + /* if we couldn't even get DBI, just return NULL */ + if (dbi == NULL) + return ISC_R_FAILURE; + + /* free dbi->zone string */ + if (dbi->zone != NULL) + isc_mem_free(ns_g_mctx, dbi->zone); + + /* free dbi->record string */ + if (dbi->record != NULL) + isc_mem_free(ns_g_mctx, dbi->record); + + /* free dbi->client string */ + if (dbi->client != NULL) + isc_mem_free(ns_g_mctx, dbi->client); + + /* release query string */ + if (querystring != NULL) + isc_mem_free(ns_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 postive 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(ns_g_mctx, len + 1); + if (tmpString == NULL) { + /* major bummer, need more ram */ + isc_log_write(dns_lctx, + DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "mysql driver unable " + "to allocate memory for " + "temporary string"); + mysql_free_result(rs); + return (ISC_R_FAILURE); /* Yeah, I'd say! */ + } + /* 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 postive 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(ns_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 postive 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(ns_g_mctx, len + 1); + if (tmpString == NULL) { /* we need more ram. */ + isc_log_write(dns_lctx, + DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "mysql driver unable " + "to allocate memory for " + "temporary string"); + mysql_free_result(rs); + return (ISC_R_FAILURE); + } + /* copy this field to tmpString */ + strcpy(tmpString, safeGet(row[3])); + /* concatonate 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(ns_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; +#if MYSQL_VERSION_ID >= 50000 + my_bool auto_reconnect = 1; +#endif + + 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 paramters. */ + + /* 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(ns_g_mctx, tmp); + result = ISC_R_FAILURE; + goto full_cleanup; + } + isc_mem_free(ns_g_mctx, tmp); + } + + /* how many queries were passed in from config file? */ + switch(argc) { + case 4: + result = build_sqldbinstance(ns_g_mctx, NULL, NULL, NULL, + argv[2], argv[3], NULL, &dbi); + break; + case 5: + result = build_sqldbinstance(ns_g_mctx, NULL, NULL, argv[4], + argv[2], argv[3], NULL, &dbi); + break; + case 6: + result = build_sqldbinstance(ns_g_mctx, argv[5], NULL, argv[4], + argv[2], argv[3], NULL, &dbi); + break; + case 7: + result = build_sqldbinstance(ns_g_mctx, argv[5], + argv[6], argv[4], + argv[2], argv[3], NULL, &dbi); + break; + case 8: + result = build_sqldbinstance(ns_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(ns_g_mctx, tmp); + } + + tmp = getParameterValue(argv[1], "ssl="); + if (tmp != NULL) { + if (strcasecmp(tmp, "true") == 0) + flags = flags | CLIENT_SSL; + isc_mem_free(ns_g_mctx, tmp); + } + + tmp = getParameterValue(argv[1], "space="); + if (tmp != NULL) { + if (strcasecmp(tmp, "ignore") == 0) + flags = flags | CLIENT_IGNORE_SPACE; + isc_mem_free(ns_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="); + +#if MYSQL_VERSION_ID >= 50000 + /* enable automatic reconnection. */ + if (mysql_options((MYSQL *) dbi->dbconn, MYSQL_OPT_RECONNECT, + &auto_reconnect) != 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"); + } +#endif + + 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(ns_g_mctx, dbname); + if (host != NULL) + isc_mem_free(ns_g_mctx, host); + if (user != NULL) + isc_mem_free(ns_g_mctx, user); + if (pass != NULL) + isc_mem_free(ns_g_mctx, pass); + if (socket != NULL) + isc_mem_free(ns_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, + ns_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 diff --git a/contrib/dlz/drivers/dlz_odbc_driver.c b/contrib/dlz/drivers/dlz_odbc_driver.c new file mode 100644 index 0000000..27a8526 --- /dev/null +++ b/contrib/dlz/drivers/dlz_odbc_driver.c @@ -0,0 +1,1572 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Copyright (C) 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 <config.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include <dns/log.h> +#include <dns/sdlz.h> +#include <dns/result.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 <named/globals.h> + +#include <dlz/sdlz_helper.h> +#include <dlz/dlz_odbc_driver.h> + +#include <sql.h> +#include <sqlext.h> +#include <sqltypes.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 everthing needed by this "instance" of the odbc driver + * remember, the driver code is only loaded once, but may have many separate + * instances + */ + +typedef struct { + +#ifdef ISC_PLATFORM_USETHREADS + + db_list_t *db; /* handle to a list of DB */ + +#else + + dbinstance_t *db; /* handle to db */ + +#endif + + 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); +} + +/*% propertly cleans up an odbc_instance_t */ + +static void +destroy_odbc_instance(odbc_instance_t *odbc_inst) { + +#ifdef ISC_PLATFORM_USETHREADS + + 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(ns_g_mctx, dbi->dbconn); + } + /* release all memory that comprised a DBI */ + destroy_sqldbinstance(dbi); + } + /* release memory for the list structure */ + isc_mem_put(ns_g_mctx, odbc_inst->db, sizeof(db_list_t)); + +#else /* ISC_PLATFORM_USETHREADS */ + + /* free statement handle */ + if (((odbc_db_t *) (odbc_inst->db->dbconn))->stmnt != NULL) { + SQLFreeHandle(SQL_HANDLE_STMT, + ((odbc_db_t *) (odbc_inst->db->dbconn))->stmnt); + ((odbc_db_t *) (odbc_inst->db->dbconn))->stmnt = NULL; + } + + /* disconnect from database, free connection handle */ + if (((odbc_db_t *) (odbc_inst->db->dbconn))->dbc != NULL) { + SQLDisconnect(((odbc_db_t *) (odbc_inst->db->dbconn))->dbc); + SQLFreeHandle(SQL_HANDLE_DBC, + ((odbc_db_t *) (odbc_inst->db->dbconn))->dbc); + ((odbc_db_t *) (odbc_inst->db->dbconn))->dbc = NULL; + } + /* free mem for the odbc_db_t structure held in db */ + if (((odbc_db_t *) odbc_inst->db->dbconn) != NULL) { + isc_mem_free(ns_g_mctx, odbc_inst->db->dbconn); + odbc_inst->db->dbconn = NULL; + } + + if (odbc_inst->db != NULL) + destroy_sqldbinstance(odbc_inst->db); + +#endif /* ISC_PLATFORM_USETHREADS */ + + + /* 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(ns_g_mctx, odbc_inst->dsn); + if (odbc_inst->pass != NULL) + isc_mem_free(ns_g_mctx, odbc_inst->pass); + if (odbc_inst->user != NULL) + isc_mem_free(ns_g_mctx, odbc_inst->user); + + /* free memory for odbc_inst */ + if (odbc_inst != NULL) + isc_mem_put(ns_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(ns_g_mctx, sizeof(odbc_db_t)); + if (ndb == NULL) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Odbc driver unable to allocate memory"); + return ISC_R_NOMEMORY; + } + 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(ns_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. + */ + +#ifdef ISC_PLATFORM_USETHREADS + +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; +} + +#endif /* ISC_PLATFORM_USETHREADS */ + +/*% 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(ns_g_mctx ,(2 * len * sizeof(char)) + 1); + if (outstr == NULL) + return NULL; + + 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 successfull, 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 successfull, 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 */ +#ifdef ISC_PLATFORM_USETHREADS + + /* find an available DBI from the list */ + dbi = odbc_find_avail_conn(((odbc_instance_t *) dbdata)->db); + +#else /* ISC_PLATFORM_USETHREADS */ + + /* + * only 1 DBI - no need to lock instance lock either + * only 1 thread in the whole process, no possible contention. + */ + dbi = (dbinstance_t *) ((odbc_instance_t *) dbdata)->db; + +#endif /* ISC_PLATFORM_USETHREADS */ + + /* if DBI is null, can't do anything else */ + if (dbi == NULL) { + result = ISC_R_FAILURE; + goto cleanup; + } + + /* 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(ns_g_mctx, dbi->allnodes_q); + break; + case ALLOWXFR: + querystring = build_querystring(ns_g_mctx, dbi->allowxfr_q); + break; + case AUTHORITY: + querystring = build_querystring(ns_g_mctx, dbi->authority_q); + break; + case FINDZONE: + querystring = build_querystring(ns_g_mctx, dbi->findzone_q); + break; + case LOOKUP: + querystring = build_querystring(ns_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; + /* incase 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 */ + + /* if we couldn't even allocate DBI, just return NULL */ + if (dbi == NULL) + return ISC_R_FAILURE; + + /* free dbi->zone string */ + if (dbi->zone != NULL) + isc_mem_free(ns_g_mctx, dbi->zone); + + /* free dbi->record string */ + if (dbi->record != NULL) + isc_mem_free(ns_g_mctx, dbi->record); + + /* free dbi->client string */ + if (dbi->client != NULL) + isc_mem_free(ns_g_mctx, dbi->client); + +#ifdef ISC_PLATFORM_USETHREADS + + /* if we are done using this dbi, release the lock */ + if (result != ISC_R_SUCCESS) + isc_mutex_unlock(&dbi->instance_lock); + +#endif /* ISC_PLATFORM_USETHREADS */ + + /* release query string */ + if (querystring != NULL) + isc_mem_free(ns_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 reponsibility of the caller to free the memory using + * isc_mem_free(ns_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(ns_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(ns_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 reponsibility of the caller to free the memory using + * isc_mem_free(ns_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(ns_g_mctx, ++totSize); + if (data == NULL) + return ISC_R_NOMEMORY; + + 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(ns_g_mctx, data); + return ISC_R_FAILURE; + } + } + + if (result != ISC_R_SUCCESS) { + isc_mem_free(ns_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 postive 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(ns_g_mctx, ttl_s); + if (type != NULL) + isc_mem_free(ns_g_mctx, type); + if (data != NULL) + isc_mem_free(ns_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); + +#ifdef ISC_PLATFORM_USETHREADS + + /* free lock on dbi so someone else can use it. */ + isc_mutex_unlock(&dbi->instance_lock); + +#endif + + 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); + +#ifdef ISC_PLATFORM_USETHREADS + + /* free lock on dbi so someone else can use it. */ + isc_mutex_unlock(&dbi->instance_lock); +#endif + } + + 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); + +#ifdef ISC_PLATFORM_USETHREADS + + /* free lock on dbi so someone else can use it. */ + isc_mutex_unlock(&dbi->instance_lock); +#endif + + } + + 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 postive 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(ns_g_mctx, ttl_s); + if (type != NULL) + isc_mem_free(ns_g_mctx, type); + if (host != NULL) + isc_mem_free(ns_g_mctx, host); + if (data != NULL) + isc_mem_free(ns_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); + +#ifdef ISC_PLATFORM_USETHREADS + + /* free lock on dbi so someone else can use it. */ + isc_mutex_unlock(&dbi->instance_lock); + +#endif + + 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; + +#ifdef ISC_PLATFORM_USETHREADS + /* if multi-threaded, we need a few extra variables. */ + int dbcount; + int i; + char *endp; + +#endif /* ISC_PLATFORM_USETHREADS */ + + UNUSED(dlzname); + UNUSED(driverarg); + +#ifdef ISC_PLATFORM_USETHREADS + /* 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"); +#else /* ISC_PLATFORM_USETHREADS */ + /* if debugging, let user know we are single threaded. */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(1), + "Odbc driver running single threaded"); +#endif /* ISC_PLATFORM_USETHREADS */ + + /* 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 */ +#ifdef ISC_PLATFORM_USETHREADS + + /* 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); + } + +#endif /* ISC_PLATFORM_USETHREADS */ + + /* allocate memory for odbc instance */ + odbc_inst = isc_mem_get(ns_g_mctx, sizeof(odbc_instance_t)); + if (odbc_inst == NULL) + return (ISC_R_NOMEMORY); + memset(odbc_inst, 0, sizeof(odbc_instance_t)); + + /* parse connection string and get paramters. */ + + /* 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; + } + } + +#ifdef ISC_PLATFORM_USETHREADS + + /* allocate memory for database connection list */ + odbc_inst->db = isc_mem_get(ns_g_mctx, sizeof(db_list_t)); + if (odbc_inst->db == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + + /* 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++) { + +#endif /* ISC_PLATFORM_USETHREADS */ + + /* how many queries were passed in from config file? */ + switch(argc) { + case 5: + result = build_sqldbinstance(ns_g_mctx, NULL, NULL, + NULL, argv[3], argv[4], + NULL, &db); + break; + case 6: + result = build_sqldbinstance(ns_g_mctx, NULL, NULL, + argv[5], argv[3], argv[4], + NULL, &db); + break; + case 7: + result = build_sqldbinstance(ns_g_mctx, argv[6], NULL, + argv[5], argv[3], argv[4], + NULL, &db); + break; + case 8: + result = build_sqldbinstance(ns_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; + } + +#ifdef ISC_PLATFORM_USETHREADS + + /* when multithreaded, build a list of DBI's */ + ISC_LINK_INIT(db, link); + ISC_LIST_APPEND(*odbc_inst->db, db, link); + +#endif + + result = odbc_connect(odbc_inst, (odbc_db_t **) &(db->dbconn)); + + if (result != ISC_R_SUCCESS) { + +#ifdef ISC_PLATFORM_USETHREADS + + /* + * 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); +#else + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Odbc driver failed to create database " + "connection after 3 attempts"); +#endif + goto cleanup; + } + +#ifdef ISC_PLATFORM_USETHREADS + + /* set DB = null for next loop through. */ + db = NULL; + + } /* end for loop */ + +#else + /* tell odbc_inst about the db connection we just created. */ + odbc_inst->db = db; + +#endif + + /* 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, + ns_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 diff --git a/contrib/dlz/drivers/dlz_postgres_driver.c b/contrib/dlz/drivers/dlz_postgres_driver.c new file mode 100644 index 0000000..7fc9ee3 --- /dev/null +++ b/contrib/dlz/drivers/dlz_postgres_driver.c @@ -0,0 +1,1381 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Copyright (C) 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 <config.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include <dns/log.h> +#include <dns/sdlz.h> +#include <dns/result.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 <named/globals.h> + +#include <dlz/sdlz_helper.h> +#include <dlz/dlz_postgres_driver.h> + +/* temporarily include time. */ +#include <time.h> + +#include <libpq-fe.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; +} + +#ifdef ISC_PLATFORM_USETHREADS + +/*% + * 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(ns_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; +} + +#endif /* ISC_PLATFORM_USETHREADS */ + +/*% + * 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(ns_g_mctx ,(2 * len * sizeof(char)) + 1); + if (outstr == NULL) + return NULL; + + 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 + + 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 + + /* get db instance / connection */ +#ifdef ISC_PLATFORM_USETHREADS + + /* find an available DBI from the list */ + dbi = postgres_find_avail_conn((db_list_t *) dbdata); + +#else /* ISC_PLATFORM_USETHREADS */ + + /* + * only 1 DBI - no need to lock instance lock either + * only 1 thread in the whole process, no possible contention. + */ + dbi = (dbinstance_t *) dbdata; + +#endif /* ISC_PLATFORM_USETHREADS */ + +#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 DBI is null, can't do anything else */ + if (dbi == NULL) { + result = ISC_R_FAILURE; + goto cleanup; + } + + /* 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 + + /* + * 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 + + /* + * 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 + + /* + * 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 + + /* + * 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(ns_g_mctx, dbi->allnodes_q); + break; + case ALLOWXFR: + querystring = build_querystring(ns_g_mctx, dbi->allowxfr_q); + break; + case AUTHORITY: + querystring = build_querystring(ns_g_mctx, dbi->authority_q); + break; + case FINDZONE: + querystring = build_querystring(ns_g_mctx, dbi->findzone_q); + break; + case LOOKUP: + querystring = build_querystring(ns_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 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 + + /* + * 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 + /* 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 + 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 outter 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 + 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 + 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 we couldn't even allocate DBI, just return NULL */ + if (dbi == NULL) + return ISC_R_FAILURE; + + /* free dbi->zone string */ + if (dbi->zone != NULL) + isc_mem_free(ns_g_mctx, dbi->zone); + + /* free dbi->record string */ + if (dbi->record != NULL) + isc_mem_free(ns_g_mctx, dbi->record); + + /* free dbi->client string */ + if (dbi->client != NULL) + isc_mem_free(ns_g_mctx, dbi->client); + +#ifdef ISC_PLATFORM_USETHREADS + +#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 + + /* release the lock so another thread can use this dbi */ + isc_mutex_unlock(&dbi->instance_lock); + +#endif /* ISC_PLATFORM_USETHREADS */ + + /* release query string */ + if (querystring != NULL) + isc_mem_free(ns_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 + + /* 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(ns_g_mctx, len + 1); + if (tmpString == NULL) { + /* major bummer, need more ram */ + isc_log_write(dns_lctx, + DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Postgres driver unable to " + "allocate memory for " + "temporary string"); + PQclear(rs); + return (ISC_R_FAILURE); /* Yeah, I'd say! */ + } + /* 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 postive 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(ns_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 postive 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(ns_g_mctx, len + 1); + if (tmpString == NULL) { /* we need more ram. */ + isc_log_write(dns_lctx, + DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_ERROR, + "Postgres driver unable to " + "allocate memory for " + "temporary string"); + PQclear(rs); + return (ISC_R_FAILURE); + } + /* copy this field to tmpString */ + strcpy(tmpString, PQgetvalue(rs, i, 3)); + /* concatonate 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(ns_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; + +#ifdef ISC_PLATFORM_USETHREADS + /* if multi-threaded, we need a few extra variables. */ + int dbcount; + db_list_t *dblist = NULL; + int i; + char *endp; + +#endif /* ISC_PLATFORM_USETHREADS */ + + UNUSED(driverarg); + UNUSED(dlzname); + +/* seed random # generator */ + srand( (unsigned)time( NULL ) ); + + +#ifdef ISC_PLATFORM_USETHREADS + /* 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"); +#else /* ISC_PLATFORM_USETHREADS */ + /* if debugging, let user know we are single threaded. */ + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, ISC_LOG_DEBUG(1), + "Postgres driver running single threaded"); +#endif /* ISC_PLATFORM_USETHREADS */ + + /* 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 */ +#ifdef ISC_PLATFORM_USETHREADS + + /* 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(ns_g_mctx, sizeof(db_list_t)); + if (dblist == NULL) + return (ISC_R_NOMEMORY); + + /* 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++) { + +#endif /* ISC_PLATFORM_USETHREADS */ + + /* how many queries were passed in from config file? */ + switch(argc) { + case 5: + result = build_sqldbinstance(ns_g_mctx, NULL, NULL, + NULL, argv[3], argv[4], + NULL, &dbi); + break; + case 6: + result = build_sqldbinstance(ns_g_mctx, NULL, NULL, + argv[5], argv[3], argv[4], + NULL, &dbi); + break; + case 7: + result = build_sqldbinstance(ns_g_mctx, argv[6], NULL, + argv[5], argv[3], argv[4], + NULL, &dbi); + break; + case 8: + result = build_sqldbinstance(ns_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; + } + +#ifdef ISC_PLATFORM_USETHREADS + + /* when multithreaded, build a list of DBI's */ + ISC_LINK_INIT(dbi, link); + ISC_LIST_APPEND(*dblist, dbi, link); + +#endif + + /* 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); + + +#ifdef ISC_PLATFORM_USETHREADS + + /* + * 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; + +#else /* ISC_PLATFORM_USETHREADS */ + /* if single threaded, just let user know we couldn't connect. */ + 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 after 4 attempts"); + goto cleanup; + } + + /* + * single threaded build can only use 1 db connection, return + * it via dbdata + */ + *dbdata = dbi; + +#endif /* ISC_PLATFORM_USETHREADS */ + + /* hey, we got through all of that ok, return success. */ + return(ISC_R_SUCCESS); + + cleanup: + +#ifdef ISC_PLATFORM_USETHREADS + /* + * 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); + +#else /* ISC_PLATFORM_USETHREADS */ + if (dbi != NULL) + destroy_sqldbinstance(dbi); + +#endif /* ISC_PLATFORM_USETHREADS */ + 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) +{ + +#ifdef ISC_PLATFORM_USETHREADS + + UNUSED(driverarg); + /* cleanup the list of DBI's */ + postgres_destroy_dblist((db_list_t *) dbdata); + +#else /* ISC_PLATFORM_USETHREADS */ + + dbinstance_t *dbi; + + UNUSED(driverarg); + + dbi = (dbinstance_t *) dbdata; + + /* release DB connection */ + if (dbi->dbconn != NULL) + PQfinish((PGconn *) dbi->dbconn); + + /* destroy single DB instance */ + destroy_sqldbinstance(dbi); + +#endif /* ISC_PLATFORM_USETHREADS */ +} + +/* 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, + ns_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 diff --git a/contrib/dlz/drivers/dlz_stub_driver.c b/contrib/dlz/drivers/dlz_stub_driver.c new file mode 100644 index 0000000..f873c1e --- /dev/null +++ b/contrib/dlz/drivers/dlz_stub_driver.c @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Copyright (C) 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 <config.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include <dns/log.h> +#include <dns/sdlz.h> +#include <dns/result.h> + +#include <isc/mem.h> +#include <isc/print.h> +#include <isc/result.h> +#include <isc/util.h> + +#include <named/globals.h> + +#include <dlz/dlz_stub_driver.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) +{ + UNUSED(driverarg); + UNUSED(dbdata); + UNUSED(name); + UNUSED(client); + return ISC_R_SUCCESS; +} + +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(ns_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(ns_g_mctx, argv[1]); + if (cd->myzone == NULL) { + isc_mem_put(ns_g_mctx, cd, sizeof(config_data_t)); + return (ISC_R_NOMEMORY); + } + + cd->myname = isc_mem_strdup(ns_g_mctx, argv[2]); + if (cd->myname == NULL) { + isc_mem_put(ns_g_mctx, cd, sizeof(config_data_t)); + isc_mem_free(ns_g_mctx, cd->myzone); + return (ISC_R_NOMEMORY); + } + + cd->myip = isc_mem_strdup(ns_g_mctx, argv[3]); + if (cd->myip == NULL) { + isc_mem_put(ns_g_mctx, cd, sizeof(config_data_t)); + isc_mem_free(ns_g_mctx, cd->myname); + isc_mem_free(ns_g_mctx, cd->myzone); + return (ISC_R_NOMEMORY); + } + + isc_mem_attach(ns_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(ns_g_mctx, cd->myzone); + isc_mem_free(ns_g_mctx, cd->myname); + isc_mem_free(ns_g_mctx, cd->myip); + mctx = cd->mctx; + isc_mem_put(mctx, cd, sizeof(config_data_t)); + isc_mem_detach(&mctx); +} + +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, + ns_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 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..1d706fc --- /dev/null +++ b/contrib/dlz/drivers/include/dlz/dlz_bdb_driver.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DLZ_BDB_DRIVER_H +#define DLZ_BDB_DRIVER_H + +isc_result_t +dlz_bdb_init(void); + +void +dlz_bdb_clear(void); + +#endif 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..0ccbc8d --- /dev/null +++ b/contrib/dlz/drivers/include/dlz/dlz_bdbhpt_driver.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DLZ_BDBHPT_DRIVER_H +#define DLZ_BDBHPT_DRIVER_H + +isc_result_t +dlz_bdbhpt_init(void); + +void +dlz_bdbhpt_clear(void); + +#endif 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..58ffada --- /dev/null +++ b/contrib/dlz/drivers/include/dlz/dlz_drivers.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2005 Internet Systems Consortium, Inc. ("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 ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + + +#ifndef DLZ_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..627427d --- /dev/null +++ b/contrib/dlz/drivers/include/dlz/dlz_filesystem_driver.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DLZ_FILESYSTEM_DRIVER_H +#define DLZ_FILESYSTEM_DRIVER_H + +isc_result_t +dlz_fs_init(void); + +void +dlz_fs_clear(void); + +#endif 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..51efbf4 --- /dev/null +++ b/contrib/dlz/drivers/include/dlz/dlz_ldap_driver.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DLZ_LDAP_DRIVER_H +#define DLZ_LDAP_DRIVER_H + +isc_result_t +dlz_ldap_init(void); + +void +dlz_ldap_clear(void); + +#endif 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..b742838 --- /dev/null +++ b/contrib/dlz/drivers/include/dlz/dlz_mysql_driver.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DLZ_MYSQL_DRIVER_H +#define DLZ_MYSQL_DRIVER_H + +isc_result_t +dlz_mysql_init(void); + +void +dlz_mysql_clear(void); + +#endif 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..5a072d8 --- /dev/null +++ b/contrib/dlz/drivers/include/dlz/dlz_odbc_driver.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DLZ_ODBC_DRIVER_H +#define DLZ_ODBC_DRIVER_H + +isc_result_t +dlz_odbc_init(void); + +void +dlz_odbc_clear(void); + +#endif 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..3416dce --- /dev/null +++ b/contrib/dlz/drivers/include/dlz/dlz_postgres_driver.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DLZ_POSTGRES_DRIVER_H +#define DLZ_POSTGRES_DRIVER_H + +isc_result_t +dlz_postgres_init(void); + +void +dlz_postgres_clear(void); + +#endif 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..a517c69 --- /dev/null +++ b/contrib/dlz/drivers/include/dlz/dlz_stub_driver.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DLZ_STUB_DRIVER_H +#define DLZ_STUB_DRIVER_H + +isc_result_t +dlz_stub_init(void); + +void +dlz_stub_clear(void); + +#endif 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..66ed118 --- /dev/null +++ b/contrib/dlz/drivers/include/dlz/sdlz_helper.h @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef SDLZHELPER_H +#define SDLZHELPER_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); + +/* Compatability 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(ns_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..81d48d6 --- /dev/null +++ b/contrib/dlz/drivers/rules.in @@ -0,0 +1,48 @@ +# Copyright (C) 2005 Internet Systems Consortium, Inc. ("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 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..979488a --- /dev/null +++ b/contrib/dlz/drivers/sdlz_helper.c @@ -0,0 +1,514 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Copyright (C) 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 <config.h> + +#include <dns/log.h> +#include <dns/result.h> + +#include <isc/mem.h> +#include <isc/result.h> +#include <isc/string.h> +#include <isc/util.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 == true) + 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 *temp_str = NULL; + char *right_str = NULL; + query_list_t *tql; + query_segment_t *tseg = 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)); + /* couldn't allocate memory. Problem!! */ + if (tql == NULL) + return (ISC_R_NOMEMORY); + + /* initialize the query segment list */ + ISC_LIST_INIT(*tql); + + /* make a copy of query_str so we can chop it up */ + temp_str = right_str = isc_mem_strdup(mctx, query_str); + /* couldn't make a copy, problem!! */ + if (right_str == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + /* loop through the string and chop it up */ + while (right_str != NULL) { + /* allocate memory for tseg */ + tseg = isc_mem_get(mctx, sizeof(query_segment_t)); + if (tseg == NULL) { /* no memory, clean everything up. */ + result = ISC_R_NOMEMORY; + goto cleanup; + } + 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); + + /* + * split string at the first "$". set query segment to + * left portion + */ + tseg->sql = isc_mem_strdup(mctx, + isc_string_separate(&right_str, + "$")); + if (tseg->sql == NULL) { + /* no memory, clean everything up. */ + result = ISC_R_NOMEMORY; + goto cleanup; + } + /* 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 = (char**) 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 = (char**) record; + tseg->strlen = 0; + /* tseg->sql points in-directly poinsts 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 = (char**) client; + tseg->strlen = 0; + /* tseg->sql points in-directly poinsts to a string */ + tseg->direct = false; + foundclient = true; + } + } + + /* we don't need temp_str any more */ + isc_mem_free(mctx, temp_str); + /* + * add checks later to verify zone and record are found if + * necessary. + */ + + /* if this query requires %client%, make sure we found it */ + if (((flags & 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 temp_str */ + if (temp_str != NULL) + isc_mem_free(mctx, temp_str); + + flag_fail: + /* get rid of what was build of the query list */ + if (tql != NULL) + 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 == true) + 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); + /* couldn't allocate memory, We need more ram! */ + if (qs == NULL) + return NULL; + + *qs = 0; + /* start at the top of the list again */ + tseg = ISC_LIST_HEAD(*querylist); + while (tseg != NULL) { + if (tseg->direct == true) + /* 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)); + if (db == NULL) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_DLZ, 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->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 */ + result = isc_mutex_init(&db->instance_lock); + if (result != ISC_R_SUCCESS) { + UNEXPECTED_ERROR(__FILE__, __LINE__, + "isc_mutex_init() failed: %s", + isc_result_totext(result)); + goto cleanup; + } + + /* 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_put(mctx, dbi, sizeof(dbinstance_t)); + isc_mem_detach(&mctx); +} + +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..5e84043 --- /dev/null +++ b/contrib/dlz/example/Makefile @@ -0,0 +1,16 @@ +# 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..3c6ba8c --- /dev/null +++ b/contrib/dlz/example/README @@ -0,0 +1,243 @@ +OVERVIEW: + +DLZ (Dynamically Loadable Zones) is an extention 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 recieve + 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..345f776 --- /dev/null +++ b/contrib/dlz/example/dlz_example.c @@ -0,0 +1,760 @@ +/* + * Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This provides a very simple example of an external loadable DLZ + * driver, with update support. + */ + +#include <stdio.h> +#include <inttypes.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> + +#include "../modules/include/dlz_minimal.h" + +#ifdef WIN32 +#define STRTOK_R(a, b, c) strtok_s(a, b, c) +#elif defined(_REENTRANT) +#define STRTOK_R(a, b, c) strtok_r(a, b, c) +#else +#define STRTOK_R(a, b, c) strtok(a, b) +#endif + +#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 >= DNS_CLIENTINFO_VERSION) { + 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) { + strcpy(buf, "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, buf, sizeof(buf)); + } + + state->log(ISC_LOG_INFO, + "dlz_example: lookup connection from: %s", buf); + + found = true; + result = state->putrr(lookup, "TXT", 0, buf); + if (result != ISC_R_SUCCESS) + return (result); + } + + if (strcmp(name, "too-long") == 0) { + 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 + + 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..bc68bee --- /dev/null +++ b/contrib/dlz/example/named.conf @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * 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 retreives 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..445905f --- /dev/null +++ b/contrib/dlz/example/win32/DLLMain.c @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2001, 2004, 2007, 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 <windows.h> +#include <signal.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..795a2e8 --- /dev/null +++ b/contrib/dlz/modules/bdbhpt/Makefile @@ -0,0 +1,18 @@ +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..10f10a9 --- /dev/null +++ b/contrib/dlz/modules/bdbhpt/README.md @@ -0,0 +1,86 @@ +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..39112a4 --- /dev/null +++ b/contrib/dlz/modules/bdbhpt/dlz_bdbhpt_dynamic.c @@ -0,0 +1,826 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> + +#include <db.h> + +#include "dlz_minimal.h" + +/* should the bdb driver use threads. */ +#ifdef ISC_PLATFORM_USETHREADS +#define bdbhpt_threads DB_THREAD +#else +#define bdbhpt_threads 0 +#endif + +/* 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 + result = dlz_findzonedb(dbdata, name); +#endif + if (result != ISC_R_SUCCESS) + return (ISC_R_NOTFOUND); + + memset(&key, 0, sizeof(DBT)); + key.flags = DB_DBT_MALLOC; + key.data = strdup(name); + if (key.data == NULL) { + result = ISC_R_NOMEMORY; + goto xfr_cleanup; + } + key.size = strlen(key.data); + + memset(&data, 0, sizeof(DBT)); + data.flags = DB_DBT_MALLOC; + data.data = strdup(client); + if (data.data == NULL) { + result = ISC_R_NOMEMORY; + goto xfr_cleanup; + } + data.size = strlen(data.data); + + switch(db->client->get(db->client, NULL, &key, &data, DB_GET_BOTH)) { + case DB_NOTFOUND: + result = ISC_R_NOTFOUND; + break; + case 0: + result = ISC_R_SUCCESS; + break; + default: + result = ISC_R_FAILURE; + } + + xfr_cleanup: + /* free any memory duplicate string in the key field */ + if (key.data != NULL) + free(key.data); + + /* free any memory allocated to the data field. */ + if (data.data != NULL) + free(data.data); + + return result; +} + +/*% + * 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 +isc_result_t +dlz_findzonedb(void *dbdata, const char *name, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) +#endif +{ + isc_result_t result; + bdbhpt_instance_t *db = (bdbhpt_instance_t *) dbdata; + DBT key, data; + + memset(&key, 0, sizeof(DBT)); + memset(&data, 0, sizeof(DBT)); + data.flags = DB_DBT_MALLOC; + +#if DLZ_DLOPEN_VERSION >= 3 + UNUSED(methods); + UNUSED(clientinfo); +#endif + + 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 futher lookups because the zone doesn't match the + * first time. + */ + key.data = bdbhpt_strrev(key.data); + key.size = strlen(key.data); + + switch(db->zone->get(db->zone, NULL, &key, &data, 0)) { + case DB_NOTFOUND: + result = ISC_R_NOTFOUND; + break; + case 0: + result = ISC_R_SUCCESS; + break; + default: + result = ISC_R_FAILURE; + } + + /* free any memory duplicate string in the key field */ + if (key.data != NULL) + free(key.data); + + /* free any memory allocated to the data field. */ + if (data.data != NULL) + free(data.data); + + return result; +} + +/* + * 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 +isc_result_t dlz_lookup(const char *zone, const char *name, void *dbdata, + dns_sdlzlookup_t *lookup, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) +#endif +{ + isc_result_t result = ISC_R_NOTFOUND; + 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 + + 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); + + if (keyStr != NULL) + free(keyStr); + if (tmp != NULL) + free(tmp); + + return result; +} + +/*% + * Initialises, sets flags and then opens Berkeley databases. + */ +static isc_result_t +bdbhpt_opendb(log_t *log, DB_ENV *db_env, DBTYPE db_type, DB **db, + const char *db_name, char *db_file, int flags) +{ + int result; + + /* Initialise the database. */ + if ((result = db_create(db, db_env, 0)) != 0) { + log(ISC_LOG_ERROR, + "bdbhpt_dynamic: could not initialize %s database. " + "BerkeleyDB error: %s", + db_name, db_strerror(result)); + return ISC_R_FAILURE; + } + + /* set database flags. */ + if ((result = (*db)->set_flags(*db, flags)) != 0) { + log(ISC_LOG_ERROR, + "bdbhpt_dynamic: could not set flags for %s database. " + "BerkeleyDB error: %s", + db_name, db_strerror(result)); + return ISC_R_FAILURE; + } + + /* open the database. */ + if ((result = (*db)->open(*db, NULL, db_file, db_name, db_type, + DB_RDONLY | bdbhpt_threads, 0)) != 0) { + log(ISC_LOG_ERROR, + "bdbhpt_dynamic: could not open %s database in %s. " + "BerkeleyDB error: %s", + db_name, db_file, db_strerror(result)); + return ISC_R_FAILURE; + } + + return ISC_R_SUCCESS; +} + + +/* + * Called to initialize the driver + */ +isc_result_t +dlz_create(const char *dlzname, unsigned int argc, char *argv[], + void **dbdata, ...) +{ + isc_result_t result; + int bdbhptres; + int bdbFlags = 0; + bdbhpt_instance_t *db = NULL; + + const char *helper_name; + va_list ap; + + UNUSED(dlzname); + + /* Allocate memory for our db structures and helper functions */ + db = calloc(1, sizeof(struct bdbhpt_instance)); + if (db == NULL) + return (ISC_R_NOMEMORY); + + /* Fill in the helper functions */ + va_start(ap, dbdata); + while ((helper_name = va_arg(ap, const char *)) != NULL) + b9_add_helper(db, helper_name, va_arg(ap, void*)); + va_end(ap); + + /* verify we have 4 arg's passed to the driver */ + if (argc != 4) { + db->log(ISC_LOG_ERROR, + "bdbhpt_dynamic: please supply 3 command line args. " + "You supplied: %s", argc); + return (ISC_R_FAILURE); + } + + switch((char) *argv[1]) { + /* + * Transactional mode. Highest safety - lowest speed. + */ + case 'T': + case 't': + bdbFlags = DB_INIT_MPOOL | DB_INIT_LOCK | + DB_INIT_LOG | DB_INIT_TXN; + db->log(ISC_LOG_INFO, + "bdbhpt_dynamic: using transactional mode."); + break; + + /* + * Concurrent mode. Lower safety (no rollback) - + * higher speed. + */ + case 'C': + case 'c': + bdbFlags = DB_INIT_CDB | DB_INIT_MPOOL; + db->log(ISC_LOG_INFO, + "bdbhpt_dynamic: using concurrent mode."); + break; + + /* + * Private mode. No inter-process communication & no locking. + * Lowest saftey - highest speed. + */ + case 'P': + case 'p': + bdbFlags = DB_PRIVATE | DB_INIT_MPOOL; + db->log(ISC_LOG_INFO, + "bdbhpt_dynamic: using private mode."); + break; + default: + db->log(ISC_LOG_ERROR, + "bdbhpt_dynamic: " + "operating mode must be set to P or C or T. " + "You specified '%s'", argv[1]); + return (ISC_R_FAILURE); + } + + /* + * create bdbhpt environment + * Basically bdbhpt allocates and assigns memory to db->dbenv + */ + bdbhptres = db_env_create(&db->dbenv, 0); + if (bdbhptres != 0) { + db->log(ISC_LOG_ERROR, + "bdbhpt_dynamic: db environment could not be created. " + "BerkeleyDB error: %s", db_strerror(bdbhptres)); + result = ISC_R_FAILURE; + goto init_cleanup; + } + + /* open bdbhpt environment */ + bdbhptres = db->dbenv->open(db->dbenv, argv[2], + bdbFlags | bdbhpt_threads | DB_CREATE, 0); + if (bdbhptres != 0) { + db->log(ISC_LOG_ERROR, + "bdbhpt_dynamic: " + "db environment at '%s' could not be opened. " + "BerkeleyDB error: %s", + argv[2], db_strerror(bdbhptres)); + result = ISC_R_FAILURE; + goto init_cleanup; + } + + /* open dlz_data database. */ + result = bdbhpt_opendb(db->log, db->dbenv, DB_UNKNOWN, &db->data, + dlz_data, argv[3], DB_DUP | DB_DUPSORT); + if (result != ISC_R_SUCCESS) + goto init_cleanup; + + /* open dlz_xfr database. */ + result = bdbhpt_opendb(db->log, db->dbenv, DB_UNKNOWN, &db->xfr, + dlz_xfr, argv[3], DB_DUP | DB_DUPSORT); + if (result != ISC_R_SUCCESS) + goto init_cleanup; + + /* open dlz_zone database. */ + result = bdbhpt_opendb(db->log, db->dbenv, DB_UNKNOWN, &db->zone, + dlz_zone, argv[3], 0); + if (result != ISC_R_SUCCESS) + goto init_cleanup; + + /* open dlz_client database. */ + result = bdbhpt_opendb(db->log, db->dbenv, DB_UNKNOWN, &db->client, + dlz_client, argv[3], DB_DUP | DB_DUPSORT); + if (result != ISC_R_SUCCESS) + goto init_cleanup; + + *dbdata = db; + + db->log(ISC_LOG_INFO, + "bdbhpt_dynamic: version %s, started", + dlz_bdbhpt_dynamic_version); + return(ISC_R_SUCCESS); + + init_cleanup: + bdbhpt_cleanup(db); + return result; +} + +/* + * Shut down the backend + */ +void +dlz_destroy(void *dbdata) { + struct bdbhpt_instance *db = (struct bdbhpt_instance *)dbdata; + + db->log(ISC_LOG_INFO, + "dlz_bdbhpt_dynamic (%s): shutting down", + dlz_bdbhpt_dynamic_version); + bdbhpt_cleanup((bdbhpt_instance_t *) dbdata); + free(db); +} + +/* + * 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..909976a --- /dev/null +++ b/contrib/dlz/modules/bdbhpt/testing/bdbhpt-populate.pl @@ -0,0 +1,232 @@ +#!/usr/bin/perl -w +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 seperated 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-seperated 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..584a1cf --- /dev/null +++ b/contrib/dlz/modules/bdbhpt/testing/named.conf @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +controls { }; + +options { + directory "."; + port 5300; + pid-file "named.pid"; + session-keyfile "session.key"; + listen-on { any; }; + listen-on-v6 { none; }; + recursion no; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm hmac-md5; +}; + +controls { + inet 127.0.0.1 port 9953 allow { any; } keys { rndc_key; }; +}; + +dlz "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..0c742b5 --- /dev/null +++ b/contrib/dlz/modules/common/dlz_dbi.c @@ -0,0 +1,481 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Copyright (C) 1999-2001, 2013, 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 <stdio.h> +#include <string.h> +#include <stdarg.h> +#include <stdlib.h> +#include <ctype.h> + +#include <sys/errno.h> + +#include <dlz_minimal.h> +#include <dlz_list.h> +#include <dlz_dbi.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 == true) + free(tseg->cmd); + /* get the next query segment, before we destroy this one. */ + nseg = DLZ_LIST_NEXT(nseg, link); + /* deallocate this query segment. */ + free(tseg); + } + /* deallocate the query segment list */ + free(*querylist); +} + +/*% constructs a query list by parsing a string into query segments */ +isc_result_t +build_querylist(const char *query_str, char **zone, char **record, + char **client, query_list_t **querylist, unsigned int flags, + log_t log) +{ + isc_result_t result; + bool foundzone = false; + bool foundrecord = false; + bool foundclient = false; + char *temp_str = NULL; + char *right_str = NULL; + query_list_t *tql; + query_segment_t *tseg = NULL; + + /* if query string is null, or zero length */ + if (query_str == NULL || strlen(query_str) < 1) { + if ((flags & REQUIRE_QUERY) == 0) + /* we don't need it were ok. */ + return (ISC_R_SUCCESS); + else + /* we did need it, PROBLEM!!! */ + return (ISC_R_FAILURE); + } + + /* allocate memory for query list */ + tql = calloc(1, sizeof(query_list_t)); + /* couldn't allocate memory. Problem!! */ + if (tql == NULL) + return (ISC_R_NOMEMORY); + + /* initialize the query segment list */ + DLZ_LIST_INIT(*tql); + + /* make a copy of query_str so we can chop it up */ + temp_str = right_str = strdup(query_str); + /* couldn't make a copy, problem!! */ + if (right_str == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + /* loop through the string and chop it up */ + while (right_str != NULL) { + /* allocate memory for tseg */ + tseg = calloc(1, sizeof(query_segment_t)); + if (tseg == NULL) { /* no memory, clean everything up. */ + result = ISC_R_NOMEMORY; + goto cleanup; + } + tseg->cmd = NULL; + tseg->direct = false; + /* initialize the query segment link */ + DLZ_LINK_INIT(tseg, link); + /* append the query segment to the list */ + DLZ_LIST_APPEND(*tql, tseg, link); + + /* + * split string at the first "$". set query segment to + * left portion + */ + tseg->cmd = strdup(strsep(&right_str, "$")); + if (tseg->cmd == NULL) { + /* no memory, clean everything up. */ + result = ISC_R_NOMEMORY; + goto cleanup; + } + /* tseg->cmd points directly to a string. */ + tseg->direct = 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(temp_str); + /* + * add checks later to verify zone and record are found if + * necessary. + */ + + /* if this query requires %client%, make sure we found it */ + if (((flags & REQUIRE_CLIENT) != 0) && (!foundclient) ) { + /* Write error message to log */ + if (log != NULL) + log(ISC_LOG_ERROR, + "Required token $client$ not found."); + result = ISC_R_FAILURE; + goto flag_fail; + } + + /* if this query requires %record%, make sure we found it */ + if (((flags & REQUIRE_RECORD) != 0) && (!foundrecord) ) { + /* Write error message to log */ + if (log != NULL) + log(ISC_LOG_ERROR, + "Required token $record$ not found."); + result = ISC_R_FAILURE; + goto flag_fail; + } + + /* if this query requires %zone%, make sure we found it */ + if (((flags & REQUIRE_ZONE) != 0) && (!foundzone) ) { + /* Write error message to log */ + if (log != NULL) + log(ISC_LOG_ERROR, "Required token $zone$ not found."); + result = ISC_R_FAILURE; + goto flag_fail; + } + + /* pass back the query list */ + *querylist = (query_list_t *) tql; + + /* return success */ + return (ISC_R_SUCCESS); + + cleanup: + /* get rid of temp_str */ + if (temp_str != NULL) + free(temp_str); + + flag_fail: + /* get rid of what was build of the query list */ + if (tql != NULL) + destroy_querylist(&tql); + return (result); +} + +/*% + * build a query string from query segments, and dynamic segments + * dynamic segments replace where the tokens %zone%, %record%, %client% + * used to be in our queries from named.conf + */ +char * +build_querystring(query_list_t *querylist) { + query_segment_t *tseg = NULL; + unsigned int length = 0; + char *qs = NULL; + + /* start at the top of the list */ + tseg = DLZ_LIST_HEAD(*querylist); + while (tseg != NULL) { + /* + * if this is a query segment, use the + * precalculated string length + */ + if (tseg->direct == true) + length += tseg->strlen; + else /* calculate string length for dynamic segments. */ + length += strlen(* (char**) tseg->cmd); + /* get the next segment */ + tseg = DLZ_LIST_NEXT(tseg, link); + } + + 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 == true) + /* query segments */ + strcat(qs, tseg->cmd); + else + /* dynamic segments */ + strcat(qs, * (char**) tseg->cmd); + /* get the next segment */ + tseg = DLZ_LIST_NEXT(tseg, link); + } + + return (qs); +} + +/*% constructs a dbinstance (DBI) */ +isc_result_t +build_dbinstance(const char *allnodes_str, const char *allowxfr_str, + const char *authority_str, const char *findzone_str, + const char *lookup_str, const char *countzone_str, + dbinstance_t **dbi, log_t log) +{ + + isc_result_t result; + dbinstance_t *db = NULL; + int err; + + /* allocate and zero memory for driver structure */ + db = calloc(1, sizeof(dbinstance_t)); + if (db == NULL) { + if (log != NULL) + log(ISC_LOG_ERROR, + "Could not allocate memory for " + "database instance object."); + return (ISC_R_NOMEMORY); + } + memset(db, 0, sizeof(dbinstance_t)); + db->dbconn = NULL; + db->client = NULL; + db->record = NULL; + db->zone = NULL; + db->query_buf = NULL; + db->allnodes_q = NULL; + db->allowxfr_q = NULL; + db->authority_q = NULL; + db->findzone_q = NULL; + db->countzone_q = NULL; + db->lookup_q = NULL; + + /* initialize the reference count mutex */ + err = dlz_mutex_init(&db->lock, NULL); + if (err == ENOMEM) { + result = ISC_R_NOMEMORY; + goto cleanup; + } else if (err != 0) { + result = ISC_R_UNEXPECTED; + goto cleanup; + } + + /* build the all nodes query list */ + result = build_querylist(allnodes_str, &db->zone, &db->record, + &db->client, &db->allnodes_q, + REQUIRE_ZONE, log); + /* if unsuccessful, log err msg and cleanup */ + if (result != ISC_R_SUCCESS) { + if (log != NULL) + log(ISC_LOG_ERROR, + "Could not build all nodes query list"); + goto cleanup; + } + + /* build the allow zone transfer query list */ + result = build_querylist(allowxfr_str, &db->zone, &db->record, + &db->client, &db->allowxfr_q, + REQUIRE_ZONE | REQUIRE_CLIENT, + log); + /* if unsuccessful, log err msg and cleanup */ + if (result != ISC_R_SUCCESS) { + if (log != NULL) + log(ISC_LOG_ERROR, + "Could not build allow xfr query list"); + goto cleanup; + } + + /* build the authority query, query list */ + result = build_querylist(authority_str, &db->zone, &db->record, + &db->client, &db->authority_q, + REQUIRE_ZONE, log); + /* if unsuccessful, log err msg and cleanup */ + if (result != ISC_R_SUCCESS) { + if (log != NULL) + log(ISC_LOG_ERROR, + "Could not build authority query list"); + goto cleanup; + } + + /* build findzone query, query list */ + result = build_querylist(findzone_str, &db->zone, &db->record, + &db->client, &db->findzone_q, + REQUIRE_ZONE, log); + /* if unsuccessful, log err msg and cleanup */ + if (result != ISC_R_SUCCESS) { + if (log != NULL) + log(ISC_LOG_ERROR, + "Could not build find zone query list"); + goto cleanup; + } + + /* build countzone query, query list */ + result = build_querylist(countzone_str, &db->zone, &db->record, + &db->client, &db->countzone_q, + REQUIRE_ZONE, log); + /* if unsuccessful, log err msg and cleanup */ + if (result != ISC_R_SUCCESS) { + if (log != NULL) + log(ISC_LOG_ERROR, + "Could not build count zone query list"); + goto cleanup; + } + + /* build lookup query, query list */ + result = build_querylist(lookup_str, &db->zone, &db->record, + &db->client, &db->lookup_q, + REQUIRE_RECORD, log); + /* if unsuccessful, log err msg and cleanup */ + if (result != ISC_R_SUCCESS) { + if (log != NULL) + log(ISC_LOG_ERROR, + "Could not build lookup query list"); + goto cleanup; + } + + /* pass back the db instance */ + *dbi = (dbinstance_t *) db; + + /* return success */ + return (ISC_R_SUCCESS); + + cleanup: + /* destroy whatever was build of the db instance */ + destroy_dbinstance(db); + /* return failure */ + return (ISC_R_FAILURE); +} + +void +destroy_dbinstance(dbinstance_t *dbi) { + /* destroy any query lists we created */ + destroy_querylist(&dbi->allnodes_q); + destroy_querylist(&dbi->allowxfr_q); + destroy_querylist(&dbi->authority_q); + destroy_querylist(&dbi->findzone_q); + destroy_querylist(&dbi->countzone_q); + destroy_querylist(&dbi->lookup_q); + + /* get rid of the mutex */ + (void) dlz_mutex_destroy(&dbi->lock); + + /* return, and detach the memory */ + free(dbi); +} + +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..fd87ee8 --- /dev/null +++ b/contrib/dlz/modules/filesystem/Makefile @@ -0,0 +1,20 @@ +prefix = /usr +libdir = $(prefix)/lib/bind9 + +CFLAGS=-fPIC -g -I../include + +all: dlz_filesystem_dynamic.so + +dir.o: dir.c + $(CC) $(CFLAGS) -c dir.c + +dlz_filesystem_dynamic.so: dlz_filesystem_dynamic.c dir.o + $(CC) $(CFLAGS) -shared -o dlz_filesystem_dynamic.so \ + dlz_filesystem_dynamic.c dir.o + +clean: + rm -f dlz_filesystem_dynamic.so *.o + +install: dlz_filesystem_dynamic.so + mkdir -p $(DESTDIR)$(libdir) + install dlz_filesystem_dynamic.so $(DESTDIR)$(libdir) diff --git a/contrib/dlz/modules/filesystem/dir.c b/contrib/dlz/modules/filesystem/dir.c new file mode 100644 index 0000000..e5f1ac4 --- /dev/null +++ b/contrib/dlz/modules/filesystem/dir.c @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <sys/stat.h> + +#include <ctype.h> +#include <errno.h> +#include <unistd.h> +#include <string.h> + +#include "dlz_minimal.h" +#include "dir.h" + +void +dir_init(dir_t *dir) { + dir->entry.name[0] = '\0'; + dir->entry.length = 0; + + dir->handle = NULL; +} + +isc_result_t +dir_open(dir_t *dir, const char *dirname) { + char *p; + isc_result_t result = ISC_R_SUCCESS; + + if (strlen(dirname) + 3 > sizeof(dir->dirname)) + return (ISC_R_NOSPACE); + strcpy(dir->dirname, dirname); + + p = dir->dirname + strlen(dir->dirname); + if (dir->dirname < p && *(p - 1) != '/') + *p++ = '/'; + *p++ = '*'; + *p = '\0'; + + dir->handle = opendir(dirname); + if (dir->handle == NULL) { + switch (errno) { + case ENOTDIR: + case ELOOP: + case EINVAL: + case ENAMETOOLONG: + case EBADF: + result = ISC_R_INVALIDFILE; + case ENOENT: + result = ISC_R_FILENOTFOUND; + case EACCES: + case EPERM: + result = ISC_R_NOPERM; + case ENOMEM: + result = ISC_R_NOMEMORY; + default: + result = ISC_R_UNEXPECTED; + } + } + + return (result); +} + +/*! + * \brief Return previously retrieved file or get next one. + + * Unix's dirent has + * separate open and read functions, but the Win32 and DOS interfaces open + * the dir stream and reads the first file in one operation. + */ +isc_result_t +dir_read(dir_t *dir) { + struct dirent *entry; + + entry = readdir(dir->handle); + if (entry == NULL) + return (ISC_R_NOMORE); + + if (sizeof(dir->entry.name) <= strlen(entry->d_name)) + return (ISC_R_UNEXPECTED); + + strcpy(dir->entry.name, entry->d_name); + + dir->entry.length = strlen(entry->d_name); + return (ISC_R_SUCCESS); +} + +/*! + * \brief Close directory stream. + */ +void +dir_close(dir_t *dir) { + (void)closedir(dir->handle); + dir->handle = NULL; +} + +/*! + * \brief Reposition directory stream at start. + */ +isc_result_t +dir_reset(dir_t *dir) { + rewinddir(dir->handle); + + return (ISC_R_SUCCESS); +} diff --git a/contrib/dlz/modules/filesystem/dir.h b/contrib/dlz/modules/filesystem/dir.h new file mode 100644 index 0000000..66041fa --- /dev/null +++ b/contrib/dlz/modules/filesystem/dir.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#include <sys/types.h> +#include <dirent.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..d164f53 --- /dev/null +++ b/contrib/dlz/modules/filesystem/dlz_filesystem_dynamic.c @@ -0,0 +1,975 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Copyright (C) 1999-2001, 2013, 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/. + */ + +/* + * This provides the externally loadable filesystem DLZ module, without + * update support + */ + +#include <stdio.h> +#include <string.h> +#include <stdarg.h> +#include <stdlib.h> + +#include <sys/stat.h> + +#include "dlz_minimal.h" +#include "dlz_list.h" +#include "dir.h" + +typedef struct config_data { + char *basedir; + int basedirsize; + char *datadir; + int datadirsize; + char *xfrdir; + int xfrdirsize; + int splitcnt; + char separator; + char pathsep; + + /* Helper functions from the dlz_dlopen driver */ + log_t *log; + dns_sdlz_putrr_t *putrr; + dns_sdlz_putnamedrr_t *putnamedrr; + dns_dlz_writeablezone_t *writeable_zone; +} config_data_t; + +typedef struct dir_entry dir_entry_t; + +struct dir_entry { + char dirpath[DIR_PATHMAX]; + DLZ_LINK(dir_entry_t) link; +}; + +typedef DLZ_LIST(dir_entry_t) dlist_t; + +/* forward reference */ + +static void +b9_add_helper(struct config_data *cd, const char *helper_name, void *ptr); + +/* + * Private methods + */ +static 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. */ + else if (input[i-1] == '.') + return (false); + /* '.' is not allowed as last char */ + if (i == len) + 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 == false) { + continue; + } + } else /* if we cannot stat entry, skip it. */ + continue; + + type = dir->entry.name; + ttlStr = strchr(type, cd->separator); + if (ttlStr == NULL) { + cd->log(ISC_LOG_ERROR, + "Filesystem driver: " + "%s could not be parsed properly", tmp); + return (ISC_R_FAILURE); + } + + /* replace separator char with NULL to split string */ + ttlStr[0] = '\0'; + /* start string after NULL of previous string */ + ttlStr = (char *) &ttlStr[1]; + + data = strchr(ttlStr, cd->separator); + if (data == NULL) { + cd->log(ISC_LOG_ERROR, + "Filesystem driver: " + "%s could not be parsed properly", tmp); + return (ISC_R_FAILURE); + } + + /* replace separator char with NULL to split string */ + data[0] = '\0'; + + /* start string after NULL of previous string */ + data = (char *) &data[1]; + + /* replace all cd->separator chars with a space. */ + len = strlen(data); + + for (i=0; i < len; i++) { + if (data[i] == cd->separator) + data[i] = ' '; + } + + /* convert text to int, make sure it worked right */ + ttl = strtol(ttlStr, &endp, 10); + if (*endp != '\0' || ttl < 0) + cd->log(ISC_LOG_ERROR, + "Filesystem driver " + "ttl must be a postive number"); + + /* pass data back to Bind */ + if (dir_list == NULL) + result = cd->putrr((dns_sdlzlookup_t *) passback, + type, ttl, data); + else + result = cd->putnamedrr((dns_sdlzallnodes_t *) passback, + (char *) host, + type, ttl, data); + + /* if error, return error right away */ + if (result != ISC_R_SUCCESS) + return (result); + } /* end of while loop */ + + return (result); +} + +/* + * DLZ methods + */ +isc_result_t +dlz_allowzonexfr(void *dbdata, const char *name, const char *client) { + isc_result_t result; + char *path; + struct stat sb; + config_data_t *cd; + path = NULL; + + cd = (config_data_t *) dbdata; + + if (create_path(name, NULL, client, cd, &path) != ISC_R_SUCCESS) { + return (ISC_R_NOTFOUND); + } + + if (stat(path, &sb) != 0) { + result = ISC_R_NOTFOUND; + goto complete_AXFR; + } + + if ((sb.st_mode & S_IFREG) != 0) { + result = ISC_R_SUCCESS; + goto complete_AXFR; + } + + result = ISC_R_NOTFOUND; + + complete_AXFR: + free(path); + return (result); +} + +isc_result_t +dlz_allnodes(const char *zone, void *dbdata, dns_sdlzallnodes_t *allnodes) { + isc_result_t result; + dlist_t *dir_list; + config_data_t *cd = (config_data_t *) dbdata; + char *basepath; + unsigned int basepathlen; + struct stat sb; + dir_t dir; + dir_entry_t *dir_entry; + dir_entry_t *next_de; + + basepath = NULL; + dir_list = NULL; + + /* allocate memory for list */ + dir_list = malloc(sizeof(dlist_t)); + if (dir_list == NULL) { + result = ISC_R_NOTFOUND; + goto complete_allnds; + } + + /* initialize list */ + DLZ_LIST_INIT(*dir_list); + + if (create_path(zone, NULL, NULL, cd, &basepath) != ISC_R_SUCCESS) { + 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 +isc_result_t +dlz_findzonedb(void *dbdata, const char *name, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) +#endif +{ + + isc_result_t result; + config_data_t *cd = (config_data_t *) dbdata; + char *path; + struct stat sb; + path = NULL; + +#if DLZ_DLOPEN_VERSION >= 3 + UNUSED(methods); + UNUSED(clientinfo); +#endif + + if (create_path(name, NULL, NULL, cd, &path) != ISC_R_SUCCESS) + return (ISC_R_NOTFOUND); + + cd->log(ISC_LOG_DEBUG(1), + "Filesystem driver Findzone() Checking for path: '%s'\n", path); + + if (stat(path, &sb) != 0) { + result = ISC_R_NOTFOUND; + goto complete_FZ; + } + + if ((sb.st_mode & S_IFDIR) != 0) { + result = ISC_R_SUCCESS; + goto complete_FZ; + } + + result = ISC_R_NOTFOUND; + + complete_FZ: + + free(path); + return (result); +} + +#if DLZ_DLOPEN_VERSION == 1 +isc_result_t +dlz_lookup(const char *zone, const char *name, + void *dbdata, dns_sdlzlookup_t *lookup) +#else +isc_result_t +dlz_lookup(const char *zone, const char *name, + void *dbdata, dns_sdlzlookup_t *lookup, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) +#endif +{ + isc_result_t result = ISC_R_NOTFOUND; + config_data_t *cd = (config_data_t *) dbdata; + char *path; + struct stat sb; + dir_t dir; + path = NULL; + + UNUSED(lookup); +#if DLZ_DLOPEN_VERSION >= 2 + UNUSED(methods); + UNUSED(clientinfo); +#endif + + if (strcmp(name, "*") == 0) + /* + * handle filesystem's special wildcard "-" + */ + result = create_path(zone, "-", NULL, cd, &path); + else + result = create_path(zone, name, NULL, cd, &path); + + if (result != ISC_R_SUCCESS) + return (ISC_R_NOTFOUND); + + /* remove path separator at end of path so stat works properly */ + path[strlen(path)-1] = '\0'; + + cd->log(ISC_LOG_DEBUG(1), + "Filesystem driver lookup() Checking for path: '%s'\n", path); + + if (stat(path, &sb) != 0) { + result = ISC_R_NOTFOUND; + goto complete_lkup; + } + + if ((sb.st_mode & S_IFDIR) == 0) { + result = ISC_R_NOTFOUND; + goto complete_lkup; + } + + /* initialize and open directory */ + dir_init(&dir); + result = dir_open(&dir, path); + + /* if directory open failed, return error. */ + if (result != ISC_R_SUCCESS) { + cd->log(ISC_LOG_ERROR, + "Unable to open %s directory to read entries.", path); + result = ISC_R_FAILURE; + goto complete_lkup; + } + + /* process any records in the directory */ + result = process_dir(&dir, lookup, cd, NULL, 0); + + /* close the directory */ + dir_close(&dir); + + complete_lkup: + + free(path); + return (result); +} + +isc_result_t +dlz_create(const char *dlzname, unsigned int argc, char *argv[], + void **dbdata, ...) +{ + 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 postive number"); + + /* get and store our separator character */ + cd->separator = *argv[5]; + + /* pass back config data */ + *dbdata = cd; + + /* return success */ + return (ISC_R_SUCCESS); + + /* handle no memory error */ + no_mem: + + /* write error message */ + if (cd != NULL && cd->log != NULL) + cd->log(ISC_LOG_ERROR, + "filesystem_dynamic: Filesystem driver unable to " + "allocate memory for config data."); + + 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/dlz_dbi.h b/contrib/dlz/modules/include/dlz_dbi.h new file mode 100644 index 0000000..10e274a --- /dev/null +++ b/contrib/dlz/modules/include/dlz_dbi.h @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <dlz_minimal.h> +#include <dlz_list.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..d236105 --- /dev/null +++ b/contrib/dlz/modules/include/dlz_list.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 1997-2002, 2004, 2006, 2007, 2011-2013, 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/. + */ + +#ifndef DLZ_LIST_H +#define DLZ_LIST_H 1 + +#define DLZ_LIST(type) struct { type *head, *tail; } +#define DLZ_LIST_INIT(list) \ + do { (list).head = NULL; (list).tail = NULL; } while (0) + +#define DLZ_LINK(type) struct { type *prev, *next; } +#define DLZ_LINK_INIT(elt, link) \ + do { \ + (elt)->link.prev = (void *)(-1); \ + (elt)->link.next = (void *)(-1); \ + } while (0) + +#define DLZ_LIST_HEAD(list) ((list).head) +#define DLZ_LIST_TAIL(list) ((list).tail) + +#define DLZ_LIST_APPEND(list, elt, link) \ + do { \ + if ((list).tail != NULL) \ + (list).tail->link.next = (elt); \ + else \ + (list).head = (elt); \ + (elt)->link.prev = (list).tail; \ + (elt)->link.next = NULL; \ + (list).tail = (elt); \ + } while (0) + +#define DLZ_LIST_PREV(elt, link) ((elt)->link.prev) +#define DLZ_LIST_NEXT(elt, link) ((elt)->link.next) + +#endif /* DLZ_LIST_H */ diff --git a/contrib/dlz/modules/include/dlz_minimal.h b/contrib/dlz/modules/include/dlz_minimal.h new file mode 100644 index 0000000..7bd6775 --- /dev/null +++ b/contrib/dlz/modules/include/dlz_minimal.h @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2011 Internet Systems Consortium, Inc. ("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 <sys/types.h> +#include <sys/socket.h> +#ifdef ISC_PLATFORM_HAVESYSUNH +#include <sys/un.h> +#endif +#include <net/if.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +typedef unsigned int isc_result_t; +typedef int bool; +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 + +/* 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 + +/* boolean values */ +#define true 1 +#define false 0 + +/* 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) + +/* 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 + } type; + unsigned int length; + void * link; +} isc_sockaddr_t; + +#define DNS_CLIENTINFO_VERSION 2 +typedef struct dns_clientinfo { + uint16_t version; + void *data; + void *dbversion; +} 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_clientinfo_version_t dbversion; +} dns_clientinfomethods_t; +#endif /* DLZ_DLOPEN_VERSION > 1 */ + +/* + * 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..90d143e --- /dev/null +++ b/contrib/dlz/modules/include/dlz_pthread.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef DLZ_PTHREAD_H +#define DLZ_PTHREAD_H 1 + +#ifndef PTHREADS +#define PTHREADS 1 +#endif + +#ifdef PTHREADS +#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 + +#endif /* DLZ_PTHREAD_H */ diff --git a/contrib/dlz/modules/ldap/Makefile b/contrib/dlz/modules/ldap/Makefile new file mode 100644 index 0000000..8926abf --- /dev/null +++ b/contrib/dlz/modules/ldap/Makefile @@ -0,0 +1,21 @@ +prefix = /usr +libdir = $(prefix)/lib/bind9 + +CFLAGS=-fPIC -g -I../include +LDAP_LIBS=-lldap + +all: dlz_ldap_dynamic.so + +dlz_dbi.o: ../common/dlz_dbi.c + $(CC) $(CFLAGS) -c ../common/dlz_dbi.c + +dlz_ldap_dynamic.so: dlz_ldap_dynamic.c dlz_dbi.o + $(CC) $(CFLAGS) -shared -o dlz_ldap_dynamic.so \ + dlz_ldap_dynamic.c dlz_dbi.o $(LDAP_LIBS) + +clean: + rm -f dlz_ldap_dynamic.so *.o + +install: dlz_ldap_dynamic.so + mkdir -p $(DESTDIR)$(libdir) + install dlz_ldap_dynamic.so $(DESTDIR)$(libdir) diff --git a/contrib/dlz/modules/ldap/dlz_ldap_dynamic.c b/contrib/dlz/modules/ldap/dlz_ldap_dynamic.c new file mode 100644 index 0000000..a2299bf --- /dev/null +++ b/contrib/dlz/modules/ldap/dlz_ldap_dynamic.c @@ -0,0 +1,1218 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + * + * The development of Dynamically Loadable Zones (DLZ) for BIND 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Copyright (C) 1999-2001, 2013, 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/. + */ + +/* + * This provides the externally loadable ldap DLZ module, without + * update support + */ + +#include <stdio.h> +#include <string.h> +#include <stdarg.h> +#include <stdlib.h> + +#include <dlz_minimal.h> +#include <dlz_list.h> +#include <dlz_dbi.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 everthing needed by this "instance" of the LDAP + * driver remember, the driver code is only loaded once, but may have + * many separate instances. + */ +typedef struct { +#if PTHREADS + db_list_t *db; /*%< handle to a list of DB */ +#else + dbinstance_t *db; /*%< handle to db */ +#endif + int method; /*%< security authentication method */ + char *user; /*%< who is authenticating */ + char *cred; /*%< password for simple authentication method */ + int protocol; /*%< LDAP communication protocol version */ + char *hosts; /*%< LDAP server hosts */ + + /* Helper functions from the dlz_dlopen driver */ + log_t *log; + dns_sdlz_putrr_t *putrr; + dns_sdlz_putnamedrr_t *putnamedrr; + dns_dlz_writeablezone_t *writeable_zone; +} ldap_instance_t; + +/* forward references */ + +#if DLZ_DLOPEN_VERSION < 3 +isc_result_t +dlz_findzonedb(void *dbdata, const char *name); +#else +isc_result_t +dlz_findzonedb(void *dbdata, const char *name, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo); +#endif + +void +dlz_destroy(void *dbdata); + +static void +b9_add_helper(ldap_instance_t *db, const char *helper_name, void *ptr); + +/* + * Private methods + */ + +/*% checks that the LDAP URL parameters make sense */ +static isc_result_t +ldap_checkURL(ldap_instance_t *db, char *URL, int attrCnt, const char *msg) { + isc_result_t result = ISC_R_SUCCESS; + int ldap_result; + LDAPURLDesc *ldap_url = NULL; + + if (!ldap_is_ldap_url(URL)) { + db->log(ISC_LOG_ERROR, + "%s query is not a valid LDAP URL", msg); + result = ISC_R_FAILURE; + goto cleanup; + } + + ldap_result = ldap_url_parse(URL, &ldap_url); + if (ldap_result != LDAP_SUCCESS || ldap_url == NULL) { + db->log(ISC_LOG_ERROR, "parsing %s query failed", msg); + result = ISC_R_FAILURE; + goto cleanup; + } + + if (ldap_count_values(ldap_url->lud_attrs) < attrCnt) { + db->log(ISC_LOG_ERROR, + "%s query must specify at least " + "%d attributes to return", msg, attrCnt); + result = ISC_R_FAILURE; + goto cleanup; + } + + if (ldap_url->lud_host != NULL) { + db->log(ISC_LOG_ERROR, + "%s query must not specify a host", msg); + result = ISC_R_FAILURE; + goto cleanup; + } + + if (ldap_url->lud_port != 389) { + db->log(ISC_LOG_ERROR, + "%s query must not specify a port", msg); + result = ISC_R_FAILURE; + goto cleanup; + } + + if (ldap_url->lud_dn == NULL || strlen (ldap_url->lud_dn) < 1) { + db->log(ISC_LOG_ERROR, + "%s query must specify a search base", msg); + result = ISC_R_FAILURE; + goto cleanup; + } + + if (ldap_url->lud_exts != NULL || ldap_url->lud_crit_exts != 0) { + db->log(ISC_LOG_ERROR, + "%s uses extensions. " + "The driver does not support LDAP extensions.", msg); + result = ISC_R_FAILURE; + goto cleanup; + } + + cleanup: + if (ldap_url != NULL) + ldap_free_urldesc(ldap_url); + + return (result); +} + +/*% Connects / reconnects to LDAP server */ +static isc_result_t +ldap_connect(ldap_instance_t *dbi, dbinstance_t *dbc) { + isc_result_t result; + int ldap_result; + + /* if we have a connection, get ride of it. */ + if (dbc->dbconn != NULL) { + ldap_unbind_s((LDAP *) dbc->dbconn); + dbc->dbconn = NULL; + } + + /* now connect / reconnect. */ + + /* initialize. */ + dbc->dbconn = ldap_init(dbi->hosts, LDAP_PORT); + if (dbc->dbconn == NULL) + return (ISC_R_NOMEMORY); + + /* set protocol version. */ + ldap_result = ldap_set_option((LDAP *) dbc->dbconn, + LDAP_OPT_PROTOCOL_VERSION, + &(dbi->protocol)); + if (ldap_result != LDAP_SUCCESS) { + result = ISC_R_NOPERM; + goto cleanup; + } + + /* "bind" to server. i.e. send username / pass */ + ldap_result = ldap_bind_s((LDAP *) dbc->dbconn, dbi->user, + dbi->cred, dbi->method); + if (ldap_result != LDAP_SUCCESS) { + result = ISC_R_FAILURE; + goto cleanup; + } + + return (ISC_R_SUCCESS); + + cleanup: + + /* cleanup if failure. */ + if (dbc->dbconn != NULL) { + ldap_unbind_s((LDAP *) dbc->dbconn); + dbc->dbconn = NULL; + } + + return (result); +} + +#if PTHREADS +/*% + * Properly cleans up a list of database instances. + * This function is only used when the driver is compiled for + * multithreaded operation. + */ +static void +ldap_destroy_dblist(db_list_t *dblist) { + dbinstance_t *ndbi = NULL; + dbinstance_t *dbi = NULL; + + /* get the first DBI in the list */ + ndbi = DLZ_LIST_HEAD(*dblist); + + /* loop through the list */ + while (ndbi != NULL) { + dbi = ndbi; + /* get the next DBI in the list */ + ndbi = DLZ_LIST_NEXT(dbi, link); + /* release DB connection */ + if (dbi->dbconn != NULL) + ldap_unbind_s((LDAP *) dbi->dbconn); + /* release all memory that comprised a DBI */ + destroy_dbinstance(dbi); + } + /* release memory for the list structure */ + free(dblist); +} + +/*% + * Loops through the list of DB instances, attempting to lock + * on the mutex. If successful, the DBI is reserved for use + * and the thread can perform queries against the database. + * If the lock fails, the next one in the list is tried. + * looping continues until a lock is obtained, or until + * the list has been searched dbc_search_limit times. + * This function is only used when the driver is compiled for + * multithreaded operation. + */ +static dbinstance_t * +ldap_find_avail_conn(ldap_instance_t *ldap) { + dbinstance_t *dbi = NULL; + dbinstance_t *head; + int count = 0; + + /* get top of list */ + head = dbi = DLZ_LIST_HEAD(*ldap->db); + + /* loop through list */ + while (count < dbc_search_limit) { + /* try to lock on the mutex */ + if (dlz_mutex_trylock(&dbi->lock) == 0) + return (dbi); /* success, return the DBI for use. */ + + /* not successful, keep trying */ + dbi = DLZ_LIST_NEXT(dbi, link); + + /* check to see if we have gone to the top of the list. */ + if (dbi == NULL) { + count++; + dbi = head; + } + } + + ldap->log(ISC_LOG_INFO, + "LDAP driver unable to find available connection " + "after searching %d times", count); + return (NULL); +} +#endif /* PTHREADS */ + +static isc_result_t +ldap_process_results(ldap_instance_t *db, LDAP *dbc, LDAPMessage *msg, + char **attrs, void *ptr, 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 attibute pointer */ + attribute = attrs[++i]; + /* start loop over */ + continue; + } + + /* + * j initially = 0. Increment j each time we + * set a field that way next loop will set + * next field. + */ + switch (j) { + case 0: + j++; + /* + * convert text to int, make sure it + * worked right + */ + ttl = strtol(vals[0], &endp, 10); + if (*endp != '\0' || ttl < 0) { + db->log(ISC_LOG_ERROR, + "LDAP driver ttl must " + "be a postive number"); + goto cleanup; + } + break; + case 1: + j++; + type = strdup(vals[0]); + break; + case 2: + j++; + if (allnodes) + host = strdup(vals[0]); + else + strcpy(data, vals[0]); + break; + case 3: + j++; + if (allnodes) + strcpy(data, vals[0]); + else { + strcat(data, " "); + strcat(data, vals[0]); + } + break; + default: + strcat(data, " "); + strcat(data, vals[0]); + break; + } + + /* free values */ + ldap_value_free(vals); + vals = NULL; + + /* increment attibute pointer */ + attribute = attrs[++i]; + } + + if (type == NULL) { + db->log(ISC_LOG_ERROR, + "LDAP driver unable to retrieve DNS type"); + result = ISC_R_FAILURE; + goto cleanup; + } + + if (strlen(data) < 1) { + db->log(ISC_LOG_ERROR, + "LDAP driver unable to retrieve DNS data"); + result = ISC_R_FAILURE; + goto cleanup; + } + + if (allnodes && host != NULL) { + dns_sdlzallnodes_t *an = (dns_sdlzallnodes_t *) ptr; + if (strcasecmp(host, "~") == 0) + result = db->putnamedrr(an, "*", type, + ttl, data); + else + result = db->putnamedrr(an, host, type, + ttl, data); + if (result != ISC_R_SUCCESS) + db->log(ISC_LOG_ERROR, + "ldap_dynamic: putnamedrr failed " + "for \"%s %s %u %s\" (%d)", + host, type, ttl, data, result); + } else { + dns_sdlzlookup_t *lookup = (dns_sdlzlookup_t *) ptr; + result = db->putrr(lookup, type, ttl, data); + if (result != ISC_R_SUCCESS) + db->log(ISC_LOG_ERROR, + "ldap_dynamic: putrr failed " + "for \"%s %u %s\" (%s)", + type, ttl, data, result); + } + + if (result != ISC_R_SUCCESS) { + db->log(ISC_LOG_ERROR, + "LDAP driver failed " + "while sending data to BIND."); + goto cleanup; + } + + /* free memory for type, data and host for next loop */ + free(type); + type = NULL; + + free(data); + data = NULL; + + if (host != NULL) { + free(host); + host = NULL; + } + + /* get the next entry to process */ + entry = ldap_next_entry(dbc, entry); + } + + cleanup: + /* de-allocate memory */ + if (vals != NULL) + ldap_value_free(vals); + if (host != NULL) + free(host); + if (type != NULL) + free(type); + if (data != NULL) + free(data); + + return (result); +} + +/*% + * This function is the real core of the driver. Zone, record + * and client strings are passed in (or NULL is passed if the + * string is not available). The type of query we want to run + * is indicated by the query flag, and the dbdata object is passed + * passed in to. dbdata really holds either: + * 1) a list of database instances (in multithreaded mode) OR + * 2) a single database instance (in single threaded mode) + * The function will construct the query and obtain an available + * database instance (DBI). It will then run the query and hopefully + * obtain a result set. + */ +static isc_result_t +ldap_get_results(const char *zone, const char *record, + const char *client, unsigned int query, + void *dbdata, void *ptr) +{ + isc_result_t result; + ldap_instance_t *db = (ldap_instance_t *)dbdata; + dbinstance_t *dbi = NULL; + char *querystring = NULL; + LDAPURLDesc *ldap_url = NULL; + int ldap_result = 0; + LDAPMessage *ldap_msg = NULL; + int i; + int entries; + + /* get db instance / connection */ +#if PTHREADS + /* find an available DBI from the list */ + dbi = ldap_find_avail_conn(db); +#else /* PTHREADS */ + /* + * only 1 DBI - no need to lock instance lock either + * only 1 thread in the whole process, no possible contention. + */ + dbi = (dbinstance_t *)(db->db); +#endif /* PTHREADS */ + + /* if DBI is null, can't do anything else */ + if (dbi == NULL) + return (ISC_R_FAILURE); + + /* set fields */ + if (zone != NULL) { + dbi->zone = strdup(zone); + if (dbi->zone == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + } else + dbi->zone = NULL; + + if (record != NULL) { + dbi->record = strdup(record); + if (dbi->record == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + } else + dbi->record = NULL; + + if (client != NULL) { + dbi->client = strdup(client); + if (dbi->client == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + } else + dbi->client = NULL; + + + /* what type of query are we going to run? */ + switch (query) { + case ALLNODES: + /* + * if the query was not passed in from the config file + * then we can't run it. return not_implemented, so + * it's like the code for that operation was never + * built into the driver.... AHHH flexibility!!! + */ + if (dbi->allnodes_q == NULL) { + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + } else + querystring = build_querystring(dbi->allnodes_q); + break; + case ALLOWXFR: + /* same as comments as ALLNODES */ + if (dbi->allowxfr_q == NULL) { + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + } else + querystring = build_querystring(dbi->allowxfr_q); + break; + case AUTHORITY: + /* same as comments as ALLNODES */ + if (dbi->authority_q == NULL) { + result = ISC_R_NOTIMPLEMENTED; + goto cleanup; + } else + querystring = build_querystring(dbi->authority_q); + break; + case FINDZONE: + /* this is required. It's the whole point of DLZ! */ + if (dbi->findzone_q == NULL) { + db->log(ISC_LOG_DEBUG(2), + "No query specified for findzone. " + "Findzone requires a query"); + result = ISC_R_FAILURE; + goto cleanup; + } else + querystring = build_querystring(dbi->findzone_q); + break; + case LOOKUP: + /* this is required. It's also a major point of DLZ! */ + if (dbi->lookup_q == NULL) { + db->log(ISC_LOG_DEBUG(2), + "No query specified for lookup. " + "Lookup requires a query"); + result = ISC_R_FAILURE; + goto cleanup; + } else + querystring = build_querystring(dbi->lookup_q); + break; + default: + /* + * this should never happen. If it does, the code is + * screwed up! + */ + db->log(ISC_LOG_ERROR, + "Incorrect query flag passed to ldap_get_results"); + result = ISC_R_UNEXPECTED; + goto cleanup; + } + + /* if the querystring is null, Bummer, outta RAM. UPGRADE TIME!!! */ + if (querystring == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + /* + * output the full query string during debug so we can see + * what lame error the query has. + */ + db->log(ISC_LOG_DEBUG(1), "Query String: %s", querystring); + + /* break URL down into it's component parts, if error cleanup */ + ldap_result = ldap_url_parse(querystring, &ldap_url); + if (ldap_result != LDAP_SUCCESS || ldap_url == NULL) { + result = ISC_R_FAILURE; + goto cleanup; + } + + for (i = 0; i < 3; i++) { + /* + * dbi->dbconn may be null if trying to reconnect on a + * previous query failed. + */ + if (dbi->dbconn == NULL) { + db->log(ISC_LOG_INFO, + "LDAP driver attempting to re-connect"); + + result = ldap_connect((ldap_instance_t *) dbdata, dbi); + if (result != ISC_R_SUCCESS) { + result = ISC_R_FAILURE; + continue; + } + } + + /* perform ldap search syncronously */ + ldap_result = ldap_search_s((LDAP *) dbi->dbconn, + ldap_url->lud_dn, + ldap_url->lud_scope, + ldap_url->lud_filter, + ldap_url->lud_attrs, 0, &ldap_msg); + + /* + * check return code. No such object is ok, just + * didn't find what we wanted + */ + switch (ldap_result) { + case LDAP_NO_SUCH_OBJECT: + db->log(ISC_LOG_DEBUG(1), + "No object found matching query requirements"); + result = ISC_R_NOTFOUND; + goto cleanup; + break; + case LDAP_SUCCESS: /* on success do nothing */ + result = ISC_R_SUCCESS; + i = 3; + break; + case LDAP_SERVER_DOWN: + db->log(ISC_LOG_INFO, + "LDAP driver attempting to re-connect"); + result = ldap_connect((ldap_instance_t *) dbdata, dbi); + if (result != ISC_R_SUCCESS) + result = ISC_R_FAILURE; + break; + default: + /* + * other errors not ok. Log error message and + * get out + */ + db->log(ISC_LOG_ERROR, "LDAP error: %s", + ldap_err2string(ldap_result)); + result = ISC_R_FAILURE; + goto cleanup; + break; + } + } + + if (result != ISC_R_SUCCESS) + goto cleanup; + + switch (query) { + case ALLNODES: + result = ldap_process_results(db, (LDAP *) dbi->dbconn, + ldap_msg, ldap_url->lud_attrs, + ptr, true); + break; + case AUTHORITY: + case LOOKUP: + result = 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 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 + result = dlz_findzonedb(dbdata, name, NULL, NULL); +#endif + if (result != ISC_R_SUCCESS) { + return (result); + } + + /* get all the zone data */ + result = ldap_get_results(name, NULL, client, ALLOWXFR, dbdata, NULL); + return (result); +} + +isc_result_t +dlz_allnodes(const char *zone, void *dbdata, dns_sdlzallnodes_t *allnodes) +{ + return (ldap_get_results(zone, NULL, NULL, ALLNODES, dbdata, allnodes)); +} + +isc_result_t +dlz_authority(const char *zone, void *dbdata, dns_sdlzlookup_t *lookup) { + return (ldap_get_results(zone, NULL, NULL, AUTHORITY, dbdata, lookup)); +} + +#if DLZ_DLOPEN_VERSION < 3 +isc_result_t +dlz_findzonedb(void *dbdata, const char *name) +#else +isc_result_t +dlz_findzonedb(void *dbdata, const char *name, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) +#endif +{ +#if DLZ_DLOPEN_VERSION >= 3 + UNUSED(methods); + UNUSED(clientinfo); +#endif + return (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 +isc_result_t dlz_lookup(const char *zone, const char *name, + void *dbdata, dns_sdlzlookup_t *lookup, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) +#endif +{ + isc_result_t result; + +#if DLZ_DLOPEN_VERSION >= 2 + UNUSED(methods); + UNUSED(clientinfo); +#endif + + if (strcmp(name, "*") == 0) + result = ldap_get_results(zone, "~", NULL, LOOKUP, + dbdata, lookup); + else + result = ldap_get_results(zone, name, NULL, LOOKUP, + dbdata, lookup); + return (result); +} + + +isc_result_t +dlz_create(const char *dlzname, unsigned int argc, char *argv[], + void **dbdata, ...) +{ + isc_result_t result = ISC_R_FAILURE; + ldap_instance_t *ldap = NULL; + dbinstance_t *dbi = NULL; + const char *helper_name; + int protocol; + int method; +#if PTHREADS + int dbcount; + char *endp; + int i; +#endif /* PTHREADS */ + va_list ap; + + UNUSED(dlzname); + + /* allocate memory for LDAP instance */ + ldap = calloc(1, sizeof(ldap_instance_t)); + if (ldap == NULL) + return (ISC_R_NOMEMORY); + memset(ldap, 0, sizeof(ldap_instance_t)); + + /* Fill in the helper functions */ + va_start(ap, dbdata); + while ((helper_name = va_arg(ap, const char*)) != NULL) + b9_add_helper(ldap, helper_name, va_arg(ap, void*)); + va_end(ap); + +#if PTHREADS + /* if debugging, let user know we are multithreaded. */ + ldap->log(ISC_LOG_DEBUG(1), "LDAP driver running multithreaded"); +#else /* PTHREADS */ + /* if debugging, let user know we are single threaded. */ + ldap->log(ISC_LOG_DEBUG(1), "LDAP driver running single threaded"); +#endif /* PTHREADS */ + + if (argc < 9) { + ldap->log(ISC_LOG_ERROR, + "LDAP driver requires at least " + "8 command line args."); + goto cleanup; + } + + /* no more than 13 arg's should be passed to the driver */ + if (argc > 12) { + ldap->log(ISC_LOG_ERROR, + "LDAP driver cannot accept more than " + "11 command line args."); + goto cleanup; + } + + /* determine protocol version. */ + if (strncasecmp(argv[2], V2, strlen(V2)) == 0) + protocol = 2; + else if (strncasecmp(argv[2], V3, strlen(V3)) == 0) + protocol = 3; + else { + ldap->log(ISC_LOG_ERROR, + "LDAP driver protocol must be either %s or %s", + V2, V3); + goto cleanup; + } + + /* determine connection method. */ + if (strncasecmp(argv[3], SIMPLE, strlen(SIMPLE)) == 0) + method = LDAP_AUTH_SIMPLE; + else if (strncasecmp(argv[3], KRB41, strlen(KRB41)) == 0) + method = LDAP_AUTH_KRBV41; + else if (strncasecmp(argv[3], KRB42, strlen(KRB42)) == 0) + method = LDAP_AUTH_KRBV42; + else { + ldap->log(ISC_LOG_ERROR, + "LDAP driver authentication method must be " + "one of %s, %s or %s", SIMPLE, KRB41, KRB42); + goto cleanup; + } + + /* multithreaded build can have multiple DB connections */ +#if PTHREADS + /* check how many db connections we should create */ + dbcount = strtol(argv[1], &endp, 10); + if (*endp != '\0' || dbcount < 0) { + ldap->log(ISC_LOG_ERROR, + "LDAP driver database connection count " + "must be positive."); + goto cleanup; + } +#endif + + /* check that LDAP URL parameters make sense */ + switch (argc) { + case 12: + result = ldap_checkURL(ldap, argv[11], 0, + "allow zone transfer"); + if (result != ISC_R_SUCCESS) + goto cleanup; + case 11: + result = ldap_checkURL(ldap, argv[10], 3, "all nodes"); + if (result != ISC_R_SUCCESS) + goto cleanup; + case 10: + if (strlen(argv[9]) > 0) { + result = ldap_checkURL(ldap, argv[9], 3, "authority"); + if (result != ISC_R_SUCCESS) + goto cleanup; + } + case 9: + result = ldap_checkURL(ldap, argv[8], 3, "lookup"); + if (result != ISC_R_SUCCESS) + goto cleanup; + result = ldap_checkURL(ldap, argv[7], 0, "find zone"); + if (result != ISC_R_SUCCESS) + goto cleanup; + break; + default: + /* not really needed, should shut up compiler. */ + result = ISC_R_FAILURE; + } + + /* store info needed to automatically re-connect. */ + ldap->protocol = protocol; + ldap->method = method; + ldap->hosts = strdup(argv[6]); + if (ldap->hosts == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + ldap->user = strdup(argv[4]); + if (ldap->user == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + ldap->cred = strdup(argv[5]); + if (ldap->cred == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + +#if PTHREADS + /* allocate memory for database connection list */ + ldap->db = calloc(1, sizeof(db_list_t)); + if (ldap->db == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + /* initialize DB connection list */ + DLZ_LIST_INIT(*(ldap->db)); + + /* + * create the appropriate number of database instances (DBI) + * append each new DBI to the end of the list + */ + for (i = 0; i < dbcount; i++) { +#endif /* PTHREADS */ + /* how many queries were passed in from config file? */ + switch (argc) { + case 9: + result = build_dbinstance(NULL, NULL, NULL, argv[7], + argv[8], NULL, &dbi, + ldap->log); + break; + case 10: + result = build_dbinstance(NULL, NULL, argv[9], + argv[7], argv[8], + NULL, &dbi, ldap->log); + break; + case 11: + result = build_dbinstance(argv[10], NULL, argv[9], + argv[7], argv[8], + NULL, &dbi, ldap->log); + break; + case 12: + result = build_dbinstance(argv[10], argv[11], + argv[9], argv[7], + argv[8], NULL, &dbi, + ldap->log); + break; + default: + /* not really needed, should shut up compiler. */ + result = ISC_R_FAILURE; + } + + if (result == ISC_R_SUCCESS) { + ldap->log(ISC_LOG_DEBUG(2), + "LDAP driver created " + "database instance object."); + } else { /* unsuccessful?, log err msg and cleanup. */ + ldap->log(ISC_LOG_ERROR, + "LDAP driver could not create " + "database instance object."); + goto cleanup; + } + +#if PTHREADS + /* when multithreaded, build a list of DBI's */ + DLZ_LINK_INIT(dbi, link); + DLZ_LIST_APPEND(*(ldap->db), dbi, link); +#else + /* + * when single threaded, hold onto the one connection + * instance. + */ + ldap->db = dbi; +#endif + /* attempt to connect */ + result = ldap_connect(ldap, dbi); + + /* + * if db connection cannot be created, log err msg and + * cleanup. + */ + switch (result) { + /* success, do nothing */ + case ISC_R_SUCCESS: + break; + /* + * no memory means ldap_init could not + * allocate memory + */ + case ISC_R_NOMEMORY: +#if PTHREADS + ldap->log(ISC_LOG_ERROR, + "LDAP driver could not allocate memory " + "for connection number %u", i + 1); +#else + ldap->log(ISC_LOG_ERROR, + "LDAP driver could not allocate memory " + "for connection"); +#endif + goto cleanup; + /* + * no perm means ldap_set_option could not set + * protocol version + */ + case ISC_R_NOPERM: + ldap->log(ISC_LOG_ERROR, + "LDAP driver could not " + "set protocol version."); + result = ISC_R_FAILURE; + goto cleanup; + /* failure means couldn't connect to ldap server */ + case ISC_R_FAILURE: +#if PTHREADS + ldap->log(ISC_LOG_ERROR, + "LDAP driver could not bind " + "connection number %u to server.", i + 1); +#else + ldap->log(ISC_LOG_ERROR, + "LDAP driver could not " + "bind connection to server."); +#endif + goto cleanup; + /* + * default should never happen. If it does, + * major errors. + */ + default: + ldap->log(ISC_LOG_ERROR, + "dlz_create() failed (%d)", result); + result = ISC_R_UNEXPECTED; + goto cleanup; + } + +#if PTHREADS + /* set DBI = null for next loop through. */ + dbi = NULL; + } +#endif /* PTHREADS */ + + /* set dbdata to the ldap_instance we created. */ + *dbdata = ldap; + + return (ISC_R_SUCCESS); + + cleanup: + dlz_destroy(ldap); + + return (result); +} + +void +dlz_destroy(void *dbdata) { + if (dbdata != NULL) { + ldap_instance_t *db = (ldap_instance_t *)dbdata; +#if PTHREADS + /* cleanup the list of DBI's */ + if (db->db != NULL) + ldap_destroy_dblist((db_list_t *)(db->db)); +#else /* PTHREADS */ + if (db->db->dbconn != NULL) + ldap_unbind_s((LDAP *)(db->db->dbconn)); + + /* destroy single DB instance */ + destroy_dbinstance(db->db); +#endif /* PTHREADS */ + + if (db->hosts != NULL) + free(db->hosts); + if (db->user != NULL) + free(db->user); + if (db->cred != NULL) + free(db->cred); + free(dbdata); + } +} + +/* + * Return the version of the API + */ +int +dlz_version(unsigned int *flags) { + *flags |= DNS_SDLZFLAG_RELATIVERDATA; +#if PTHREADS + *flags |= DNS_SDLZFLAG_THREADSAFE; +#else + *flags &= ~DNS_SDLZFLAG_THREADSAFE; +#endif + return (DLZ_DLOPEN_VERSION); +} + +/* + * Register a helper function from the bind9 dlz_dlopen driver + */ +static void +b9_add_helper(ldap_instance_t *db, const char *helper_name, void *ptr) { + if (strcmp(helper_name, "log") == 0) + db->log = (log_t *)ptr; + if (strcmp(helper_name, "putrr") == 0) + db->putrr = (dns_sdlz_putrr_t *)ptr; + if (strcmp(helper_name, "putnamedrr") == 0) + db->putnamedrr = (dns_sdlz_putnamedrr_t *)ptr; + if (strcmp(helper_name, "writeable_zone") == 0) + db->writeable_zone = (dns_dlz_writeablezone_t *)ptr; +} diff --git a/contrib/dlz/modules/ldap/testing/README b/contrib/dlz/modules/ldap/testing/README new file mode 100644 index 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..6c79ab2 --- /dev/null +++ b/contrib/dlz/modules/ldap/testing/dlz.schema @@ -0,0 +1,187 @@ +# +# +# 1.3.6.1.4.1.18420.1.1.X is reserved for attribute types declared by the DLZ project. +# 1.3.6.1.4.1.18420.1.2.X is reserved for object classes declared by the DLZ project. +# 1.3.6.1.4.1.18420.1.3.X is reserved for PRIVATE extensions to the DLZ attribute +# types and object classes that may be needed by end users +# to add security, etc. Attributes and object classes using +# this OID MUST NOT be published outside of an organization +# except to offer them for consideration to become part of the +# standard attributes and object classes published by the DLZ project. + +attributetype ( 1.3.6.1.4.1.18420.1.1.10 + NAME 'dlzZoneName' + DESC 'DNS zone name - domain name not including host name' + SUP name + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.20 + NAME 'dlzHostName' + DESC 'Host portion of a domain name' + SUP name + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.30 + NAME 'dlzData' + DESC 'Data for the resource record' + SUP name + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.40 + NAME 'dlzType' + DESC 'DNS record type - A, SOA, NS, MX, etc...' + SUP name + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.50 + NAME 'dlzSerial' + DESC 'SOA record serial number' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.60 + NAME 'dlzRefresh' + DESC 'SOA record refresh time in seconds' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.70 + NAME 'dlzRetry' + DESC 'SOA retry time in seconds' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.80 + NAME 'dlzExpire' + DESC 'SOA expire time in seconds' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.90 + NAME 'dlzMinimum' + DESC 'SOA minimum time in seconds' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.100 + NAME 'dlzAdminEmail' + DESC 'E-mail address of person responsible for this zone - @ should be replaced with . (period)' + SUP name + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.110 + NAME 'dlzPrimaryNS' + DESC 'Primary name server for this zone - should be host name not IP address' + SUP name + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.120 + NAME 'dlzIPAddr' + DESC 'IP address - IPV4 should be in dot notation xxx.xxx.xxx.xxx IPV6 should be in colon notation xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx' + EQUALITY caseExactIA5Match + SYNTAX 1.3.6.1.4.1.1466.115.121.1.26{40} + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.130 + NAME 'dlzCName' + DESC 'DNS cname' + SUP name + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.140 + NAME 'dlzPreference' + DESC 'DNS MX record preference. Lower numbers have higher preference' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.150 + NAME 'dlzTTL' + DESC 'DNS time to live - how long this record can be cached by caching DNS servers' + EQUALITY integerMatch + SYNTAX 1.3.6.1.4.1.1466.115.121.1.27 + SINGLE-VALUE ) + +attributetype ( 1.3.6.1.4.1.18420.1.1.160 + NAME 'dlzRecordID' + DESC 'Unique ID for each DLZ resource record' + SUP name + SINGLE-VALUE ) + +#------------------------------------------------------------------------------ +# Object class definitions +#------------------------------------------------------------------------------ + +objectclass ( 1.3.6.1.4.1.18420.1.2.10 + NAME 'dlzZone' + DESC 'Zone name portion of a domain name' + SUP top STRUCTURAL + MUST ( objectclass $ dlzZoneName ) ) + +objectclass ( 1.3.6.1.4.1.18420.1.2.20 + NAME 'dlzHost' + DESC 'Host name portion of a domain name' + SUP top STRUCTURAL + MUST ( objectclass $ dlzHostName ) ) + +objectclass ( 1.3.6.1.4.1.18420.1.2.30 + NAME 'dlzAbstractRecord' + DESC 'Data common to all DNS record types' + SUP top ABSTRACT + MUST ( objectclass $ dlzRecordID $ dlzHostName $ dlzType $ dlzTTL ) ) + +objectclass ( 1.3.6.1.4.1.18420.1.2.40 + NAME 'dlzGenericRecord' + DESC 'Generic DNS record - useful when a specific object class has not been defined for a DNS record' + SUP dlzAbstractRecord STRUCTURAL + MUST ( dlzData ) ) + +objectclass ( 1.3.6.1.4.1.18420.1.2.50 + NAME 'dlzARecord' + DESC 'DNS A record' + SUP dlzAbstractrecord STRUCTURAL + MUST ( dlzIPAddr ) ) + +objectclass ( 1.3.6.1.4.1.18420.1.2.60 + NAME 'dlzNSRecord' + DESC 'DNS NS record' + SUP dlzGenericRecord STRUCTURAL ) + +objectclass ( 1.3.6.1.4.1.18420.1.2.70 + NAME 'dlzMXRecord' + DESC 'DNS MX record' + SUP dlzGenericRecord STRUCTURAL + MUST ( dlzPreference ) ) + +objectclass ( 1.3.6.1.4.1.18420.1.2.80 + NAME 'dlzSOARecord' + DESC 'DNS SOA record' + SUP dlzAbstractRecord STRUCTURAL + MUST ( dlzSerial $ dlzRefresh $ dlzRetry + $ dlzExpire $ dlzMinimum $ dlzAdminEmail $ dlzPrimaryNS ) ) + +objectclass ( 1.3.6.1.4.1.18420.1.2.90 + NAME 'dlzTextRecord' + DESC 'Text data with spaces should be wrapped in double quotes' + SUP dlzGenericRecord STRUCTURAL ) + +objectclass ( 1.3.6.1.4.1.18420.1.2.100 + NAME 'dlzPTRRecord' + DESC 'DNS PTR record' + SUP dlzGenericRecord STRUCTURAL ) + +objectclass ( 1.3.6.1.4.1.18420.1.2.110 + NAME 'dlzCNameRecord' + DESC 'DNS CName record' + SUP dlzGenericRecord STRUCTURAL ) + +objectclass ( 1.3.6.1.4.1.18420.1.2.120 + NAME 'dlzXFR' + DESC 'Host allowed to perform zone transfer' + SUP top STRUCTURAL + MUST ( objectclass $ dlzRecordID $ dlzIPAddr ) ) diff --git a/contrib/dlz/modules/ldap/testing/example.ldif b/contrib/dlz/modules/ldap/testing/example.ldif new file mode 100644 index 0000000..8362b1e --- /dev/null +++ b/contrib/dlz/modules/ldap/testing/example.ldif @@ -0,0 +1,168 @@ +# server suffix - o=bind-dlz + +dn: o=bind-dlz +objectclass: organization +o: bind-dlz + +dn: ou=dns,o=bind-dlz +objectclass: organizationalUnit +ou: dns + +dn: dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzZone +dlzZoneName: example.com + +dn: dlzHostName=@,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzHost +dlzHostName: @ + +dn: dlzHostName=www,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzHost +dlzHostName: www + +dn: dlzHostName=mail,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzHost +dlzHostName: mail + +dn: dlzHostName=backup,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzHost +dlzHostName: backup + +dn: dlzHostName=ns1,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzHost +dlzHostName: ns1 + +dn: dlzHostName=ns2,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzHost +dlzHostName: ns2 + +dn: dlzHostName=~,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzHost +dlzHostName: ~ + +dn: dlzRecordID=1,dlzHostName=@,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzGenericRecord +dlzRecordID: 1 +dlzHostName: @ +dlzType: txt +dlzData: "this is a text record" +dlzTTL: 10 + +dn: dlzRecordID=2,dlzHostName=www,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzARecord +dlzRecordID: 2 +dlzHostName: www +dlzType: a +dlzIPAddr: 192.168.0.1 +dlzTTL: 10 + +dn: dlzRecordID=3,dlzHostName=mail,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzARecord +dlzRecordID: 3 +dlzHostName: mail +dlzType: a +dlzIPAddr: 192.168.0.2 +dlzTTL: 10 + +dn: dlzRecordID=4,dlzHostName=backup,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzARecord +dlzRecordID: 4 +dlzHostName: backup +dlzType: a +dlzIPAddr: 192.168.0.3 +dlzTTL: 10 + +dn: dlzRecordID=5,dlzHostName=@,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzMXRecord +dlzRecordID: 5 +dlzHostName: @ +dlzType: mx +dlzData: mail +dlzPreference: 20 +dlzTTL: 10 + +dn: dlzRecordID=6,dlzHostName=@,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzMXRecord +dlzRecordID: 6 +dlzHostName: @ +dlzType: mx +dlzData: backup +dlzPreference: 40 +dlzTTL: 10 + +dn: dlzRecordID=7,dlzHostName=www,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzMXRecord +dlzRecordID: 7 +dlzHostName: www +dlzType: mx +dlzData: backup +dlzPreference: 40 +dlzTTL: 10 + +dn: dlzRecordID=8,dlzHostName=www,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzMXRecord +dlzRecordID: 8 +dlzHostName: www +dlzType: mx +dlzData: mail +dlzPreference: 20 +dlzTTL: 10 + +dn: dlzRecordID=9,dlzHostName=ns1,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzARecord +dlzRecordID: 9 +dlzHostName: ns1 +dlzType: a +dlzIPAddr: 192.168.0.4 +dlzTTL: 10 + +dn: dlzRecordID=10,dlzHostName=ns2,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzARecord +dlzRecordID: 10 +dlzHostName: ns2 +dlzType: a +dlzIPAddr: 192.168.0.5 +dlzTTL: 10 + +dn: dlzRecordID=11,dlzHostName=@,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzSOARecord +dlzRecordID: 11 +dlzHostName: @ +dlzType: soa +dlzSerial: 2 +dlzRefresh: 2800 +dlzRetry: 7200 +dlzExpire: 604800 +dlzMinimum: 86400 +dlzAdminEmail: root.example.com. +dlzPrimaryns: ns1.example.com. +dlzTTL: 10 + +dn: dlzRecordID=12,dlzHostName=@,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzNSRecord +dlzRecordID: 12 +dlzHostName: @ +dlzType: ns +dlzData: ns1.example.com. +dlzTTL: 10 + +dn: dlzRecordID=13,dlzHostName=@,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzNSRecord +dlzRecordID: 13 +dlzHostName: @ +dlzType: ns +dlzData: ns2 +dlzTTL: 10 + +dn: dlzRecordID=14,dlzHostName=~,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzARecord +dlzRecordID: 14 +dlzHostName: ~ +dlzType: a +dlzIPAddr: 192.168.0.250 +dlzTTL: 10 + +dn: dlzRecordID=15,dlzZoneName=example.com,ou=dns,o=bind-dlz +objectclass: dlzXFR +dlzRecordID: 15 +dlzIPAddr: 127.0.0.1 diff --git a/contrib/dlz/modules/ldap/testing/named.conf b/contrib/dlz/modules/ldap/testing/named.conf new file mode 100644 index 0000000..e79a02e --- /dev/null +++ b/contrib/dlz/modules/ldap/testing/named.conf @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +controls { }; + +options { + directory "."; + port 5300; + pid-file "named.pid"; + session-keyfile "session.key"; + listen-on { any; }; + listen-on-v6 { none; }; + recursion no; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm hmac-md5; +}; + +controls { + inet 127.0.0.1 port 9953 allow { any; } keys { rndc_key; }; +}; + +dlz "test" { + database "dlopen ../dlz_ldap_dynamic.so 2 + v3 simple {cn=Manager,o=bind-dlz} {secret} {127.0.0.1} + ldap:///dlzZoneName=$zone$,ou=dns,o=bind-dlz???objectclass=dlzZone + ldap:///dlzHostName=$record$,dlzZoneName=$zone$,ou=dns,o=bind-dlz?dlzTTL,dlzType,dlzPreference,dlzData,dlzIPAddr?sub?(&(objectclass=dlzAbstractRecord)(!(dlzType=soa))) + ldap:///dlzHostName=@,dlzZoneName=$zone$,ou=dns,o=bind-dlz?dlzTTL,dlzType,dlzData,dlzPrimaryNS,dlzAdminEmail,dlzSerial,dlzRefresh,dlzRetry,dlzExpire,dlzMinimum?sub?(&(objectclass=dlzAbstractRecord)(dlzType=soa)) + ldap:///dlzZoneName=$zone$,ou=dns,o=bind-dlz?dlzTTL,dlzType,dlzHostName,dlzPreference,dlzData,dlzIPAddr,dlzPrimaryNS,dlzAdminEmail,dlzSerial,dlzRefresh,dlzRetry,dlzExpire,dlzMinimum?sub?(&(objectclass=dlzAbstractRecord)(!(dlzType=soa))) + ldap:///dlzZoneName=$zone$,ou=dns,o=bind-dlz??sub?(&(objectclass=dlzXFR)(dlzIPAddr=$client$))"; +}; diff --git a/contrib/dlz/modules/ldap/testing/slapd.conf b/contrib/dlz/modules/ldap/testing/slapd.conf new file mode 100644 index 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..5ab31be --- /dev/null +++ b/contrib/dlz/modules/mysql/Makefile.in @@ -0,0 +1,21 @@ +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..a325fc5 --- /dev/null +++ b/contrib/dlz/modules/mysql/dlz_mysql_dynamic.c @@ -0,0 +1,1104 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + * + * The development of Dynamically Loadable Zones (DLZ) for BIND 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Copyright (C) 1999-2001, 2013, 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/. + */ + +/* + * This provides the externally loadable MySQL DLZ module, without + * update support + */ + +#include <stdio.h> +#include <string.h> +#include <stdarg.h> +#include <stdlib.h> + +#include <dlz_minimal.h> +#include <dlz_list.h> +#include <dlz_dbi.h> +#include <dlz_pthread.h> + +#include <mysql/mysql.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 everthing 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 + dbinstance_t *db; /*%< handle to DB */ +#endif + + 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) { + result = ISC_R_FAILURE; + goto cleanup; + } + + /* 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 == NULL) + return (ISC_R_FAILURE); + + 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 postive 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 postive 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 postive 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 MYSQL_VERSION_ID >= 50000 + my_bool auto_reconnect = 1; +#endif +#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 + /* + * when single threaded, hold onto the one connection + * instance. + */ + mysql->db = dbi; +#endif + + /* 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; + +#if MYSQL_VERSION_ID >= 50000 + /* enable automatic reconnection. */ + if (mysql_options((MYSQL *) dbi->dbconn, MYSQL_OPT_RECONNECT, + &auto_reconnect) != 0) { + mysql->log(ISC_LOG_WARNING, + "MySQL module failed to set " + "MYSQL_OPT_RECONNECT option, continuing"); + } +#endif + + 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..f8afa23 --- /dev/null +++ b/contrib/dlz/modules/mysql/testing/named.conf @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +controls { }; + +options { + directory "."; + port 5300; + pid-file "named.pid"; + session-keyfile "session.key"; + listen-on { any; }; + listen-on-v6 { none; }; + recursion no; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm hmac-md5; +}; + +controls { + inet 127.0.0.1 port 9953 allow { any; } keys { rndc_key; }; +}; + +dlz "test" { + database "dlopen ../dlz_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..248e3da --- /dev/null +++ b/contrib/dlz/modules/mysqldyn/Makefile.in @@ -0,0 +1,21 @@ +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..468e37c --- /dev/null +++ b/contrib/dlz/modules/mysqldyn/README @@ -0,0 +1,60 @@ +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..13f99f6 --- /dev/null +++ b/contrib/dlz/modules/mysqldyn/dlz_mysqldyn_mod.c @@ -0,0 +1,1705 @@ +/* + * Copyright (C) 2014 Maui Systems Ltd, Scotland, contact@maui-systems.co.uk. + * + * 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 MAUI SYSTEMS LTD DISCLAIMS ALL + * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL MAUI SYSTEMS LTD BE + * LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR + * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, + * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + */ + +/* + * Copyright (C) 2011,2014 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <unistd.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <netinet/in.h> +#include <inttypes.h> +#include <pthread.h> +#include <netdb.h> +#include <ifaddrs.h> + +#include <mysql/mysql.h> +#include <mysql/errmsg.h> + +#include <dlz_minimal.h> +#include <dlz_list.h> +#include <dlz_pthread.h> + +/* + * 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')" + +#ifdef WIN32 +#define STRTOK_R(a, b, c) strtok_s(a, b, c) +#elif defined(_REENTRANT) +#define STRTOK_R(a, b, c) strtok_r(a, b, c) +#else +#define STRTOK_R(a, b, c) strtok(a, b) +#endif + +/* + * 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); + + for (item = DLZ_LIST_HEAD(arglist); + item != NULL; + item = DLZ_LIST_NEXT(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 inline 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] = '.'; + memcpy(&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; + + memcpy(&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, *dclass, *type, *data, *ttlstr, *buf; + 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++) { + my_bool opt = 1; + 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, &opt); + 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) +{ + 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) +{ + 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 >= DNS_CLIENTINFO_VERSION) { + 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[1024]; + int ttl; + + sscanf(row[7], "%d", &ttl); + fqhn(row[0], zone, host); + fqhn(row[1], zone, admin); + + /* zone admin serial refresh retry expire min */ + sprintf(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 *) + malloc(sizeof(mysql_transaction_t)); + newtx->zone = strdup(zone); + newtx->zone_id = strdup(zone_id); + 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); + free(newtx->zone); + 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 + 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..3b3a0dc --- /dev/null +++ b/contrib/dlz/modules/mysqldyn/testing/named.conf @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +controls { }; + +options { + directory "."; + port 5300; + pid-file "named.pid"; + session-keyfile "session.key"; + listen-on { any; }; + listen-on-v6 { none; }; + recursion no; +}; + +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..ff2da39 --- /dev/null +++ b/contrib/dlz/modules/perl/Makefile @@ -0,0 +1,33 @@ +# 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 diff --git a/contrib/dlz/modules/perl/README b/contrib/dlz/modules/perl/README new file mode 100644 index 0000000..6f79e3f --- /dev/null +++ b/contrib/dlz/modules/perl/README @@ -0,0 +1,9 @@ +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..ccecb24 --- /dev/null +++ b/contrib/dlz/modules/perl/dlz_perl_callback.xs @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2012 John Eaglesham + * + * 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 JOHN EAGLESHAM + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * JOHN EAGLESHAM 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..fd341d9 --- /dev/null +++ b/contrib/dlz/modules/perl/dlz_perl_callback_clientinfo.xs @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2012 John Eaglesham + * + * 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 JOHN EAGLESHAM + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * JOHN EAGLESHAM 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..2a9a44f --- /dev/null +++ b/contrib/dlz/modules/perl/dlz_perl_driver.c @@ -0,0 +1,738 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Copyright (C) 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/. + */ + +/* + * Copyright (C) 2009-2012 John Eaglesham + * + * 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 JOHN EAGLESHAM + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * JOHN EAGLESHAM 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 <config.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include <EXTERN.h> +#include <perl.h> + +#include <dlz_minimal.h> + +#include "dlz_perl_driver.h" + +/* Enable debug logging? */ +#if 0 +#define carp(...) cd->log(ISC_LOG_INFO, __VA_ARGS__); +#else +#define carp(...) +#endif + +#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 + +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) +{ + 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) { + 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 + 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)) { + 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 + 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. + */ + 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 +isc_result_t +dlz_findzonedb(void *dbdata, const char *name, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) +#endif +{ + config_data_t *cd = (config_data_t *) dbdata; + int r; + isc_result_t retval; +#ifdef MULTIPLICITY + PerlInterpreter *my_perl = cd->perl; +#endif + +#if DLZ_DLOPEN_VERSION >= 3 + UNUSED(methods); + UNUSED(clientinfo); +#endif + + 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. + */ + 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 +isc_result_t +dlz_lookup(const char *zone, const char *name, + void *dbdata, dns_sdlzlookup_t *lookup, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) +#endif +{ + isc_result_t 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 + +#if DLZ_DLOPEN_VERSION >= 2 + UNUSED(methods); + UNUSED(clientinfo); +#endif + + 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)) { + 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); +} + +const char * +#ifdef MULTIPLICITY +missing_perl_method(const char *perl_class_name, PerlInterpreter *my_perl) +#else +missing_perl_method(const char *perl_class_name) +#endif +{ + const int BUF_LEN = 64; /* Should be big enough, right? hah */ + 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 *init_args[] = { NULL, NULL }; + char *perlrun[] = { "", NULL, "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 + + 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 + + cd->perl = perl_alloc(); + if (cd->perl == NULL) { + free(cd); + return (ISC_R_FAILURE); + } +#ifdef MULTIPLICITY + my_perl = cd->perl; +#endif + 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 + if (missing_method_name = missing_perl_method(perl_class_name)) +#endif + { + 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)) { + 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 + 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 + + cd->log(ISC_LOG_INFO, "DLZ Perl: Unloading driver."); + +#ifndef MULTIPLICITY + if (!global_perl_dont_free) { +#endif + 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 + + 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..1db8e28 --- /dev/null +++ b/contrib/dlz/modules/perl/dlz_perl_driver.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2009-2012 John Eaglesham + * + * 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 JOHN EAGLESHAM + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * JOHN EAGLESHAM 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..55bc388 --- /dev/null +++ b/contrib/dlz/modules/perl/testing/dlz_perl_example.pm @@ -0,0 +1,177 @@ +# +# Copyright (C) 2009-2012 John Eaglesham +# +# 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 JOHN EAGLESHAM +# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL +# JOHN EAGLESHAM 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..32eebef --- /dev/null +++ b/contrib/dlz/modules/perl/testing/named.conf @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2011 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +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..94af96a --- /dev/null +++ b/contrib/dlz/modules/sqlite3/Makefile @@ -0,0 +1,21 @@ +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..2d7d0b0 --- /dev/null +++ b/contrib/dlz/modules/sqlite3/dlz_sqlite3_dynamic.c @@ -0,0 +1,1105 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + * + * The development of Dynamically Loadable Zones (DLZ) for BIND 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Copyright (C) 1999-2001, 2013-2014, 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/. + */ + +/* + * This provides the externally loadable SQLitee DLZ module, without + * update support. Based in part on SQLite code contributed by Tim Tessier. + */ + +#include <stdio.h> +#include <string.h> +#include <stdarg.h> +#include <stdlib.h> + +#include <dlz_minimal.h> +#include <dlz_list.h> +#include <dlz_dbi.h> +#include <dlz_pthread.h> + +#include <sqlite3.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 everthing 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 + dbinstance_t *db; /*%< handle to DB */ +#endif + + 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 */ + unsigned int pnRow; /* Number of result rows */ + unsigned int pnColumn; /* Number of result columns */ + unsigned 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; + unsigned int i = 0; + unsigned int j = 0; + 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) { + result = ISC_R_FAILURE; + goto cleanup; + } + + /* 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 (rs == NULL) + result = ISC_R_FAILURE; + } + + *rsp = rs; + + cleanup: + if (dbi == NULL) + return (ISC_R_FAILURE); + + 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 > 0U && 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 i, 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 postive 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 postive 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 postive 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 SQLITE3_VERSION_ID >= 50000 + my_bool auto_reconnect = 1; +#endif +#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 + /* + * when single threaded, hold onto the one connection + * instance. + */ + s3->db = dbi; +#endif + + 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..9f310af --- /dev/null +++ b/contrib/dlz/modules/sqlite3/testing/named.conf @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +controls { }; + +options { + directory "."; + port 5300; + pid-file "named.pid"; + session-keyfile "session.key"; + listen-on { any; }; + listen-on-v6 { none; }; + recursion no; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm hmac-md5; +}; + +controls { + inet 127.0.0.1 port 9953 allow { any; } keys { rndc_key; }; +}; + +dlz "test" { + database "dlopen ../dlz_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..20a5d4e --- /dev/null +++ b/contrib/dlz/modules/wildcard/Makefile @@ -0,0 +1,20 @@ +prefix = /usr +libdir = $(prefix)/lib/bind9 + +CFLAGS=-fPIC -g -I../include + +all: dlz_wildcard_dynamic.so + +dlz_dbi.o: ../common/dlz_dbi.c + $(CC) $(CFLAGS) -c ../common/dlz_dbi.c + +dlz_wildcard_dynamic.so: dlz_wildcard_dynamic.c dlz_dbi.o + $(CC) $(CFLAGS) -shared -o dlz_wildcard_dynamic.so \ + dlz_wildcard_dynamic.c dlz_dbi.o + +clean: + rm -f dlz_wildcard_dynamic.so *.o + +install: dlz_wildcard_dynamic.so + mkdir -p $(DESTDIR)$(libdir) + install dlz_wildcard_dynamic.so $(DESTDIR)$(libdir) diff --git a/contrib/dlz/modules/wildcard/README b/contrib/dlz/modules/wildcard/README new file mode 100644 index 0000000..b19009b --- /dev/null +++ b/contrib/dlz/modules/wildcard/README @@ -0,0 +1,31 @@ +The "wildcard" DLZ module provides a "template" zone for domains matching +a wildcard name. For example, the following DLZ configuration would match +any zone name containing the string "example" and ending with .com, such +as "thisexample.com", "exampleofthat.com", or "anexampleoftheotherthing.com". + + dlz "test" { + database "dlopen ../dlz_wildcard_dynamic.so + *example*.com 10.53.* 1800 + @ 3600 SOA {ns3.example.nil. support.example.nil. 42 14400 7200 2592000 600} + @ 3600 NS ns3.example.nil. + @ 3600 NS ns4.example.nil. + @ 3600 NS ns8.example.nil. + @ 3600 MX {5 mail.example.nil.} + ftp 86400 A 192.0.0.1 + sql 86400 A 192.0.0.2 + tmp {} A 192.0.0.3 + www 86400 A 192.0.0.3 + www 86400 AAAA ::1 + txt 300 TXT {\"you requested $record$ in $zone$\"} + * 86400 A 192.0.0.100"; + }; + +For any zone name matchin the wildcard, it would return the data from +the template. "$zone$" is replaced with zone name: i.e., the shortest +possible string of labels in the query name that matches the wildcard. +"$record$" is replaced with the remainder of the query name. In the +example above, a query for "txt.thisexample.com/TXT" would return the +string "you requested txt in thisexample.com". + +Any client whose source address matches the second wildcard ("10.53.*") +is allowed to request a zone transfer. diff --git a/contrib/dlz/modules/wildcard/dlz_wildcard_dynamic.c b/contrib/dlz/modules/wildcard/dlz_wildcard_dynamic.c new file mode 100644 index 0000000..07b65d2 --- /dev/null +++ b/contrib/dlz/modules/wildcard/dlz_wildcard_dynamic.c @@ -0,0 +1,730 @@ +/* + * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl. + * Copyright (C) 2012 Vadim Goncharov, Russia, vadim_nuclight@mail.ru. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the + * above copyright notice and this permission notice appear in all + * copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR + * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE + * USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * Copyright (C) 1999-2001, 2013, 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/. + */ + +/* + * This provides the externally loadable wildcard DLZ module. + */ + +#include <inttypes.h> +#include <stdio.h> +#include <string.h> +#include <stdarg.h> +#include <stdlib.h> + +#include <dlz_minimal.h> +#include <dlz_list.h> +#include <dlz_dbi.h> + +#include <ctype.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 +isc_result_t +dlz_findzonedb(void *dbdata, const char *name, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) +#endif +{ + config_data_t *cd = (config_data_t *) dbdata; + const char *p; + +#if DLZ_DLOPEN_VERSION >= 3 + UNUSED(methods); + UNUSED(clientinfo); +#endif + + p = shortest_match(cd->zone_pattern, name); + if (p == NULL) + return (ISC_R_NOTFOUND); + + /* Write info message to log */ + cd->log(ISC_LOG_DEBUG(1), + "dlz_wildcard findzonedb matched '%s'", p); + + return (ISC_R_SUCCESS); +} + +#if DLZ_DLOPEN_VERSION == 1 +isc_result_t +dlz_lookup(const char *zone, const char *name, + void *dbdata, dns_sdlzlookup_t *lookup) +#else +isc_result_t +dlz_lookup(const char *zone, const char *name, + void *dbdata, dns_sdlzlookup_t *lookup, + dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) +#endif +{ + isc_result_t result; + config_data_t *cd = (config_data_t *) dbdata; + char *querystring = NULL; + const char *p; + char *namebuf; + nrr_t *nrec; + bool origin = true; + +#if DLZ_DLOPEN_VERSION >= 2 + UNUSED(methods); + UNUSED(clientinfo); +#endif + + p = shortest_match(cd->zone_pattern, zone); + if (p == NULL) + return (ISC_R_NOTFOUND); + + DE_CONST(name, cd->record); + DE_CONST(p, cd->zone); + + if ((p != zone) && (strcmp(name, "@") == 0 || strcmp(name, zone) == 0)) + { + size_t len = p - zone; + namebuf = malloc(len); + if (namebuf == NULL) + return (ISC_R_NOMEMORY); + strncpy(namebuf, zone, len - 1); + namebuf[len - 1] = '\0'; + cd->record = namebuf; + origin = false; + } else if (p == zone) + cd->record = "@"; + + /* Write info message to log */ + cd->log(ISC_LOG_DEBUG(1), + "dlz_wildcard_dynamic: lookup for '%s' in '%s': " + "trying '%s' in '%s'", + name, zone, cd->record, cd->zone); + + result = ISC_R_NOTFOUND; + nrec = DLZ_LIST_HEAD(cd->rrs_list); + while (nrec != NULL) { + nrr_t *next = DLZ_LIST_NEXT(nrec, link); + if (strcmp(cd->record, nrec->name) == 0) { + /* We handle authority data in dlz_authority() */ + if (strcmp(nrec->type, "SOA") == 0 || + strcmp(nrec->type, "NS") == 0) + { + nrec = next; + continue; + } + + querystring = build_querystring(nrec->data); + if (querystring == NULL) { + result = ISC_R_NOMEMORY; + goto done; + } + + result = cd->putrr(lookup, nrec->type, + nrec->ttl, querystring); + if (result != ISC_R_SUCCESS) + goto done; + + result = ISC_R_SUCCESS; + + free(querystring); + querystring = NULL; + } + nrec = next; + } + +done: + cd->zone = NULL; + cd->record = NULL; + + if (querystring != NULL) + free(querystring); + + return (result); +} + +isc_result_t +dlz_authority(const char *zone, void *dbdata, dns_sdlzlookup_t *lookup) { + isc_result_t result; + config_data_t *cd = (config_data_t *) dbdata; + char *querystring = NULL; + nrr_t *nrec; + const char *p, *name = "@"; + + p = shortest_match(cd->zone_pattern, zone); + if (p == NULL) + return (ISC_R_NOTFOUND); + + DE_CONST(p, cd->zone); + + /* Write info message to log */ + cd->log(ISC_LOG_DEBUG(1), + "dlz_wildcard_dynamic: authority for '%s'", zone); + + result = ISC_R_NOTFOUND; + nrec = DLZ_LIST_HEAD(cd->rrs_list); + while (nrec != NULL) { + bool origin; + if (strcmp("@", nrec->name) == 0) { + isc_result_t presult; + + querystring = build_querystring(nrec->data); + if (querystring == NULL) { + result = ISC_R_NOMEMORY; + goto done; + } + + presult = cd->putrr(lookup, nrec->type, + nrec->ttl, querystring); + if (presult != ISC_R_SUCCESS) { + result = presult; + goto done; + } + + result = ISC_R_SUCCESS; + + free(querystring); + querystring = NULL; + } + nrec = DLZ_LIST_NEXT(nrec, link); + } + +done: + cd->zone = NULL; + + if (querystring != NULL) + free(querystring); + + return (result); +} + +static void +destroy_rrlist(config_data_t *cd) { + nrr_t *trec, *nrec; + + nrec = DLZ_LIST_HEAD(cd->rrs_list); + + while (nrec != NULL) { + trec = nrec; + + destroy_querylist(&trec->data); + + if (trec->name != NULL) + free(trec->name); + if (trec->type != NULL) + free(trec->type); + trec->name = trec->type = NULL; + + /* Get the next record, before we destroy this one. */ + nrec = DLZ_LIST_NEXT(nrec, link); + + free(trec); + } +} + +isc_result_t +dlz_create(const char *dlzname, unsigned int argc, char *argv[], + void **dbdata, ...) +{ + config_data_t *cd; + char *endp; + int i, def_ttl; + nrr_t *trec = NULL; + isc_result_t result; + const char *helper_name; + va_list ap; + + if (argc < 8 || argc % 4 != 0) + return (ISC_R_FAILURE); + + cd = calloc(1, sizeof(config_data_t)); + if (cd == NULL) + return (ISC_R_NOMEMORY); + memset(cd, 0, sizeof(config_data_t)); + + /* Fill in the helper functions */ + va_start(ap, dbdata); + while ((helper_name = va_arg(ap, const char*)) != NULL) + b9_add_helper(cd, helper_name, va_arg(ap, void*)); + va_end(ap); + + /* + * Write info message to log + */ + cd->log(ISC_LOG_INFO, + "Loading '%s' using DLZ_wildcard driver. " + "Zone: %s, AXFR allowed for: %s, $TTL: %s", + dlzname, argv[1], argv[2], argv[3]); + + /* initialize the records list here to simplify cleanup */ + DLZ_LIST_INIT(cd->rrs_list); + + cd->zone_pattern = strdup(argv[1]); + 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; + } + /* NOTREACHED */ +} + +static int +rangematch(const char *pattern, char test, int flags, char **newp) { + int negate, ok; + char c, c2; + + /* + * A bracket expression starting with an unquoted circumflex + * character produces unspecified results (IEEE 1003.2-1992, + * 3.13.2). This implementation treats it like '!', for + * consistency with the regular expression syntax. + * J.T. Conklin (conklin@ngai.kaleida.com) + */ + if ( (negate = (*pattern == '!' || *pattern == '^')) ) + ++pattern; + + if (flags & FNM_CASEFOLD) + test = tolower((unsigned char)test); + + /* + * A right bracket shall lose its special meaning and represent + * itself in a bracket expression if it occurs first in the list. + * -- POSIX.2 2.8.3.2 + */ + ok = 0; + c = *pattern++; + do { + if (c == '\\' && !(flags & FNM_NOESCAPE)) + c = *pattern++; + if (c == EOS) + return (RANGE_ERROR); + + if (c == '/' && (flags & FNM_PATHNAME)) + return (RANGE_NOMATCH); + + if (flags & FNM_CASEFOLD) + c = tolower((unsigned char)c); + + if (*pattern == '-' + && (c2 = *(pattern+1)) != EOS && c2 != ']') { + pattern += 2; + if (c2 == '\\' && !(flags & FNM_NOESCAPE)) + c2 = *pattern++; + if (c2 == EOS) + return (RANGE_ERROR); + + if (flags & FNM_CASEFOLD) + c2 = tolower((unsigned char)c2); + + if (c <= test && test <= c2) + ok = 1; + } else if (c == test) + ok = 1; + } while ((c = *pattern++) != ']'); + + *newp = (char *)(uintptr_t)pattern; + return (ok == negate ? RANGE_NOMATCH : RANGE_MATCH); +} diff --git a/contrib/dlz/modules/wildcard/testing/named.conf b/contrib/dlz/modules/wildcard/testing/named.conf new file mode 100644 index 0000000..0192e18 --- /dev/null +++ b/contrib/dlz/modules/wildcard/testing/named.conf @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC") + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +controls { }; + +options { + directory "."; + port 5300; + pid-file "named.pid"; + session-keyfile "session.key"; + listen-on { any; }; + listen-on-v6 { none; }; + recursion no; +}; + +key rndc_key { + secret "1234abcd8765"; + algorithm hmac-md5; +}; + +controls { + inet 127.0.0.1 port 9953 allow { any; } keys { rndc_key; }; +}; + +/* + * This will match any zone name containing the string "example" and + * ending with .com, such as "thisexample.com", "exampleofthat.com", + * or "anexampleoftheotherthing.com". + */ +dlz "test" { + database "dlopen ../dlz_wildcard_dynamic.so + *example*.com 10.53.* 1800 + @ 3600 SOA {ns3.example.nil. support.example.nil. 42 14400 7200 2592000 600} + @ 3600 NS ns3.example.nil. + @ 3600 NS ns4.example.nil. + @ 3600 NS ns8.example.nil. + @ 3600 MX {5 mail.example.nil.} + ftp 86400 A 192.0.0.1 + sql 86400 A 192.0.0.2 + tmp {} A 192.0.0.3 + www 86400 A 192.0.0.3 + www 86400 AAAA ::1 + txt 300 TXT {\"you requested $record$ in $zone$\"} + * 86400 A 192.0.0.100"; +}; diff --git a/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..12d07a3 --- /dev/null +++ b/contrib/dnspriv/named.conf @@ -0,0 +1,18 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +options { + 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..65e3868 --- /dev/null +++ b/contrib/dnspriv/nginx.conf @@ -0,0 +1,41 @@ +# 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. + +# 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..d94b084 --- /dev/null +++ b/contrib/kasp/kasp.xml @@ -0,0 +1,134 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<!-- 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..b78a968 --- /dev/null +++ b/contrib/kasp/kasp2policy.py @@ -0,0 +1,209 @@ +#!/usr/bin/python +############################################################################ +# Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. +############################################################################ +# kasp2policy.py +# This translates the Keys section of a KASP XML file into a dnssec.policy +# file that can be used by dnssec-keymgr. +############################################################################ + +from xml.etree import cElementTree as ET +from collections import defaultdict +from isc import dnskey +import ply.yacc as yacc +import ply.lex as lex +import re + +############################################################################ +# Translate KASP duration values into seconds +############################################################################ +class kasptime: + class ktlex: + 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' + + def t_NUM(self, t): + r'\d+' + t.value = int(t.value) + return t + + def t_error(self, 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) + + def p_ktime_4(self, p): + "ktime : P periods T times" + p[0] = p[2] + p[4] + + def p_ktime_3(self, p): + "ktime : P T times" + p[0] = p[3] + + def p_ktime_2(self, p): + "ktime : P periods" + p[0] = p[2] + + def p_periods_1(self, p): + "periods : period" + p[0] = p[1] + + def p_periods_2(self, p): + "periods : periods period" + p[0] = p[1] + p[2] + + def p_times_1(self, p): + "times : time" + p[0] = p[1] + + def p_times_2(self, p): + "times : times time" + p[0] = p[1] + p[2] + + def p_period(self, 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 + + def p_time(self, 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]) + + def p_error(self, p): + print("Syntax error") + +############################################################################ +# Load the contents of a KASP XML file as a python dictionary +############################################################################ +class kasp(): + @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) + d = {t.tag: + {k:v[0] if len(v) == 1 else v for k, v in dd.iteritems()}} + 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__": + from pprint import * + import sys + + if len(sys.argv) < 2: + print("Usage: kasp2policy <filename>") + exit(1) + + try: + kinfo = kasp(sys.argv[1]) + except: + print("%s: unable to load KASP file '%s'" % (sys.argv[0], sys.argv[1])) + exit(1) + + kt = kasptime() + first = True + + for p in kinfo['KASP']['Policy']: + if not p['@name'] or not p['Keys']: continue + if not first: + print("") + first = False + if p['Description']: + d = p['Description'].strip() + print("# %s" % re.sub(r"\n\s*", "\n# ", d)) + print("policy %s {" % p['@name']) + ksk = p['Keys']['KSK'] + zsk = p['Keys']['ZSK'] + kalg = ksk['Algorithm'] + zalg = zsk['Algorithm'] + algnum = kalg['#text'] or zalg['#text'] + if algnum: + print("\talgorithm %s;" % dnskey.algstr(int(algnum))) + if p['Keys']['TTL']: + print("\tkeyttl %d;" % kt.parse(p['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/perftcpdns/Makefile.in b/contrib/perftcpdns/Makefile.in new file mode 100644 index 0000000..bff2db0 --- /dev/null +++ b/contrib/perftcpdns/Makefile.in @@ -0,0 +1,28 @@ +# 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. + +CC = @CC@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +DEFS = @DEFS@ + +perftcpdns: perftcpdns.o $(LIBOBJS) + $(CC) $(CFLAGS) $(DEFS) $(LDFLAGS) perftcpdns.o $(LIBOBJS) $(LIBS) -o perftcpdns + +perftcpdns.o: perftcpdns.c + $(CC) $(CFLAGS) $(DEFS) -c perftcpdns.c + +clean: + rm -f *.o perftcpdns + +distclean: clean + rm -f config.log + rm -f config.cache + rm -f config.status + rm -f Makefile diff --git a/contrib/perftcpdns/configure b/contrib/perftcpdns/configure new file mode 100755 index 0000000..2be15f8 --- /dev/null +++ b/contrib/perftcpdns/configure @@ -0,0 +1,4342 @@ +#! /bin/sh +# Guess values for system-dependent variables and create Makefiles. +# Generated by GNU Autoconf 2.69. +# +# +# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. +# +# +# This configure script is free software; the Free Software Foundation +# gives unlimited permission to copy, distribute and modify it. +## -------------------- ## +## M4sh Initialization. ## +## -------------------- ## + +# Be more Bourne compatible +DUALCASE=1; export DUALCASE # for MKS sh +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi + + +as_nl=' +' +export as_nl +# Printing a long string crashes Solaris 7 /usr/bin/printf. +as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo +# Prefer a ksh shell builtin over an external printf program on Solaris, +# but without wasting forks for bash or zsh. +if test -z "$BASH_VERSION$ZSH_VERSION" \ + && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='print -r --' + as_echo_n='print -rn --' +elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='printf %s\n' + as_echo_n='printf %s' +else + if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then + as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' + as_echo_n='/usr/ucb/echo -n' + else + as_echo_body='eval expr "X$1" : "X\\(.*\\)"' + as_echo_n_body='eval + arg=$1; + case $arg in #( + *"$as_nl"*) + expr "X$arg" : "X\\(.*\\)$as_nl"; + arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; + esac; + expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" + ' + export as_echo_n_body + as_echo_n='sh -c $as_echo_n_body as_echo' + fi + export as_echo_body + as_echo='sh -c $as_echo_body as_echo' +fi + +# The user is always right. +if test "${PATH_SEPARATOR+set}" != set; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + +# IFS +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent editors from complaining about space-tab. +# (If _AS_PATH_WALK were called with IFS unset, it would disable word +# splitting by setting IFS to empty value.) +IFS=" "" $as_nl" + +# Find who we are. Look in the path if we contain no directory separator. +as_myself= +case $0 in #(( + *[\\/]* ) as_myself=$0 ;; + *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break + done +IFS=$as_save_IFS + + ;; +esac +# We did not find ourselves, most probably we were run as `sh COMMAND' +# in which case we are not to be found in the path. +if test "x$as_myself" = x; then + as_myself=$0 +fi +if test ! -f "$as_myself"; then + $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + exit 1 +fi + +# Unset variables that we do not need and which cause bugs (e.g. in +# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" +# suppresses any "Segmentation fault" message there. '((' could +# trigger a bug in pdksh 5.2.14. +for as_var in BASH_ENV ENV MAIL MAILPATH +do eval test x\${$as_var+set} = xset \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done +PS1='$ ' +PS2='> ' +PS4='+ ' + +# NLS nuisances. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# CDPATH. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +# Use a proper internal environment variable to ensure we don't fall + # into an infinite loop, continuously re-executing ourselves. + if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then + _as_can_reexec=no; export _as_can_reexec; + # We cannot yet assume a decent shell, so we have to provide a +# neutralization value for shells without unset; and this also +# works around shells that cannot unset nonexistent variables. +# Preserve -v and -x to the replacement shell. +BASH_ENV=/dev/null +ENV=/dev/null +(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV +case $- in # (((( + *v*x* | *x*v* ) as_opts=-vx ;; + *v* ) as_opts=-v ;; + *x* ) as_opts=-x ;; + * ) as_opts= ;; +esac +exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} +# Admittedly, this is quite paranoid, since all the known shells bail +# out after a failed `exec'. +$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 +as_fn_exit 255 + fi + # We don't want this to propagate to other subprocesses. + { _as_can_reexec=; unset _as_can_reexec;} +if test "x$CONFIG_SHELL" = x; then + as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on \${1+\"\$@\"}, which + # is contrary to our usage. Disable this feature. + alias -g '\${1+\"\$@\"}'='\"\$@\"' + setopt NO_GLOB_SUBST +else + case \`(set -o) 2>/dev/null\` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi +" + as_required="as_fn_return () { (exit \$1); } +as_fn_success () { as_fn_return 0; } +as_fn_failure () { as_fn_return 1; } +as_fn_ret_success () { return 0; } +as_fn_ret_failure () { return 1; } + +exitcode=0 +as_fn_success || { exitcode=1; echo as_fn_success failed.; } +as_fn_failure && { exitcode=1; echo as_fn_failure succeeded.; } +as_fn_ret_success || { exitcode=1; echo as_fn_ret_success failed.; } +as_fn_ret_failure && { exitcode=1; echo as_fn_ret_failure succeeded.; } +if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then : + +else + exitcode=1; echo positional parameters were not saved. +fi +test x\$exitcode = x0 || exit 1 +test -x / || exit 1" + as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO + as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO + eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && + test \"x\`expr \$as_lineno_1'\$as_run' + 1\`\" = \"x\$as_lineno_2'\$as_run'\"' || exit 1" + if (eval "$as_required") 2>/dev/null; then : + as_have_required=yes +else + as_have_required=no +fi + if test x$as_have_required = xyes && (eval "$as_suggested") 2>/dev/null; then : + +else + as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +as_found=false +for as_dir in /bin$PATH_SEPARATOR/usr/bin$PATH_SEPARATOR$PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + as_found=: + case $as_dir in #( + /*) + for as_base in sh bash ksh sh5; do + # Try only shells that exist, to save several forks. + as_shell=$as_dir/$as_base + if { test -f "$as_shell" || test -f "$as_shell.exe"; } && + { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$as_shell"; } 2>/dev/null; then : + CONFIG_SHELL=$as_shell as_have_required=yes + if { $as_echo "$as_bourne_compatible""$as_suggested" | as_run=a "$as_shell"; } 2>/dev/null; then : + break 2 +fi +fi + done;; + esac + as_found=false +done +$as_found || { if { test -f "$SHELL" || test -f "$SHELL.exe"; } && + { $as_echo "$as_bourne_compatible""$as_required" | as_run=a "$SHELL"; } 2>/dev/null; then : + CONFIG_SHELL=$SHELL as_have_required=yes +fi; } +IFS=$as_save_IFS + + + if test "x$CONFIG_SHELL" != x; then : + export CONFIG_SHELL + # We cannot yet assume a decent shell, so we have to provide a +# neutralization value for shells without unset; and this also +# works around shells that cannot unset nonexistent variables. +# Preserve -v and -x to the replacement shell. +BASH_ENV=/dev/null +ENV=/dev/null +(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV +case $- in # (((( + *v*x* | *x*v* ) as_opts=-vx ;; + *v* ) as_opts=-v ;; + *x* ) as_opts=-x ;; + * ) as_opts= ;; +esac +exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} +# Admittedly, this is quite paranoid, since all the known shells bail +# out after a failed `exec'. +$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 +exit 255 +fi + + if test x$as_have_required = xno; then : + $as_echo "$0: This script requires a shell more modern than all" + $as_echo "$0: the shells that I found on your system." + if test x${ZSH_VERSION+set} = xset ; then + $as_echo "$0: In particular, zsh $ZSH_VERSION has bugs and should" + $as_echo "$0: be upgraded to zsh 4.3.4 or later." + else + $as_echo "$0: Please tell bug-autoconf@gnu.org about your system, +$0: including any error possibly output before this +$0: message. Then install a modern shell, or manually run +$0: the script under such a shell if you do have one." + fi + exit 1 +fi +fi +fi +SHELL=${CONFIG_SHELL-/bin/sh} +export SHELL +# Unset more variables known to interfere with behavior of common tools. +CLICOLOR_FORCE= GREP_OPTIONS= +unset CLICOLOR_FORCE GREP_OPTIONS + +## --------------------- ## +## M4sh Shell Functions. ## +## --------------------- ## +# as_fn_unset VAR +# --------------- +# Portably unset VAR. +as_fn_unset () +{ + { eval $1=; unset $1;} +} +as_unset=as_fn_unset + +# as_fn_set_status STATUS +# ----------------------- +# Set $? to STATUS, without forking. +as_fn_set_status () +{ + return $1 +} # as_fn_set_status + +# as_fn_exit STATUS +# ----------------- +# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. +as_fn_exit () +{ + set +e + as_fn_set_status $1 + exit $1 +} # as_fn_exit + +# as_fn_mkdir_p +# ------------- +# Create "$as_dir" as a directory, including parents if necessary. +as_fn_mkdir_p () +{ + + case $as_dir in #( + -*) as_dir=./$as_dir;; + esac + test -d "$as_dir" || eval $as_mkdir_p || { + as_dirs= + while :; do + case $as_dir in #( + *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *) as_qdir=$as_dir;; + esac + as_dirs="'$as_qdir' $as_dirs" + as_dir=`$as_dirname -- "$as_dir" || +$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_dir" : 'X\(//\)[^/]' \| \ + X"$as_dir" : 'X\(//\)$' \| \ + X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_dir" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + test -d "$as_dir" && break + done + test -z "$as_dirs" || eval "mkdir $as_dirs" + } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" + + +} # as_fn_mkdir_p + +# as_fn_executable_p FILE +# ----------------------- +# Test if FILE is an executable regular file. +as_fn_executable_p () +{ + test -f "$1" && test -x "$1" +} # as_fn_executable_p +# as_fn_append VAR VALUE +# ---------------------- +# Append the text in VALUE to the end of the definition contained in VAR. Take +# advantage of any shell optimizations that allow amortized linear growth over +# repeated appends, instead of the typical quadratic growth present in naive +# implementations. +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : + eval 'as_fn_append () + { + eval $1+=\$2 + }' +else + as_fn_append () + { + eval $1=\$$1\$2 + } +fi # as_fn_append + +# as_fn_arith ARG... +# ------------------ +# Perform arithmetic evaluation on the ARGs, and store the result in the +# global $as_val. Take advantage of shells that can avoid forks. The arguments +# must be portable across $(()) and expr. +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : + eval 'as_fn_arith () + { + as_val=$(( $* )) + }' +else + as_fn_arith () + { + as_val=`expr "$@" || test $? -eq 1` + } +fi # as_fn_arith + + +# as_fn_error STATUS ERROR [LINENO LOG_FD] +# ---------------------------------------- +# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are +# provided, also output the error to LOG_FD, referencing LINENO. Then exit the +# script with STATUS, using 1 if that was 0. +as_fn_error () +{ + as_status=$1; test $as_status -eq 0 && as_status=1 + if test "$4"; then + as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + fi + $as_echo "$as_me: error: $2" >&2 + as_fn_exit $as_status +} # as_fn_error + +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr +else + as_expr=false +fi + +if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then + as_basename=basename +else + as_basename=false +fi + +if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then + as_dirname=dirname +else + as_dirname=false +fi + +as_me=`$as_basename -- "$0" || +$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ + X"$0" : 'X\(//\)$' \| \ + X"$0" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X/"$0" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + +# Avoid depending upon Character Ranges. +as_cr_letters='abcdefghijklmnopqrstuvwxyz' +as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +as_cr_Letters=$as_cr_letters$as_cr_LETTERS +as_cr_digits='0123456789' +as_cr_alnum=$as_cr_Letters$as_cr_digits + + + as_lineno_1=$LINENO as_lineno_1a=$LINENO + as_lineno_2=$LINENO as_lineno_2a=$LINENO + eval 'test "x$as_lineno_1'$as_run'" != "x$as_lineno_2'$as_run'" && + test "x`expr $as_lineno_1'$as_run' + 1`" = "x$as_lineno_2'$as_run'"' || { + # Blame Lee E. McMahon (1931-1989) for sed's syntax. :-) + sed -n ' + p + /[$]LINENO/= + ' <$as_myself | + sed ' + s/[$]LINENO.*/&-/ + t lineno + b + :lineno + N + :loop + s/[$]LINENO\([^'$as_cr_alnum'_].*\n\)\(.*\)/\2\1\2/ + t loop + s/-\n.*// + ' >$as_me.lineno && + chmod +x "$as_me.lineno" || + { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } + + # If we had to re-execute with $CONFIG_SHELL, we're ensured to have + # already done that, so ensure we don't try to do so again and fall + # in an infinite loop. This has already happened in practice. + _as_can_reexec=no; export _as_can_reexec + # Don't try to exec as it changes $[0], causing all sort of problems + # (the dirname of $[0] is not the place where we might find the + # original and so on. Autoconf is especially sensitive to this). + . "./$as_me.lineno" + # Exit status is that of the last command. + exit +} + +ECHO_C= ECHO_N= ECHO_T= +case `echo -n x` in #((((( +-n*) + case `echo 'xy\c'` in + *c*) ECHO_T=' ';; # ECHO_T is single tab character. + xy) ECHO_C='\c';; + *) echo `echo ksh88 bug on AIX 6.1` > /dev/null + ECHO_T=' ';; + esac;; +*) + ECHO_N='-n';; +esac + +rm -f conf$$ conf$$.exe conf$$.file +if test -d conf$$.dir; then + rm -f conf$$.dir/conf$$.file +else + rm -f conf$$.dir + mkdir conf$$.dir 2>/dev/null +fi +if (echo >conf$$.file) 2>/dev/null; then + if ln -s conf$$.file conf$$ 2>/dev/null; then + as_ln_s='ln -s' + # ... but there are two gotchas: + # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. + # In both cases, we have to default to `cp -pR'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + as_ln_s='cp -pR' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else + as_ln_s='cp -pR' + fi +else + as_ln_s='cp -pR' +fi +rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file +rmdir conf$$.dir 2>/dev/null + +if mkdir -p . 2>/dev/null; then + as_mkdir_p='mkdir -p "$as_dir"' +else + test -d ./-p && rmdir ./-p + as_mkdir_p=false +fi + +as_test_x='test -x' +as_executable_p=as_fn_executable_p + +# Sed expression to map a string onto a valid CPP name. +as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" + +# Sed expression to map a string onto a valid variable name. +as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" + + +test -n "$DJDIR" || exec 7<&0 </dev/null +exec 6>&1 + +# Name of the host. +# hostname on some systems (SVR3.2, old GNU/Linux) returns a bogus exit status, +# so uname gets run too. +ac_hostname=`(hostname || uname -n) 2>/dev/null | sed 1q` + +# +# Initializations. +# +ac_default_prefix=/usr/local +ac_clean_files= +ac_config_libobj_dir=. +LIBOBJS= +cross_compiling=no +subdirs= +MFLAGS= +MAKEFLAGS= + +# Identity of this package. +PACKAGE_NAME= +PACKAGE_TARNAME= +PACKAGE_VERSION= +PACKAGE_STRING= +PACKAGE_BUGREPORT= +PACKAGE_URL= + +ac_unique_file="perftcpdns.c" +ac_subst_vars='LTLIBOBJS +LIBOBJS +OBJEXT +EXEEXT +ac_ct_CC +CPPFLAGS +LDFLAGS +CFLAGS +CC +target_alias +host_alias +build_alias +LIBS +ECHO_T +ECHO_N +ECHO_C +DEFS +mandir +localedir +libdir +psdir +pdfdir +dvidir +htmldir +infodir +docdir +oldincludedir +includedir +localstatedir +sharedstatedir +sysconfdir +datadir +datarootdir +libexecdir +sbindir +bindir +program_transform_name +prefix +exec_prefix +PACKAGE_URL +PACKAGE_BUGREPORT +PACKAGE_STRING +PACKAGE_VERSION +PACKAGE_TARNAME +PACKAGE_NAME +PATH_SEPARATOR +SHELL' +ac_subst_files='' +ac_user_opts=' +enable_option_checking +' + ac_precious_vars='build_alias +host_alias +target_alias +CC +CFLAGS +LDFLAGS +LIBS +CPPFLAGS' + + +# Initialize some variables set by options. +ac_init_help= +ac_init_version=false +ac_unrecognized_opts= +ac_unrecognized_sep= +# The variables have the same names as the options, with +# dashes changed to underlines. +cache_file=/dev/null +exec_prefix=NONE +no_create= +no_recursion= +prefix=NONE +program_prefix=NONE +program_suffix=NONE +program_transform_name=s,x,x, +silent= +site= +srcdir= +verbose= +x_includes=NONE +x_libraries=NONE + +# Installation directory options. +# These are left unexpanded so users can "make install exec_prefix=/foo" +# and all the variables that are supposed to be based on exec_prefix +# by default will actually change. +# Use braces instead of parens because sh, perl, etc. also accept them. +# (The list follows the same order as the GNU Coding Standards.) +bindir='${exec_prefix}/bin' +sbindir='${exec_prefix}/sbin' +libexecdir='${exec_prefix}/libexec' +datarootdir='${prefix}/share' +datadir='${datarootdir}' +sysconfdir='${prefix}/etc' +sharedstatedir='${prefix}/com' +localstatedir='${prefix}/var' +includedir='${prefix}/include' +oldincludedir='/usr/include' +docdir='${datarootdir}/doc/${PACKAGE}' +infodir='${datarootdir}/info' +htmldir='${docdir}' +dvidir='${docdir}' +pdfdir='${docdir}' +psdir='${docdir}' +libdir='${exec_prefix}/lib' +localedir='${datarootdir}/locale' +mandir='${datarootdir}/man' + +ac_prev= +ac_dashdash= +for ac_option +do + # If the previous option needs an argument, assign it. + if test -n "$ac_prev"; then + eval $ac_prev=\$ac_option + ac_prev= + continue + fi + + case $ac_option in + *=?*) ac_optarg=`expr "X$ac_option" : '[^=]*=\(.*\)'` ;; + *=) ac_optarg= ;; + *) ac_optarg=yes ;; + esac + + # Accept the important Cygnus configure options, so we can diagnose typos. + + case $ac_dashdash$ac_option in + --) + ac_dashdash=yes ;; + + -bindir | --bindir | --bindi | --bind | --bin | --bi) + ac_prev=bindir ;; + -bindir=* | --bindir=* | --bindi=* | --bind=* | --bin=* | --bi=*) + bindir=$ac_optarg ;; + + -build | --build | --buil | --bui | --bu) + ac_prev=build_alias ;; + -build=* | --build=* | --buil=* | --bui=* | --bu=*) + build_alias=$ac_optarg ;; + + -cache-file | --cache-file | --cache-fil | --cache-fi \ + | --cache-f | --cache- | --cache | --cach | --cac | --ca | --c) + ac_prev=cache_file ;; + -cache-file=* | --cache-file=* | --cache-fil=* | --cache-fi=* \ + | --cache-f=* | --cache-=* | --cache=* | --cach=* | --cac=* | --ca=* | --c=*) + cache_file=$ac_optarg ;; + + --config-cache | -C) + cache_file=config.cache ;; + + -datadir | --datadir | --datadi | --datad) + ac_prev=datadir ;; + -datadir=* | --datadir=* | --datadi=* | --datad=*) + datadir=$ac_optarg ;; + + -datarootdir | --datarootdir | --datarootdi | --datarootd | --dataroot \ + | --dataroo | --dataro | --datar) + ac_prev=datarootdir ;; + -datarootdir=* | --datarootdir=* | --datarootdi=* | --datarootd=* \ + | --dataroot=* | --dataroo=* | --dataro=* | --datar=*) + datarootdir=$ac_optarg ;; + + -disable-* | --disable-*) + ac_useropt=`expr "x$ac_option" : 'x-*disable-\(.*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid feature name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"enable_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--disable-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval enable_$ac_useropt=no ;; + + -docdir | --docdir | --docdi | --doc | --do) + ac_prev=docdir ;; + -docdir=* | --docdir=* | --docdi=* | --doc=* | --do=*) + docdir=$ac_optarg ;; + + -dvidir | --dvidir | --dvidi | --dvid | --dvi | --dv) + ac_prev=dvidir ;; + -dvidir=* | --dvidir=* | --dvidi=* | --dvid=* | --dvi=* | --dv=*) + dvidir=$ac_optarg ;; + + -enable-* | --enable-*) + ac_useropt=`expr "x$ac_option" : 'x-*enable-\([^=]*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid feature name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"enable_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--enable-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval enable_$ac_useropt=\$ac_optarg ;; + + -exec-prefix | --exec_prefix | --exec-prefix | --exec-prefi \ + | --exec-pref | --exec-pre | --exec-pr | --exec-p | --exec- \ + | --exec | --exe | --ex) + ac_prev=exec_prefix ;; + -exec-prefix=* | --exec_prefix=* | --exec-prefix=* | --exec-prefi=* \ + | --exec-pref=* | --exec-pre=* | --exec-pr=* | --exec-p=* | --exec-=* \ + | --exec=* | --exe=* | --ex=*) + exec_prefix=$ac_optarg ;; + + -gas | --gas | --ga | --g) + # Obsolete; use --with-gas. + with_gas=yes ;; + + -help | --help | --hel | --he | -h) + ac_init_help=long ;; + -help=r* | --help=r* | --hel=r* | --he=r* | -hr*) + ac_init_help=recursive ;; + -help=s* | --help=s* | --hel=s* | --he=s* | -hs*) + ac_init_help=short ;; + + -host | --host | --hos | --ho) + ac_prev=host_alias ;; + -host=* | --host=* | --hos=* | --ho=*) + host_alias=$ac_optarg ;; + + -htmldir | --htmldir | --htmldi | --htmld | --html | --htm | --ht) + ac_prev=htmldir ;; + -htmldir=* | --htmldir=* | --htmldi=* | --htmld=* | --html=* | --htm=* \ + | --ht=*) + htmldir=$ac_optarg ;; + + -includedir | --includedir | --includedi | --included | --include \ + | --includ | --inclu | --incl | --inc) + ac_prev=includedir ;; + -includedir=* | --includedir=* | --includedi=* | --included=* | --include=* \ + | --includ=* | --inclu=* | --incl=* | --inc=*) + includedir=$ac_optarg ;; + + -infodir | --infodir | --infodi | --infod | --info | --inf) + ac_prev=infodir ;; + -infodir=* | --infodir=* | --infodi=* | --infod=* | --info=* | --inf=*) + infodir=$ac_optarg ;; + + -libdir | --libdir | --libdi | --libd) + ac_prev=libdir ;; + -libdir=* | --libdir=* | --libdi=* | --libd=*) + libdir=$ac_optarg ;; + + -libexecdir | --libexecdir | --libexecdi | --libexecd | --libexec \ + | --libexe | --libex | --libe) + ac_prev=libexecdir ;; + -libexecdir=* | --libexecdir=* | --libexecdi=* | --libexecd=* | --libexec=* \ + | --libexe=* | --libex=* | --libe=*) + libexecdir=$ac_optarg ;; + + -localedir | --localedir | --localedi | --localed | --locale) + ac_prev=localedir ;; + -localedir=* | --localedir=* | --localedi=* | --localed=* | --locale=*) + localedir=$ac_optarg ;; + + -localstatedir | --localstatedir | --localstatedi | --localstated \ + | --localstate | --localstat | --localsta | --localst | --locals) + ac_prev=localstatedir ;; + -localstatedir=* | --localstatedir=* | --localstatedi=* | --localstated=* \ + | --localstate=* | --localstat=* | --localsta=* | --localst=* | --locals=*) + localstatedir=$ac_optarg ;; + + -mandir | --mandir | --mandi | --mand | --man | --ma | --m) + ac_prev=mandir ;; + -mandir=* | --mandir=* | --mandi=* | --mand=* | --man=* | --ma=* | --m=*) + mandir=$ac_optarg ;; + + -nfp | --nfp | --nf) + # Obsolete; use --without-fp. + with_fp=no ;; + + -no-create | --no-create | --no-creat | --no-crea | --no-cre \ + | --no-cr | --no-c | -n) + no_create=yes ;; + + -no-recursion | --no-recursion | --no-recursio | --no-recursi \ + | --no-recurs | --no-recur | --no-recu | --no-rec | --no-re | --no-r) + no_recursion=yes ;; + + -oldincludedir | --oldincludedir | --oldincludedi | --oldincluded \ + | --oldinclude | --oldinclud | --oldinclu | --oldincl | --oldinc \ + | --oldin | --oldi | --old | --ol | --o) + ac_prev=oldincludedir ;; + -oldincludedir=* | --oldincludedir=* | --oldincludedi=* | --oldincluded=* \ + | --oldinclude=* | --oldinclud=* | --oldinclu=* | --oldincl=* | --oldinc=* \ + | --oldin=* | --oldi=* | --old=* | --ol=* | --o=*) + oldincludedir=$ac_optarg ;; + + -prefix | --prefix | --prefi | --pref | --pre | --pr | --p) + ac_prev=prefix ;; + -prefix=* | --prefix=* | --prefi=* | --pref=* | --pre=* | --pr=* | --p=*) + prefix=$ac_optarg ;; + + -program-prefix | --program-prefix | --program-prefi | --program-pref \ + | --program-pre | --program-pr | --program-p) + ac_prev=program_prefix ;; + -program-prefix=* | --program-prefix=* | --program-prefi=* \ + | --program-pref=* | --program-pre=* | --program-pr=* | --program-p=*) + program_prefix=$ac_optarg ;; + + -program-suffix | --program-suffix | --program-suffi | --program-suff \ + | --program-suf | --program-su | --program-s) + ac_prev=program_suffix ;; + -program-suffix=* | --program-suffix=* | --program-suffi=* \ + | --program-suff=* | --program-suf=* | --program-su=* | --program-s=*) + program_suffix=$ac_optarg ;; + + -program-transform-name | --program-transform-name \ + | --program-transform-nam | --program-transform-na \ + | --program-transform-n | --program-transform- \ + | --program-transform | --program-transfor \ + | --program-transfo | --program-transf \ + | --program-trans | --program-tran \ + | --progr-tra | --program-tr | --program-t) + ac_prev=program_transform_name ;; + -program-transform-name=* | --program-transform-name=* \ + | --program-transform-nam=* | --program-transform-na=* \ + | --program-transform-n=* | --program-transform-=* \ + | --program-transform=* | --program-transfor=* \ + | --program-transfo=* | --program-transf=* \ + | --program-trans=* | --program-tran=* \ + | --progr-tra=* | --program-tr=* | --program-t=*) + program_transform_name=$ac_optarg ;; + + -pdfdir | --pdfdir | --pdfdi | --pdfd | --pdf | --pd) + ac_prev=pdfdir ;; + -pdfdir=* | --pdfdir=* | --pdfdi=* | --pdfd=* | --pdf=* | --pd=*) + pdfdir=$ac_optarg ;; + + -psdir | --psdir | --psdi | --psd | --ps) + ac_prev=psdir ;; + -psdir=* | --psdir=* | --psdi=* | --psd=* | --ps=*) + psdir=$ac_optarg ;; + + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + silent=yes ;; + + -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) + ac_prev=sbindir ;; + -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ + | --sbi=* | --sb=*) + sbindir=$ac_optarg ;; + + -sharedstatedir | --sharedstatedir | --sharedstatedi \ + | --sharedstated | --sharedstate | --sharedstat | --sharedsta \ + | --sharedst | --shareds | --shared | --share | --shar \ + | --sha | --sh) + ac_prev=sharedstatedir ;; + -sharedstatedir=* | --sharedstatedir=* | --sharedstatedi=* \ + | --sharedstated=* | --sharedstate=* | --sharedstat=* | --sharedsta=* \ + | --sharedst=* | --shareds=* | --shared=* | --share=* | --shar=* \ + | --sha=* | --sh=*) + sharedstatedir=$ac_optarg ;; + + -site | --site | --sit) + ac_prev=site ;; + -site=* | --site=* | --sit=*) + site=$ac_optarg ;; + + -srcdir | --srcdir | --srcdi | --srcd | --src | --sr) + ac_prev=srcdir ;; + -srcdir=* | --srcdir=* | --srcdi=* | --srcd=* | --src=* | --sr=*) + srcdir=$ac_optarg ;; + + -sysconfdir | --sysconfdir | --sysconfdi | --sysconfd | --sysconf \ + | --syscon | --sysco | --sysc | --sys | --sy) + ac_prev=sysconfdir ;; + -sysconfdir=* | --sysconfdir=* | --sysconfdi=* | --sysconfd=* | --sysconf=* \ + | --syscon=* | --sysco=* | --sysc=* | --sys=* | --sy=*) + sysconfdir=$ac_optarg ;; + + -target | --target | --targe | --targ | --tar | --ta | --t) + ac_prev=target_alias ;; + -target=* | --target=* | --targe=* | --targ=* | --tar=* | --ta=* | --t=*) + target_alias=$ac_optarg ;; + + -v | -verbose | --verbose | --verbos | --verbo | --verb) + verbose=yes ;; + + -version | --version | --versio | --versi | --vers | -V) + ac_init_version=: ;; + + -with-* | --with-*) + ac_useropt=`expr "x$ac_option" : 'x-*with-\([^=]*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid package name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"with_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--with-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval with_$ac_useropt=\$ac_optarg ;; + + -without-* | --without-*) + ac_useropt=`expr "x$ac_option" : 'x-*without-\(.*\)'` + # Reject names that are not valid shell variable names. + expr "x$ac_useropt" : ".*[^-+._$as_cr_alnum]" >/dev/null && + as_fn_error $? "invalid package name: $ac_useropt" + ac_useropt_orig=$ac_useropt + ac_useropt=`$as_echo "$ac_useropt" | sed 's/[-+.]/_/g'` + case $ac_user_opts in + *" +"with_$ac_useropt" +"*) ;; + *) ac_unrecognized_opts="$ac_unrecognized_opts$ac_unrecognized_sep--without-$ac_useropt_orig" + ac_unrecognized_sep=', ';; + esac + eval with_$ac_useropt=no ;; + + --x) + # Obsolete; use --with-x. + with_x=yes ;; + + -x-includes | --x-includes | --x-include | --x-includ | --x-inclu \ + | --x-incl | --x-inc | --x-in | --x-i) + ac_prev=x_includes ;; + -x-includes=* | --x-includes=* | --x-include=* | --x-includ=* | --x-inclu=* \ + | --x-incl=* | --x-inc=* | --x-in=* | --x-i=*) + x_includes=$ac_optarg ;; + + -x-libraries | --x-libraries | --x-librarie | --x-librari \ + | --x-librar | --x-libra | --x-libr | --x-lib | --x-li | --x-l) + ac_prev=x_libraries ;; + -x-libraries=* | --x-libraries=* | --x-librarie=* | --x-librari=* \ + | --x-librar=* | --x-libra=* | --x-libr=* | --x-lib=* | --x-li=* | --x-l=*) + x_libraries=$ac_optarg ;; + + -*) as_fn_error $? "unrecognized option: \`$ac_option' +Try \`$0 --help' for more information" + ;; + + *=*) + ac_envvar=`expr "x$ac_option" : 'x\([^=]*\)='` + # Reject names that are not valid shell variable names. + case $ac_envvar in #( + '' | [0-9]* | *[!_$as_cr_alnum]* ) + as_fn_error $? "invalid variable name: \`$ac_envvar'" ;; + esac + eval $ac_envvar=\$ac_optarg + export $ac_envvar ;; + + *) + # FIXME: should be removed in autoconf 3.0. + $as_echo "$as_me: WARNING: you should use --build, --host, --target" >&2 + expr "x$ac_option" : ".*[^-._$as_cr_alnum]" >/dev/null && + $as_echo "$as_me: WARNING: invalid host type: $ac_option" >&2 + : "${build_alias=$ac_option} ${host_alias=$ac_option} ${target_alias=$ac_option}" + ;; + + esac +done + +if test -n "$ac_prev"; then + ac_option=--`echo $ac_prev | sed 's/_/-/g'` + as_fn_error $? "missing argument to $ac_option" +fi + +if test -n "$ac_unrecognized_opts"; then + case $enable_option_checking in + no) ;; + fatal) as_fn_error $? "unrecognized options: $ac_unrecognized_opts" ;; + *) $as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2 ;; + esac +fi + +# Check all directory arguments for consistency. +for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ + datadir sysconfdir sharedstatedir localstatedir includedir \ + oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ + libdir localedir mandir +do + eval ac_val=\$$ac_var + # Remove trailing slashes. + case $ac_val in + */ ) + ac_val=`expr "X$ac_val" : 'X\(.*[^/]\)' \| "X$ac_val" : 'X\(.*\)'` + eval $ac_var=\$ac_val;; + esac + # Be sure to have absolute directory names. + case $ac_val in + [\\/$]* | ?:[\\/]* ) continue;; + NONE | '' ) case $ac_var in *prefix ) continue;; esac;; + esac + as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" +done + +# There might be people who depend on the old broken behavior: `$host' +# used to hold the argument of --host etc. +# FIXME: To remove some day. +build=$build_alias +host=$host_alias +target=$target_alias + +# FIXME: To remove some day. +if test "x$host_alias" != x; then + if test "x$build_alias" = x; then + cross_compiling=maybe + elif test "x$build_alias" != "x$host_alias"; then + cross_compiling=yes + fi +fi + +ac_tool_prefix= +test -n "$host_alias" && ac_tool_prefix=$host_alias- + +test "$silent" = yes && exec 6>/dev/null + + +ac_pwd=`pwd` && test -n "$ac_pwd" && +ac_ls_di=`ls -di .` && +ac_pwd_ls_di=`cd "$ac_pwd" && ls -di .` || + as_fn_error $? "working directory cannot be determined" +test "X$ac_ls_di" = "X$ac_pwd_ls_di" || + as_fn_error $? "pwd does not report name of working directory" + + +# Find the source files, if location was not specified. +if test -z "$srcdir"; then + ac_srcdir_defaulted=yes + # Try the directory containing this script, then the parent directory. + ac_confdir=`$as_dirname -- "$as_myself" || +$as_expr X"$as_myself" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_myself" : 'X\(//\)[^/]' \| \ + X"$as_myself" : 'X\(//\)$' \| \ + X"$as_myself" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_myself" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + srcdir=$ac_confdir + if test ! -r "$srcdir/$ac_unique_file"; then + srcdir=.. + fi +else + ac_srcdir_defaulted=no +fi +if test ! -r "$srcdir/$ac_unique_file"; then + test "$ac_srcdir_defaulted" = yes && srcdir="$ac_confdir or .." + as_fn_error $? "cannot find sources ($ac_unique_file) in $srcdir" +fi +ac_msg="sources are in $srcdir, but \`cd $srcdir' does not work" +ac_abs_confdir=`( + cd "$srcdir" && test -r "./$ac_unique_file" || as_fn_error $? "$ac_msg" + pwd)` +# When building in place, set srcdir=. +if test "$ac_abs_confdir" = "$ac_pwd"; then + srcdir=. +fi +# Remove unnecessary trailing slashes from srcdir. +# Double slashes in file names in object file debugging info +# mess up M-x gdb in Emacs. +case $srcdir in +*/) srcdir=`expr "X$srcdir" : 'X\(.*[^/]\)' \| "X$srcdir" : 'X\(.*\)'`;; +esac +for ac_var in $ac_precious_vars; do + eval ac_env_${ac_var}_set=\${${ac_var}+set} + eval ac_env_${ac_var}_value=\$${ac_var} + eval ac_cv_env_${ac_var}_set=\${${ac_var}+set} + eval ac_cv_env_${ac_var}_value=\$${ac_var} +done + +# +# Report the --help message. +# +if test "$ac_init_help" = "long"; then + # Omit some internal or obsolete options to make the list less imposing. + # This message is too long to be a string in the A/UX 3.1 sh. + cat <<_ACEOF +\`configure' configures this package to adapt to many kinds of systems. + +Usage: $0 [OPTION]... [VAR=VALUE]... + +To assign environment variables (e.g., CC, CFLAGS...), specify them as +VAR=VALUE. See below for descriptions of some of the useful variables. + +Defaults for the options are specified in brackets. + +Configuration: + -h, --help display this help and exit + --help=short display options specific to this package + --help=recursive display the short help of all the included packages + -V, --version display version information and exit + -q, --quiet, --silent do not print \`checking ...' messages + --cache-file=FILE cache test results in FILE [disabled] + -C, --config-cache alias for \`--cache-file=config.cache' + -n, --no-create do not create output files + --srcdir=DIR find the sources in DIR [configure dir or \`..'] + +Installation directories: + --prefix=PREFIX install architecture-independent files in PREFIX + [$ac_default_prefix] + --exec-prefix=EPREFIX install architecture-dependent files in EPREFIX + [PREFIX] + +By default, \`make install' will install all the files in +\`$ac_default_prefix/bin', \`$ac_default_prefix/lib' etc. You can specify +an installation prefix other than \`$ac_default_prefix' using \`--prefix', +for instance \`--prefix=\$HOME'. + +For better control, use the options below. + +Fine tuning of the installation directories: + --bindir=DIR user executables [EPREFIX/bin] + --sbindir=DIR system admin executables [EPREFIX/sbin] + --libexecdir=DIR program executables [EPREFIX/libexec] + --sysconfdir=DIR read-only single-machine data [PREFIX/etc] + --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] + --localstatedir=DIR modifiable single-machine data [PREFIX/var] + --libdir=DIR object code libraries [EPREFIX/lib] + --includedir=DIR C header files [PREFIX/include] + --oldincludedir=DIR C header files for non-gcc [/usr/include] + --datarootdir=DIR read-only arch.-independent data root [PREFIX/share] + --datadir=DIR read-only architecture-independent data [DATAROOTDIR] + --infodir=DIR info documentation [DATAROOTDIR/info] + --localedir=DIR locale-dependent data [DATAROOTDIR/locale] + --mandir=DIR man documentation [DATAROOTDIR/man] + --docdir=DIR documentation root [DATAROOTDIR/doc/PACKAGE] + --htmldir=DIR html documentation [DOCDIR] + --dvidir=DIR dvi documentation [DOCDIR] + --pdfdir=DIR pdf documentation [DOCDIR] + --psdir=DIR ps documentation [DOCDIR] +_ACEOF + + cat <<\_ACEOF +_ACEOF +fi + +if test -n "$ac_init_help"; then + + cat <<\_ACEOF + +Some influential environment variables: + CC C compiler command + CFLAGS C compiler flags + LDFLAGS linker flags, e.g. -L<lib dir> if you have libraries in a + nonstandard directory <lib dir> + LIBS libraries to pass to the linker, e.g. -l<library> + CPPFLAGS (Objective) C/C++ preprocessor flags, e.g. -I<include dir> if + you have headers in a nonstandard directory <include dir> + +Use these variables to override the choices made by `configure' or to help +it to find libraries and programs with nonstandard names/locations. + +Report bugs to the package provider. +_ACEOF +ac_status=$? +fi + +if test "$ac_init_help" = "recursive"; then + # If there are subdirs, report their specific --help. + for ac_dir in : $ac_subdirs_all; do test "x$ac_dir" = x: && continue + test -d "$ac_dir" || + { cd "$srcdir" && ac_pwd=`pwd` && srcdir=. && test -d "$ac_dir"; } || + continue + ac_builddir=. + +case "$ac_dir" in +.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; +*) + ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` + # A ".." for each directory in $ac_dir_suffix. + ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + case $ac_top_builddir_sub in + "") ac_top_builddir_sub=. ac_top_build_prefix= ;; + *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; + esac ;; +esac +ac_abs_top_builddir=$ac_pwd +ac_abs_builddir=$ac_pwd$ac_dir_suffix +# for backward compatibility: +ac_top_builddir=$ac_top_build_prefix + +case $srcdir in + .) # We are building in place. + ac_srcdir=. + ac_top_srcdir=$ac_top_builddir_sub + ac_abs_top_srcdir=$ac_pwd ;; + [\\/]* | ?:[\\/]* ) # Absolute name. + ac_srcdir=$srcdir$ac_dir_suffix; + ac_top_srcdir=$srcdir + ac_abs_top_srcdir=$srcdir ;; + *) # Relative name. + ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix + ac_top_srcdir=$ac_top_build_prefix$srcdir + ac_abs_top_srcdir=$ac_pwd/$srcdir ;; +esac +ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix + + cd "$ac_dir" || { ac_status=$?; continue; } + # Check for guested configure. + if test -f "$ac_srcdir/configure.gnu"; then + echo && + $SHELL "$ac_srcdir/configure.gnu" --help=recursive + elif test -f "$ac_srcdir/configure"; then + echo && + $SHELL "$ac_srcdir/configure" --help=recursive + else + $as_echo "$as_me: WARNING: no configuration information is in $ac_dir" >&2 + fi || ac_status=$? + cd "$ac_pwd" || { ac_status=$?; break; } + done +fi + +test -n "$ac_init_help" && exit $ac_status +if $ac_init_version; then + cat <<\_ACEOF +configure +generated by GNU Autoconf 2.69 + +Copyright (C) 2012 Free Software Foundation, Inc. +This configure script is free software; the Free Software Foundation +gives unlimited permission to copy, distribute and modify it. +_ACEOF + exit +fi + +## ------------------------ ## +## Autoconf initialization. ## +## ------------------------ ## + +# ac_fn_c_try_compile LINENO +# -------------------------- +# Try to compile conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_compile () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext + if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest.$ac_objext; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_compile + +# ac_fn_c_try_link LINENO +# ----------------------- +# Try to link conftest.$ac_ext, and return whether this succeeded. +ac_fn_c_try_link () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + rm -f conftest.$ac_objext conftest$ac_exeext + if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + grep -v '^ *+' conftest.err >conftest.er1 + cat conftest.er1 >&5 + mv -f conftest.er1 conftest.err + fi + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { + test -z "$ac_c_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || + test -x conftest$ac_exeext + }; then : + ac_retval=0 +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=1 +fi + # Delete the IPA/IPO (Inter Procedural Analysis/Optimization) information + # created by the PGI compiler (conftest_ipa8_conftest.oo), as it would + # interfere with the next link command; also delete a directory that is + # left behind by Apple's compiler. We do this before executing the actions. + rm -rf conftest.dSYM conftest_ipa8_conftest.oo + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_link + +# ac_fn_c_try_run LINENO +# ---------------------- +# Try to link conftest.$ac_ext, and return whether this succeeded. Assumes +# that executables *can* be run. +ac_fn_c_try_run () +{ + as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && { ac_try='./conftest$ac_exeext' + { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; }; then : + ac_retval=0 +else + $as_echo "$as_me: program exited with status $ac_status" >&5 + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + ac_retval=$ac_status +fi + rm -rf conftest.dSYM conftest_ipa8_conftest.oo + eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno + as_fn_set_status $ac_retval + +} # ac_fn_c_try_run +cat >config.log <<_ACEOF +This file contains any messages produced by compilers while +running configure, to aid debugging if configure makes a mistake. + +It was created by $as_me, which was +generated by GNU Autoconf 2.69. Invocation command line was + + $ $0 $@ + +_ACEOF +exec 5>>config.log +{ +cat <<_ASUNAME +## --------- ## +## Platform. ## +## --------- ## + +hostname = `(hostname || uname -n) 2>/dev/null | sed 1q` +uname -m = `(uname -m) 2>/dev/null || echo unknown` +uname -r = `(uname -r) 2>/dev/null || echo unknown` +uname -s = `(uname -s) 2>/dev/null || echo unknown` +uname -v = `(uname -v) 2>/dev/null || echo unknown` + +/usr/bin/uname -p = `(/usr/bin/uname -p) 2>/dev/null || echo unknown` +/bin/uname -X = `(/bin/uname -X) 2>/dev/null || echo unknown` + +/bin/arch = `(/bin/arch) 2>/dev/null || echo unknown` +/usr/bin/arch -k = `(/usr/bin/arch -k) 2>/dev/null || echo unknown` +/usr/convex/getsysinfo = `(/usr/convex/getsysinfo) 2>/dev/null || echo unknown` +/usr/bin/hostinfo = `(/usr/bin/hostinfo) 2>/dev/null || echo unknown` +/bin/machine = `(/bin/machine) 2>/dev/null || echo unknown` +/usr/bin/oslevel = `(/usr/bin/oslevel) 2>/dev/null || echo unknown` +/bin/universe = `(/bin/universe) 2>/dev/null || echo unknown` + +_ASUNAME + +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + $as_echo "PATH: $as_dir" + done +IFS=$as_save_IFS + +} >&5 + +cat >&5 <<_ACEOF + + +## ----------- ## +## Core tests. ## +## ----------- ## + +_ACEOF + + +# Keep a trace of the command line. +# Strip out --no-create and --no-recursion so they do not pile up. +# Strip out --silent because we don't want to record it for future runs. +# Also quote any args containing shell meta-characters. +# Make two passes to allow for proper duplicate-argument suppression. +ac_configure_args= +ac_configure_args0= +ac_configure_args1= +ac_must_keep_next=false +for ac_pass in 1 2 +do + for ac_arg + do + case $ac_arg in + -no-create | --no-c* | -n | -no-recursion | --no-r*) continue ;; + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil) + continue ;; + *\'*) + ac_arg=`$as_echo "$ac_arg" | sed "s/'/'\\\\\\\\''/g"` ;; + esac + case $ac_pass in + 1) as_fn_append ac_configure_args0 " '$ac_arg'" ;; + 2) + as_fn_append ac_configure_args1 " '$ac_arg'" + if test $ac_must_keep_next = true; then + ac_must_keep_next=false # Got value, back to normal. + else + case $ac_arg in + *=* | --config-cache | -C | -disable-* | --disable-* \ + | -enable-* | --enable-* | -gas | --g* | -nfp | --nf* \ + | -q | -quiet | --q* | -silent | --sil* | -v | -verb* \ + | -with-* | --with-* | -without-* | --without-* | --x) + case "$ac_configure_args0 " in + "$ac_configure_args1"*" '$ac_arg' "* ) continue ;; + esac + ;; + -* ) ac_must_keep_next=true ;; + esac + fi + as_fn_append ac_configure_args " '$ac_arg'" + ;; + esac + done +done +{ ac_configure_args0=; unset ac_configure_args0;} +{ ac_configure_args1=; unset ac_configure_args1;} + +# When interrupted or exit'd, cleanup temporary files, and complete +# config.log. We remove comments because anyway the quotes in there +# would cause problems or look ugly. +# WARNING: Use '\'' to represent an apostrophe within the trap. +# WARNING: Do not start the trap code with a newline, due to a FreeBSD 4.0 bug. +trap 'exit_status=$? + # Save into config.log some information that might help in debugging. + { + echo + + $as_echo "## ---------------- ## +## Cache variables. ## +## ---------------- ##" + echo + # The following way of writing the cache mishandles newlines in values, +( + for ac_var in `(set) 2>&1 | sed -n '\''s/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'\''`; do + eval ac_val=\$$ac_var + case $ac_val in #( + *${as_nl}*) + case $ac_var in #( + *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + esac + case $ac_var in #( + _ | IFS | as_nl) ;; #( + BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( + *) { eval $ac_var=; unset $ac_var;} ;; + esac ;; + esac + done + (set) 2>&1 | + case $as_nl`(ac_space='\'' '\''; set) 2>&1` in #( + *${as_nl}ac_space=\ *) + sed -n \ + "s/'\''/'\''\\\\'\'''\''/g; + s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\''\\2'\''/p" + ;; #( + *) + sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" + ;; + esac | + sort +) + echo + + $as_echo "## ----------------- ## +## Output variables. ## +## ----------------- ##" + echo + for ac_var in $ac_subst_vars + do + eval ac_val=\$$ac_var + case $ac_val in + *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + esac + $as_echo "$ac_var='\''$ac_val'\''" + done | sort + echo + + if test -n "$ac_subst_files"; then + $as_echo "## ------------------- ## +## File substitutions. ## +## ------------------- ##" + echo + for ac_var in $ac_subst_files + do + eval ac_val=\$$ac_var + case $ac_val in + *\'\''*) ac_val=`$as_echo "$ac_val" | sed "s/'\''/'\''\\\\\\\\'\'''\''/g"`;; + esac + $as_echo "$ac_var='\''$ac_val'\''" + done | sort + echo + fi + + if test -s confdefs.h; then + $as_echo "## ----------- ## +## confdefs.h. ## +## ----------- ##" + echo + cat confdefs.h + echo + fi + test "$ac_signal" != 0 && + $as_echo "$as_me: caught signal $ac_signal" + $as_echo "$as_me: exit $exit_status" + } >&5 + rm -f core *.core core.conftest.* && + rm -f -r conftest* confdefs* conf$$* $ac_clean_files && + exit $exit_status +' 0 +for ac_signal in 1 2 13 15; do + trap 'ac_signal='$ac_signal'; as_fn_exit 1' $ac_signal +done +ac_signal=0 + +# confdefs.h avoids OS command line length limits that DEFS can exceed. +rm -f -r conftest* confdefs.h + +$as_echo "/* confdefs.h */" > confdefs.h + +# Predefined preprocessor variables. + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_NAME "$PACKAGE_NAME" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_TARNAME "$PACKAGE_TARNAME" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_VERSION "$PACKAGE_VERSION" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_STRING "$PACKAGE_STRING" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_BUGREPORT "$PACKAGE_BUGREPORT" +_ACEOF + +cat >>confdefs.h <<_ACEOF +#define PACKAGE_URL "$PACKAGE_URL" +_ACEOF + + +# Let the site file select an alternate cache file if it wants to. +# Prefer an explicitly selected file to automatically selected ones. +ac_site_file1=NONE +ac_site_file2=NONE +if test -n "$CONFIG_SITE"; then + # We do not want a PATH search for config.site. + case $CONFIG_SITE in #(( + -*) ac_site_file1=./$CONFIG_SITE;; + */*) ac_site_file1=$CONFIG_SITE;; + *) ac_site_file1=./$CONFIG_SITE;; + esac +elif test "x$prefix" != xNONE; then + ac_site_file1=$prefix/share/config.site + ac_site_file2=$prefix/etc/config.site +else + ac_site_file1=$ac_default_prefix/share/config.site + ac_site_file2=$ac_default_prefix/etc/config.site +fi +for ac_site_file in "$ac_site_file1" "$ac_site_file2" +do + test "x$ac_site_file" = xNONE && continue + if test /dev/null != "$ac_site_file" && test -r "$ac_site_file"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: loading site script $ac_site_file" >&5 +$as_echo "$as_me: loading site script $ac_site_file" >&6;} + sed 's/^/| /' "$ac_site_file" >&5 + . "$ac_site_file" \ + || { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "failed to load site script $ac_site_file +See \`config.log' for more details" "$LINENO" 5; } + fi +done + +if test -r "$cache_file"; then + # Some versions of bash will fail to source /dev/null (special files + # actually), so we avoid doing that. DJGPP emulates it as a regular file. + if test /dev/null != "$cache_file" && test -f "$cache_file"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: loading cache $cache_file" >&5 +$as_echo "$as_me: loading cache $cache_file" >&6;} + case $cache_file in + [\\/]* | ?:[\\/]* ) . "$cache_file";; + *) . "./$cache_file";; + esac + fi +else + { $as_echo "$as_me:${as_lineno-$LINENO}: creating cache $cache_file" >&5 +$as_echo "$as_me: creating cache $cache_file" >&6;} + >$cache_file +fi + +# Check that the precious variables saved in the cache have kept the same +# value. +ac_cache_corrupted=false +for ac_var in $ac_precious_vars; do + eval ac_old_set=\$ac_cv_env_${ac_var}_set + eval ac_new_set=\$ac_env_${ac_var}_set + eval ac_old_val=\$ac_cv_env_${ac_var}_value + eval ac_new_val=\$ac_env_${ac_var}_value + case $ac_old_set,$ac_new_set in + set,) + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&5 +$as_echo "$as_me: error: \`$ac_var' was set to \`$ac_old_val' in the previous run" >&2;} + ac_cache_corrupted=: ;; + ,set) + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' was not set in the previous run" >&5 +$as_echo "$as_me: error: \`$ac_var' was not set in the previous run" >&2;} + ac_cache_corrupted=: ;; + ,);; + *) + if test "x$ac_old_val" != "x$ac_new_val"; then + # differences in whitespace do not lead to failure. + ac_old_val_w=`echo x $ac_old_val` + ac_new_val_w=`echo x $ac_new_val` + if test "$ac_old_val_w" != "$ac_new_val_w"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: error: \`$ac_var' has changed since the previous run:" >&5 +$as_echo "$as_me: error: \`$ac_var' has changed since the previous run:" >&2;} + ac_cache_corrupted=: + else + { $as_echo "$as_me:${as_lineno-$LINENO}: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&5 +$as_echo "$as_me: warning: ignoring whitespace changes in \`$ac_var' since the previous run:" >&2;} + eval $ac_var=\$ac_old_val + fi + { $as_echo "$as_me:${as_lineno-$LINENO}: former value: \`$ac_old_val'" >&5 +$as_echo "$as_me: former value: \`$ac_old_val'" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: current value: \`$ac_new_val'" >&5 +$as_echo "$as_me: current value: \`$ac_new_val'" >&2;} + fi;; + esac + # Pass precious variables to config.status. + if test "$ac_new_set" = set; then + case $ac_new_val in + *\'*) ac_arg=$ac_var=`$as_echo "$ac_new_val" | sed "s/'/'\\\\\\\\''/g"` ;; + *) ac_arg=$ac_var=$ac_new_val ;; + esac + case " $ac_configure_args " in + *" '$ac_arg' "*) ;; # Avoid dups. Use of quotes ensures accuracy. + *) as_fn_append ac_configure_args " '$ac_arg'" ;; + esac + fi +done +if $ac_cache_corrupted; then + { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} + { $as_echo "$as_me:${as_lineno-$LINENO}: error: changes in the environment can compromise the build" >&5 +$as_echo "$as_me: error: changes in the environment can compromise the build" >&2;} + as_fn_error $? "run \`make distclean' and/or \`rm $cache_file' and start over" "$LINENO" 5 +fi +## -------------------- ## +## Main body of script. ## +## -------------------- ## + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + + + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu +if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}gcc", so it can be a program name with args. +set dummy ${ac_tool_prefix}gcc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$ac_cv_prog_CC"; then + ac_ct_CC=$CC + # Extract the first word of "gcc", so it can be a program name with args. +set dummy gcc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +else + CC="$ac_cv_prog_CC" +fi + +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + # Extract the first word of "${ac_tool_prefix}cc", so it can be a program name with args. +set dummy ${ac_tool_prefix}cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + fi +fi +if test -z "$CC"; then + # Extract the first word of "cc", so it can be a program name with args. +set dummy cc; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else + ac_prog_rejected=no +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then + ac_prog_rejected=yes + continue + fi + ac_cv_prog_CC="cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +if test $ac_prog_rejected = yes; then + # We found a bogon in the path, so make sure we never use it. + set dummy $ac_cv_prog_CC + shift + if test $# != 0; then + # We chose a different compiler from the bogus one. + # However, it has the same basename, so the bogon will be chosen + # first if we set CC to just the basename; use the full file name. + shift + ac_cv_prog_CC="$as_dir/$ac_word${1+' '}$@" + fi +fi +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + +fi +if test -z "$CC"; then + if test -n "$ac_tool_prefix"; then + for ac_prog in cl.exe + do + # Extract the first word of "$ac_tool_prefix$ac_prog", so it can be a program name with args. +set dummy $ac_tool_prefix$ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$CC"; then + ac_cv_prog_CC="$CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +CC=$ac_cv_prog_CC +if test -n "$CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $CC" >&5 +$as_echo "$CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$CC" && break + done +fi +if test -z "$CC"; then + ac_ct_CC=$CC + for ac_prog in cl.exe +do + # Extract the first word of "$ac_prog", so it can be a program name with args. +set dummy $ac_prog; ac_word=$2 +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 +$as_echo_n "checking for $ac_word... " >&6; } +if ${ac_cv_prog_ac_ct_CC+:} false; then : + $as_echo_n "(cached) " >&6 +else + if test -n "$ac_ct_CC"; then + ac_cv_prog_ac_ct_CC="$ac_ct_CC" # Let the user override the test. +else +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 + fi +done + done +IFS=$as_save_IFS + +fi +fi +ac_ct_CC=$ac_cv_prog_ac_ct_CC +if test -n "$ac_ct_CC"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_ct_CC" >&5 +$as_echo "$ac_ct_CC" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi + + + test -n "$ac_ct_CC" && break +done + + if test "x$ac_ct_CC" = x; then + CC="" + else + case $cross_compiling:$ac_tool_warned in +yes:) +{ $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: using cross tools not prefixed with host triplet" >&5 +$as_echo "$as_me: WARNING: using cross tools not prefixed with host triplet" >&2;} +ac_tool_warned=yes ;; +esac + CC=$ac_ct_CC + fi +fi + +fi + + +test -z "$CC" && { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "no acceptable C compiler found in \$PATH +See \`config.log' for more details" "$LINENO" 5; } + +# Provide some information about the compiler. +$as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler version" >&5 +set X $ac_compile +ac_compiler=$2 +for ac_option in --version -v -V -qversion; do + { { ac_try="$ac_compiler $ac_option >&5" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compiler $ac_option >&5") 2>conftest.err + ac_status=$? + if test -s conftest.err; then + sed '10a\ +... rest of stderr output deleted ... + 10q' conftest.err >conftest.er1 + cat conftest.er1 >&5 + fi + rm -f conftest.er1 conftest.err + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } +done + +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +ac_clean_files_save=$ac_clean_files +ac_clean_files="$ac_clean_files a.out a.out.dSYM a.exe b.out" +# Try to create an executable without -o first, disregard a.out. +# It will help us diagnose broken compilers, and finding out an intuition +# of exeext. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether the C compiler works" >&5 +$as_echo_n "checking whether the C compiler works... " >&6; } +ac_link_default=`$as_echo "$ac_link" | sed 's/ -o *conftest[^ ]*//'` + +# The possible output files: +ac_files="a.out conftest.exe conftest a.exe a_out.exe b.out conftest.*" + +ac_rmfiles= +for ac_file in $ac_files +do + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; + * ) ac_rmfiles="$ac_rmfiles $ac_file";; + esac +done +rm -f $ac_rmfiles + +if { { ac_try="$ac_link_default" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link_default") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + # Autoconf-2.13 could set the ac_cv_exeext variable to `no'. +# So ignore a value of `no', otherwise this would lead to `EXEEXT = no' +# in a Makefile. We should not override ac_cv_exeext if it was cached, +# so that the user can short-circuit this test for compilers unknown to +# Autoconf. +for ac_file in $ac_files '' +do + test -f "$ac_file" || continue + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) + ;; + [ab].out ) + # We found the default executable, but exeext='' is most + # certainly right. + break;; + *.* ) + if test "${ac_cv_exeext+set}" = set && test "$ac_cv_exeext" != no; + then :; else + ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` + fi + # We set ac_cv_exeext here because the later test for it is not + # safe: cross compilers may not add the suffix if given an `-o' + # argument, so we may need to know it at that point already. + # Even if this section looks crufty: it has the advantage of + # actually working. + break;; + * ) + break;; + esac +done +test "$ac_cv_exeext" = no && ac_cv_exeext= + +else + ac_file='' +fi +if test -z "$ac_file"; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +$as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error 77 "C compiler cannot create executables +See \`config.log' for more details" "$LINENO" 5; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for C compiler default output file name" >&5 +$as_echo_n "checking for C compiler default output file name... " >&6; } +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_file" >&5 +$as_echo "$ac_file" >&6; } +ac_exeext=$ac_cv_exeext + +rm -f -r a.out a.out.dSYM a.exe conftest$ac_cv_exeext b.out +ac_clean_files=$ac_clean_files_save +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of executables" >&5 +$as_echo_n "checking for suffix of executables... " >&6; } +if { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + # If both `conftest.exe' and `conftest' are `present' (well, observable) +# catch `conftest.exe'. For instance with Cygwin, `ls conftest' will +# work properly (i.e., refer to `conftest.exe'), while it won't with +# `rm'. +for ac_file in conftest.exe conftest conftest.*; do + test -f "$ac_file" || continue + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM | *.o | *.obj ) ;; + *.* ) ac_cv_exeext=`expr "$ac_file" : '[^.]*\(\..*\)'` + break;; + * ) break;; + esac +done +else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot compute suffix of executables: cannot compile and link +See \`config.log' for more details" "$LINENO" 5; } +fi +rm -f conftest conftest$ac_cv_exeext +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_exeext" >&5 +$as_echo "$ac_cv_exeext" >&6; } + +rm -f conftest.$ac_ext +EXEEXT=$ac_cv_exeext +ac_exeext=$EXEEXT +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <stdio.h> +int +main () +{ +FILE *f = fopen ("conftest.out", "w"); + return ferror (f) || fclose (f) != 0; + + ; + return 0; +} +_ACEOF +ac_clean_files="$ac_clean_files conftest.out" +# Check that the compiler produces executables we can run. If not, either +# the compiler is broken, or we cross compile. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are cross compiling" >&5 +$as_echo_n "checking whether we are cross compiling... " >&6; } +if test "$cross_compiling" != yes; then + { { ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_link") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } + if { ac_try='./conftest$ac_cv_exeext' + { { case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_try") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; }; then + cross_compiling=no + else + if test "$cross_compiling" = maybe; then + cross_compiling=yes + else + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot run C compiled programs. +If you meant to cross compile, use \`--host'. +See \`config.log' for more details" "$LINENO" 5; } + fi + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $cross_compiling" >&5 +$as_echo "$cross_compiling" >&6; } + +rm -f conftest.$ac_ext conftest$ac_cv_exeext conftest.out +ac_clean_files=$ac_clean_files_save +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for suffix of object files" >&5 +$as_echo_n "checking for suffix of object files... " >&6; } +if ${ac_cv_objext+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +rm -f conftest.o conftest.obj +if { { ac_try="$ac_compile" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval ac_try_echo="\"\$as_me:${as_lineno-$LINENO}: $ac_try_echo\"" +$as_echo "$ac_try_echo"; } >&5 + (eval "$ac_compile") 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; }; then : + for ac_file in conftest.o conftest.obj conftest.*; do + test -f "$ac_file" || continue; + case $ac_file in + *.$ac_ext | *.xcoff | *.tds | *.d | *.pdb | *.xSYM | *.bb | *.bbg | *.map | *.inf | *.dSYM ) ;; + *) ac_cv_objext=`expr "$ac_file" : '.*\.\(.*\)'` + break;; + esac +done +else + $as_echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + +{ { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot compute suffix of object files: cannot compile +See \`config.log' for more details" "$LINENO" 5; } +fi +rm -f conftest.$ac_cv_objext conftest.$ac_ext +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_objext" >&5 +$as_echo "$ac_cv_objext" >&6; } +OBJEXT=$ac_cv_objext +ac_objext=$OBJEXT +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether we are using the GNU C compiler" >&5 +$as_echo_n "checking whether we are using the GNU C compiler... " >&6; } +if ${ac_cv_c_compiler_gnu+:} false; then : + $as_echo_n "(cached) " >&6 +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ +#ifndef __GNUC__ + choke me +#endif + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_compiler_gnu=yes +else + ac_compiler_gnu=no +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +ac_cv_c_compiler_gnu=$ac_compiler_gnu + +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_c_compiler_gnu" >&5 +$as_echo "$ac_cv_c_compiler_gnu" >&6; } +if test $ac_compiler_gnu = yes; then + GCC=yes +else + GCC= +fi +ac_test_CFLAGS=${CFLAGS+set} +ac_save_CFLAGS=$CFLAGS +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $CC accepts -g" >&5 +$as_echo_n "checking whether $CC accepts -g... " >&6; } +if ${ac_cv_prog_cc_g+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_save_c_werror_flag=$ac_c_werror_flag + ac_c_werror_flag=yes + ac_cv_prog_cc_g=no + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +else + CFLAGS="" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + +else + ac_c_werror_flag=$ac_save_c_werror_flag + CFLAGS="-g" + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +int +main () +{ + + ; + return 0; +} +_ACEOF +if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_g=yes +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext +fi +rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext + ac_c_werror_flag=$ac_save_c_werror_flag +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_g" >&5 +$as_echo "$ac_cv_prog_cc_g" >&6; } +if test "$ac_test_CFLAGS" = set; then + CFLAGS=$ac_save_CFLAGS +elif test $ac_cv_prog_cc_g = yes; then + if test "$GCC" = yes; then + CFLAGS="-g -O2" + else + CFLAGS="-g" + fi +else + if test "$GCC" = yes; then + CFLAGS="-O2" + else + CFLAGS= + fi +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $CC option to accept ISO C89" >&5 +$as_echo_n "checking for $CC option to accept ISO C89... " >&6; } +if ${ac_cv_prog_cc_c89+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_cv_prog_cc_c89=no +ac_save_CC=$CC +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <stdarg.h> +#include <stdio.h> +struct stat; +/* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */ +struct buf { int x; }; +FILE * (*rcsopen) (struct buf *, struct stat *, int); +static char *e (p, i) + char **p; + int i; +{ + return p[i]; +} +static char *f (char * (*g) (char **, int), char **p, ...) +{ + char *s; + va_list v; + va_start (v,p); + s = g (p, va_arg (v,int)); + va_end (v); + return s; +} + +/* OSF 4.0 Compaq cc is some sort of almost-ANSI by default. It has + function prototypes and stuff, but not '\xHH' hex character constants. + These don't provoke an error unfortunately, instead are silently treated + as 'x'. The following induces an error, until -std is added to get + proper ANSI mode. Curiously '\x00'!='x' always comes out true, for an + array size at least. It's necessary to write '\x00'==0 to get something + that's true only with -std. */ +int osf4_cc_array ['\x00' == 0 ? 1 : -1]; + +/* IBM C 6 for AIX is almost-ANSI by default, but it replaces macro parameters + inside strings and character constants. */ +#define FOO(x) 'x' +int xlc6_cc_array[FOO(a) == 'x' ? 1 : -1]; + +int test (int i, double x); +struct s1 {int (*f) (int a);}; +struct s2 {int (*f) (double a);}; +int pairnames (int, char **, FILE *(*)(struct buf *, struct stat *, int), int, int); +int argc; +char **argv; +int +main () +{ +return f (e, argv, 0) != argv[0] || f (e, argv, 1) != argv[1]; + ; + return 0; +} +_ACEOF +for ac_arg in '' -qlanglvl=extc89 -qlanglvl=ansi -std \ + -Ae "-Aa -D_HPUX_SOURCE" "-Xc -D__EXTENSIONS__" +do + CC="$ac_save_CC $ac_arg" + if ac_fn_c_try_compile "$LINENO"; then : + ac_cv_prog_cc_c89=$ac_arg +fi +rm -f core conftest.err conftest.$ac_objext + test "x$ac_cv_prog_cc_c89" != "xno" && break +done +rm -f conftest.$ac_ext +CC=$ac_save_CC + +fi +# AC_CACHE_VAL +case "x$ac_cv_prog_cc_c89" in + x) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: none needed" >&5 +$as_echo "none needed" >&6; } ;; + xno) + { $as_echo "$as_me:${as_lineno-$LINENO}: result: unsupported" >&5 +$as_echo "unsupported" >&6; } ;; + *) + CC="$CC $ac_cv_prog_cc_c89" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_prog_cc_c89" >&5 +$as_echo "$ac_cv_prog_cc_c89" >&6; } ;; +esac +if test "x$ac_cv_prog_cc_c89" != xno; then : + +fi + +ac_ext=c +ac_cpp='$CPP $CPPFLAGS' +ac_compile='$CC -c $CFLAGS $CPPFLAGS conftest.$ac_ext >&5' +ac_link='$CC -o conftest$ac_exeext $CFLAGS $CPPFLAGS $LDFLAGS conftest.$ac_ext $LIBS >&5' +ac_compiler_gnu=$ac_cv_c_compiler_gnu + + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for sqrt in -lm" >&5 +$as_echo_n "checking for sqrt in -lm... " >&6; } +if ${ac_cv_lib_m_sqrt+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lm $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char sqrt (); +int +main () +{ +return sqrt (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_m_sqrt=yes +else + ac_cv_lib_m_sqrt=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_m_sqrt" >&5 +$as_echo "$ac_cv_lib_m_sqrt" >&6; } +if test "x$ac_cv_lib_m_sqrt" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBM 1 +_ACEOF + + LIBS="-lm $LIBS" + +fi + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for clock_gettime in -lrt" >&5 +$as_echo_n "checking for clock_gettime in -lrt... " >&6; } +if ${ac_cv_lib_rt_clock_gettime+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lrt $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char clock_gettime (); +int +main () +{ +return clock_gettime (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_rt_clock_gettime=yes +else + ac_cv_lib_rt_clock_gettime=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_rt_clock_gettime" >&5 +$as_echo "$ac_cv_lib_rt_clock_gettime" >&6; } +if test "x$ac_cv_lib_rt_clock_gettime" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBRT 1 +_ACEOF + + LIBS="-lrt $LIBS" + +fi + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking epoll support" >&5 +$as_echo_n "checking epoll support... " >&6; } +if test "$cross_compiling" = yes; then : + { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 +$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} +as_fn_error $? "cannot run test program while cross compiling +See \`config.log' for more details" "$LINENO" 5; } +else + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +#include <sys/epoll.h> +int main() { + if (epoll_create(1) < 0) + return (1); + return (0); +} + +_ACEOF +if ac_fn_c_try_run "$LINENO"; then : + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +else + as_fn_error $? "epoll not found" "$LINENO" 5 +fi +rm -f core *.core core.conftest.* gmon.out bb.out conftest$ac_exeext \ + conftest.$ac_objext conftest.beam conftest.$ac_ext +fi + + +case "$host" in + *-freebsd*) + # We don't want to set -lpthread as that break + # the ability to choose threads library at final + # link time and is not valid for all architectures. + + PTHREAD= + if test "X$GCC" = "Xyes"; then + saved_cc="$CC" + CC="$CC -pthread" + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for gcc -pthread support" >&5 +$as_echo_n "checking for gcc -pthread support... " >&6; }; + cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ +#include <pthread.h> +int +main () +{ +printf("%x\n", pthread_create); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + PTHREAD="yes" + { $as_echo "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +$as_echo "yes" >&6; } +else + { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 +$as_echo "no" >&6; } +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext + CC="$saved_cc" + fi + if test "X$PTHREAD" != "Xyes"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for pthread_create in -lpthread" >&5 +$as_echo_n "checking for pthread_create in -lpthread... " >&6; } +if ${ac_cv_lib_pthread_pthread_create+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lpthread $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char pthread_create (); +int +main () +{ +return pthread_create (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_pthread_pthread_create=yes +else + ac_cv_lib_pthread_pthread_create=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pthread_pthread_create" >&5 +$as_echo "$ac_cv_lib_pthread_pthread_create" >&6; } +if test "x$ac_cv_lib_pthread_pthread_create" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBPTHREAD 1 +_ACEOF + + LIBS="-lpthread $LIBS" + +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for thread_create in -lthr" >&5 +$as_echo_n "checking for thread_create in -lthr... " >&6; } +if ${ac_cv_lib_thr_thread_create+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lthr $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char thread_create (); +int +main () +{ +return thread_create (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_thr_thread_create=yes +else + ac_cv_lib_thr_thread_create=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_thr_thread_create" >&5 +$as_echo "$ac_cv_lib_thr_thread_create" >&6; } +if test "x$ac_cv_lib_thr_thread_create" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBTHR 1 +_ACEOF + + LIBS="-lthr $LIBS" + +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for pthread_create in -lc_r" >&5 +$as_echo_n "checking for pthread_create in -lc_r... " >&6; } +if ${ac_cv_lib_c_r_pthread_create+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lc_r $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char pthread_create (); +int +main () +{ +return pthread_create (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_c_r_pthread_create=yes +else + ac_cv_lib_c_r_pthread_create=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_c_r_pthread_create" >&5 +$as_echo "$ac_cv_lib_c_r_pthread_create" >&6; } +if test "x$ac_cv_lib_c_r_pthread_create" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBC_R 1 +_ACEOF + + LIBS="-lc_r $LIBS" + +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for pthread_create in -lc" >&5 +$as_echo_n "checking for pthread_create in -lc... " >&6; } +if ${ac_cv_lib_c_pthread_create+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lc $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char pthread_create (); +int +main () +{ +return pthread_create (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_c_pthread_create=yes +else + ac_cv_lib_c_pthread_create=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_c_pthread_create" >&5 +$as_echo "$ac_cv_lib_c_pthread_create" >&6; } +if test "x$ac_cv_lib_c_pthread_create" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBC 1 +_ACEOF + + LIBS="-lc $LIBS" + +else + as_fn_error $? "\"could not find thread libraries\"" "$LINENO" 5 +fi + +fi + +fi + +fi + + fi + ;; + *) + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for pthread_create in -lpthread" >&5 +$as_echo_n "checking for pthread_create in -lpthread... " >&6; } +if ${ac_cv_lib_pthread_pthread_create+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lpthread $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char pthread_create (); +int +main () +{ +return pthread_create (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_pthread_pthread_create=yes +else + ac_cv_lib_pthread_pthread_create=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pthread_pthread_create" >&5 +$as_echo "$ac_cv_lib_pthread_pthread_create" >&6; } +if test "x$ac_cv_lib_pthread_pthread_create" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBPTHREAD 1 +_ACEOF + + LIBS="-lpthread $LIBS" + +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for __pthread_create in -lpthread" >&5 +$as_echo_n "checking for __pthread_create in -lpthread... " >&6; } +if ${ac_cv_lib_pthread___pthread_create+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lpthread $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char __pthread_create (); +int +main () +{ +return __pthread_create (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_pthread___pthread_create=yes +else + ac_cv_lib_pthread___pthread_create=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pthread___pthread_create" >&5 +$as_echo "$ac_cv_lib_pthread___pthread_create" >&6; } +if test "x$ac_cv_lib_pthread___pthread_create" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBPTHREAD 1 +_ACEOF + + LIBS="-lpthread $LIBS" + +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for __pthread_create_system in -lpthread" >&5 +$as_echo_n "checking for __pthread_create_system in -lpthread... " >&6; } +if ${ac_cv_lib_pthread___pthread_create_system+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lpthread $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char __pthread_create_system (); +int +main () +{ +return __pthread_create_system (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_pthread___pthread_create_system=yes +else + ac_cv_lib_pthread___pthread_create_system=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_pthread___pthread_create_system" >&5 +$as_echo "$ac_cv_lib_pthread___pthread_create_system" >&6; } +if test "x$ac_cv_lib_pthread___pthread_create_system" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBPTHREAD 1 +_ACEOF + + LIBS="-lpthread $LIBS" + +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for pthread_create in -lc_r" >&5 +$as_echo_n "checking for pthread_create in -lc_r... " >&6; } +if ${ac_cv_lib_c_r_pthread_create+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lc_r $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char pthread_create (); +int +main () +{ +return pthread_create (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_c_r_pthread_create=yes +else + ac_cv_lib_c_r_pthread_create=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_c_r_pthread_create" >&5 +$as_echo "$ac_cv_lib_c_r_pthread_create" >&6; } +if test "x$ac_cv_lib_c_r_pthread_create" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBC_R 1 +_ACEOF + + LIBS="-lc_r $LIBS" + +else + { $as_echo "$as_me:${as_lineno-$LINENO}: checking for pthread_create in -lc" >&5 +$as_echo_n "checking for pthread_create in -lc... " >&6; } +if ${ac_cv_lib_c_pthread_create+:} false; then : + $as_echo_n "(cached) " >&6 +else + ac_check_lib_save_LIBS=$LIBS +LIBS="-lc $LIBS" +cat confdefs.h - <<_ACEOF >conftest.$ac_ext +/* end confdefs.h. */ + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char pthread_create (); +int +main () +{ +return pthread_create (); + ; + return 0; +} +_ACEOF +if ac_fn_c_try_link "$LINENO"; then : + ac_cv_lib_c_pthread_create=yes +else + ac_cv_lib_c_pthread_create=no +fi +rm -f core conftest.err conftest.$ac_objext \ + conftest$ac_exeext conftest.$ac_ext +LIBS=$ac_check_lib_save_LIBS +fi +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_c_pthread_create" >&5 +$as_echo "$ac_cv_lib_c_pthread_create" >&6; } +if test "x$ac_cv_lib_c_pthread_create" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_LIBC 1 +_ACEOF + + LIBS="-lc $LIBS" + +else + as_fn_error $? "\"could not find thread libraries\"" "$LINENO" 5 +fi + +fi + +fi + +fi + +fi + + ;; +esac + +ac_config_files="$ac_config_files Makefile" + +cat >confcache <<\_ACEOF +# This file is a shell script that caches the results of configure +# tests run on this system so they can be shared between configure +# scripts and configure runs, see configure's option --config-cache. +# It is not useful on other systems. If it contains results you don't +# want to keep, you may remove or edit it. +# +# config.status only pays attention to the cache file if you give it +# the --recheck option to rerun configure. +# +# `ac_cv_env_foo' variables (set or unset) will be overridden when +# loading this file, other *unset* `ac_cv_foo' will be assigned the +# following values. + +_ACEOF + +# The following way of writing the cache mishandles newlines in values, +# but we know of no workaround that is simple, portable, and efficient. +# So, we kill variables containing newlines. +# Ultrix sh set writes to stderr and can't be redirected directly, +# and sets the high bit in the cache file unless we assign to the vars. +( + for ac_var in `(set) 2>&1 | sed -n 's/^\([a-zA-Z_][a-zA-Z0-9_]*\)=.*/\1/p'`; do + eval ac_val=\$$ac_var + case $ac_val in #( + *${as_nl}*) + case $ac_var in #( + *_cv_*) { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: cache variable $ac_var contains a newline" >&5 +$as_echo "$as_me: WARNING: cache variable $ac_var contains a newline" >&2;} ;; + esac + case $ac_var in #( + _ | IFS | as_nl) ;; #( + BASH_ARGV | BASH_SOURCE) eval $ac_var= ;; #( + *) { eval $ac_var=; unset $ac_var;} ;; + esac ;; + esac + done + + (set) 2>&1 | + case $as_nl`(ac_space=' '; set) 2>&1` in #( + *${as_nl}ac_space=\ *) + # `set' does not quote correctly, so add quotes: double-quote + # substitution turns \\\\ into \\, and sed turns \\ into \. + sed -n \ + "s/'/'\\\\''/g; + s/^\\([_$as_cr_alnum]*_cv_[_$as_cr_alnum]*\\)=\\(.*\\)/\\1='\\2'/p" + ;; #( + *) + # `set' quotes correctly as required by POSIX, so do not add quotes. + sed -n "/^[_$as_cr_alnum]*_cv_[_$as_cr_alnum]*=/p" + ;; + esac | + sort +) | + sed ' + /^ac_cv_env_/b end + t clear + :clear + s/^\([^=]*\)=\(.*[{}].*\)$/test "${\1+set}" = set || &/ + t end + s/^\([^=]*\)=\(.*\)$/\1=${\1=\2}/ + :end' >>confcache +if diff "$cache_file" confcache >/dev/null 2>&1; then :; else + if test -w "$cache_file"; then + if test "x$cache_file" != "x/dev/null"; then + { $as_echo "$as_me:${as_lineno-$LINENO}: updating cache $cache_file" >&5 +$as_echo "$as_me: updating cache $cache_file" >&6;} + if test ! -f "$cache_file" || test -h "$cache_file"; then + cat confcache >"$cache_file" + else + case $cache_file in #( + */* | ?:*) + mv -f confcache "$cache_file"$$ && + mv -f "$cache_file"$$ "$cache_file" ;; #( + *) + mv -f confcache "$cache_file" ;; + esac + fi + fi + else + { $as_echo "$as_me:${as_lineno-$LINENO}: not updating unwritable cache $cache_file" >&5 +$as_echo "$as_me: not updating unwritable cache $cache_file" >&6;} + fi +fi +rm -f confcache + +test "x$prefix" = xNONE && prefix=$ac_default_prefix +# Let make expand exec_prefix. +test "x$exec_prefix" = xNONE && exec_prefix='${prefix}' + +# Transform confdefs.h into DEFS. +# Protect against shell expansion while executing Makefile rules. +# Protect against Makefile macro expansion. +# +# If the first sed substitution is executed (which looks for macros that +# take arguments), then branch to the quote section. Otherwise, +# look for a macro that doesn't take arguments. +ac_script=' +:mline +/\\$/{ + N + s,\\\n,, + b mline +} +t clear +:clear +s/^[ ]*#[ ]*define[ ][ ]*\([^ (][^ (]*([^)]*)\)[ ]*\(.*\)/-D\1=\2/g +t quote +s/^[ ]*#[ ]*define[ ][ ]*\([^ ][^ ]*\)[ ]*\(.*\)/-D\1=\2/g +t quote +b any +:quote +s/[ `~#$^&*(){}\\|;'\''"<>?]/\\&/g +s/\[/\\&/g +s/\]/\\&/g +s/\$/$$/g +H +:any +${ + g + s/^\n// + s/\n/ /g + p +} +' +DEFS=`sed -n "$ac_script" confdefs.h` + + +ac_libobjs= +ac_ltlibobjs= +U= +for ac_i in : $LIBOBJS; do test "x$ac_i" = x: && continue + # 1. Remove the extension, and $U if already installed. + ac_script='s/\$U\././;s/\.o$//;s/\.obj$//' + ac_i=`$as_echo "$ac_i" | sed "$ac_script"` + # 2. Prepend LIBOBJDIR. When used with automake>=1.10 LIBOBJDIR + # will be set to the directory where LIBOBJS objects are built. + as_fn_append ac_libobjs " \${LIBOBJDIR}$ac_i\$U.$ac_objext" + as_fn_append ac_ltlibobjs " \${LIBOBJDIR}$ac_i"'$U.lo' +done +LIBOBJS=$ac_libobjs + +LTLIBOBJS=$ac_ltlibobjs + + + +: "${CONFIG_STATUS=./config.status}" +ac_write_fail=0 +ac_clean_files_save=$ac_clean_files +ac_clean_files="$ac_clean_files $CONFIG_STATUS" +{ $as_echo "$as_me:${as_lineno-$LINENO}: creating $CONFIG_STATUS" >&5 +$as_echo "$as_me: creating $CONFIG_STATUS" >&6;} +as_write_fail=0 +cat >$CONFIG_STATUS <<_ASEOF || as_write_fail=1 +#! $SHELL +# Generated by $as_me. +# Run this file to recreate the current configuration. +# Compiler output produced by configure, useful for debugging +# configure, is in config.log if it exists. + +debug=false +ac_cs_recheck=false +ac_cs_silent=false + +SHELL=\${CONFIG_SHELL-$SHELL} +export SHELL +_ASEOF +cat >>$CONFIG_STATUS <<\_ASEOF || as_write_fail=1 +## -------------------- ## +## M4sh Initialization. ## +## -------------------- ## + +# Be more Bourne compatible +DUALCASE=1; export DUALCASE # for MKS sh +if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then : + emulate sh + NULLCMD=: + # Pre-4.2 versions of Zsh do word splitting on ${1+"$@"}, which + # is contrary to our usage. Disable this feature. + alias -g '${1+"$@"}'='"$@"' + setopt NO_GLOB_SUBST +else + case `(set -o) 2>/dev/null` in #( + *posix*) : + set -o posix ;; #( + *) : + ;; +esac +fi + + +as_nl=' +' +export as_nl +# Printing a long string crashes Solaris 7 /usr/bin/printf. +as_echo='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo +as_echo=$as_echo$as_echo$as_echo$as_echo$as_echo$as_echo +# Prefer a ksh shell builtin over an external printf program on Solaris, +# but without wasting forks for bash or zsh. +if test -z "$BASH_VERSION$ZSH_VERSION" \ + && (test "X`print -r -- $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='print -r --' + as_echo_n='print -rn --' +elif (test "X`printf %s $as_echo`" = "X$as_echo") 2>/dev/null; then + as_echo='printf %s\n' + as_echo_n='printf %s' +else + if test "X`(/usr/ucb/echo -n -n $as_echo) 2>/dev/null`" = "X-n $as_echo"; then + as_echo_body='eval /usr/ucb/echo -n "$1$as_nl"' + as_echo_n='/usr/ucb/echo -n' + else + as_echo_body='eval expr "X$1" : "X\\(.*\\)"' + as_echo_n_body='eval + arg=$1; + case $arg in #( + *"$as_nl"*) + expr "X$arg" : "X\\(.*\\)$as_nl"; + arg=`expr "X$arg" : ".*$as_nl\\(.*\\)"`;; + esac; + expr "X$arg" : "X\\(.*\\)" | tr -d "$as_nl" + ' + export as_echo_n_body + as_echo_n='sh -c $as_echo_n_body as_echo' + fi + export as_echo_body + as_echo='sh -c $as_echo_body as_echo' +fi + +# The user is always right. +if test "${PATH_SEPARATOR+set}" != set; then + PATH_SEPARATOR=: + (PATH='/bin;/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 && { + (PATH='/bin:/bin'; FPATH=$PATH; sh -c :) >/dev/null 2>&1 || + PATH_SEPARATOR=';' + } +fi + + +# IFS +# We need space, tab and new line, in precisely that order. Quoting is +# there to prevent editors from complaining about space-tab. +# (If _AS_PATH_WALK were called with IFS unset, it would disable word +# splitting by setting IFS to empty value.) +IFS=" "" $as_nl" + +# Find who we are. Look in the path if we contain no directory separator. +as_myself= +case $0 in #(( + *[\\/]* ) as_myself=$0 ;; + *) as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + test -r "$as_dir/$0" && as_myself=$as_dir/$0 && break + done +IFS=$as_save_IFS + + ;; +esac +# We did not find ourselves, most probably we were run as `sh COMMAND' +# in which case we are not to be found in the path. +if test "x$as_myself" = x; then + as_myself=$0 +fi +if test ! -f "$as_myself"; then + $as_echo "$as_myself: error: cannot find myself; rerun with an absolute file name" >&2 + exit 1 +fi + +# Unset variables that we do not need and which cause bugs (e.g. in +# pre-3.0 UWIN ksh). But do not cause bugs in bash 2.01; the "|| exit 1" +# suppresses any "Segmentation fault" message there. '((' could +# trigger a bug in pdksh 5.2.14. +for as_var in BASH_ENV ENV MAIL MAILPATH +do eval test x\${$as_var+set} = xset \ + && ( (unset $as_var) || exit 1) >/dev/null 2>&1 && unset $as_var || : +done +PS1='$ ' +PS2='> ' +PS4='+ ' + +# NLS nuisances. +LC_ALL=C +export LC_ALL +LANGUAGE=C +export LANGUAGE + +# CDPATH. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + + +# as_fn_error STATUS ERROR [LINENO LOG_FD] +# ---------------------------------------- +# Output "`basename $0`: error: ERROR" to stderr. If LINENO and LOG_FD are +# provided, also output the error to LOG_FD, referencing LINENO. Then exit the +# script with STATUS, using 1 if that was 0. +as_fn_error () +{ + as_status=$1; test $as_status -eq 0 && as_status=1 + if test "$4"; then + as_lineno=${as_lineno-"$3"} as_lineno_stack=as_lineno_stack=$as_lineno_stack + $as_echo "$as_me:${as_lineno-$LINENO}: error: $2" >&$4 + fi + $as_echo "$as_me: error: $2" >&2 + as_fn_exit $as_status +} # as_fn_error + + +# as_fn_set_status STATUS +# ----------------------- +# Set $? to STATUS, without forking. +as_fn_set_status () +{ + return $1 +} # as_fn_set_status + +# as_fn_exit STATUS +# ----------------- +# Exit the shell with STATUS, even in a "trap 0" or "set -e" context. +as_fn_exit () +{ + set +e + as_fn_set_status $1 + exit $1 +} # as_fn_exit + +# as_fn_unset VAR +# --------------- +# Portably unset VAR. +as_fn_unset () +{ + { eval $1=; unset $1;} +} +as_unset=as_fn_unset +# as_fn_append VAR VALUE +# ---------------------- +# Append the text in VALUE to the end of the definition contained in VAR. Take +# advantage of any shell optimizations that allow amortized linear growth over +# repeated appends, instead of the typical quadratic growth present in naive +# implementations. +if (eval "as_var=1; as_var+=2; test x\$as_var = x12") 2>/dev/null; then : + eval 'as_fn_append () + { + eval $1+=\$2 + }' +else + as_fn_append () + { + eval $1=\$$1\$2 + } +fi # as_fn_append + +# as_fn_arith ARG... +# ------------------ +# Perform arithmetic evaluation on the ARGs, and store the result in the +# global $as_val. Take advantage of shells that can avoid forks. The arguments +# must be portable across $(()) and expr. +if (eval "test \$(( 1 + 1 )) = 2") 2>/dev/null; then : + eval 'as_fn_arith () + { + as_val=$(( $* )) + }' +else + as_fn_arith () + { + as_val=`expr "$@" || test $? -eq 1` + } +fi # as_fn_arith + + +if expr a : '\(a\)' >/dev/null 2>&1 && + test "X`expr 00001 : '.*\(...\)'`" = X001; then + as_expr=expr +else + as_expr=false +fi + +if (basename -- /) >/dev/null 2>&1 && test "X`basename -- / 2>&1`" = "X/"; then + as_basename=basename +else + as_basename=false +fi + +if (as_dir=`dirname -- /` && test "X$as_dir" = X/) >/dev/null 2>&1; then + as_dirname=dirname +else + as_dirname=false +fi + +as_me=`$as_basename -- "$0" || +$as_expr X/"$0" : '.*/\([^/][^/]*\)/*$' \| \ + X"$0" : 'X\(//\)$' \| \ + X"$0" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X/"$0" | + sed '/^.*\/\([^/][^/]*\)\/*$/{ + s//\1/ + q + } + /^X\/\(\/\/\)$/{ + s//\1/ + q + } + /^X\/\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + +# Avoid depending upon Character Ranges. +as_cr_letters='abcdefghijklmnopqrstuvwxyz' +as_cr_LETTERS='ABCDEFGHIJKLMNOPQRSTUVWXYZ' +as_cr_Letters=$as_cr_letters$as_cr_LETTERS +as_cr_digits='0123456789' +as_cr_alnum=$as_cr_Letters$as_cr_digits + +ECHO_C= ECHO_N= ECHO_T= +case `echo -n x` in #((((( +-n*) + case `echo 'xy\c'` in + *c*) ECHO_T=' ';; # ECHO_T is single tab character. + xy) ECHO_C='\c';; + *) echo `echo ksh88 bug on AIX 6.1` > /dev/null + ECHO_T=' ';; + esac;; +*) + ECHO_N='-n';; +esac + +rm -f conf$$ conf$$.exe conf$$.file +if test -d conf$$.dir; then + rm -f conf$$.dir/conf$$.file +else + rm -f conf$$.dir + mkdir conf$$.dir 2>/dev/null +fi +if (echo >conf$$.file) 2>/dev/null; then + if ln -s conf$$.file conf$$ 2>/dev/null; then + as_ln_s='ln -s' + # ... but there are two gotchas: + # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. + # In both cases, we have to default to `cp -pR'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || + as_ln_s='cp -pR' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else + as_ln_s='cp -pR' + fi +else + as_ln_s='cp -pR' +fi +rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file +rmdir conf$$.dir 2>/dev/null + + +# as_fn_mkdir_p +# ------------- +# Create "$as_dir" as a directory, including parents if necessary. +as_fn_mkdir_p () +{ + + case $as_dir in #( + -*) as_dir=./$as_dir;; + esac + test -d "$as_dir" || eval $as_mkdir_p || { + as_dirs= + while :; do + case $as_dir in #( + *\'*) as_qdir=`$as_echo "$as_dir" | sed "s/'/'\\\\\\\\''/g"`;; #'( + *) as_qdir=$as_dir;; + esac + as_dirs="'$as_qdir' $as_dirs" + as_dir=`$as_dirname -- "$as_dir" || +$as_expr X"$as_dir" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$as_dir" : 'X\(//\)[^/]' \| \ + X"$as_dir" : 'X\(//\)$' \| \ + X"$as_dir" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$as_dir" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + test -d "$as_dir" && break + done + test -z "$as_dirs" || eval "mkdir $as_dirs" + } || test -d "$as_dir" || as_fn_error $? "cannot create directory $as_dir" + + +} # as_fn_mkdir_p +if mkdir -p . 2>/dev/null; then + as_mkdir_p='mkdir -p "$as_dir"' +else + test -d ./-p && rmdir ./-p + as_mkdir_p=false +fi + + +# as_fn_executable_p FILE +# ----------------------- +# Test if FILE is an executable regular file. +as_fn_executable_p () +{ + test -f "$1" && test -x "$1" +} # as_fn_executable_p +as_test_x='test -x' +as_executable_p=as_fn_executable_p + +# Sed expression to map a string onto a valid CPP name. +as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" + +# Sed expression to map a string onto a valid variable name. +as_tr_sh="eval sed 'y%*+%pp%;s%[^_$as_cr_alnum]%_%g'" + + +exec 6>&1 +## ----------------------------------- ## +## Main body of $CONFIG_STATUS script. ## +## ----------------------------------- ## +_ASEOF +test $as_write_fail = 0 && chmod +x $CONFIG_STATUS || ac_write_fail=1 + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# Save the log message, to keep $0 and so on meaningful, and to +# report actual input values of CONFIG_FILES etc. instead of their +# values after options handling. +ac_log=" +This file was extended by $as_me, which was +generated by GNU Autoconf 2.69. Invocation command line was + + CONFIG_FILES = $CONFIG_FILES + CONFIG_HEADERS = $CONFIG_HEADERS + CONFIG_LINKS = $CONFIG_LINKS + CONFIG_COMMANDS = $CONFIG_COMMANDS + $ $0 $@ + +on `(hostname || uname -n) 2>/dev/null | sed 1q` +" + +_ACEOF + +case $ac_config_files in *" +"*) set x $ac_config_files; shift; ac_config_files=$*;; +esac + + + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +# Files that config.status was made for. +config_files="$ac_config_files" + +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +ac_cs_usage="\ +\`$as_me' instantiates files and other configuration actions +from templates according to the current configuration. Unless the files +and actions are specified as TAGs, all are instantiated by default. + +Usage: $0 [OPTION]... [TAG]... + + -h, --help print this help, then exit + -V, --version print version number and configuration settings, then exit + --config print configuration, then exit + -q, --quiet, --silent + do not print progress messages + -d, --debug don't remove temporary files + --recheck update $as_me by reconfiguring in the same conditions + --file=FILE[:TEMPLATE] + instantiate the configuration file FILE + +Configuration files: +$config_files + +Report bugs to the package provider." + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" +ac_cs_version="\\ +config.status +configured by $0, generated by GNU Autoconf 2.69, + with options \\"\$ac_cs_config\\" + +Copyright (C) 2012 Free Software Foundation, Inc. +This config.status script is free software; the Free Software Foundation +gives unlimited permission to copy, distribute and modify it." + +ac_pwd='$ac_pwd' +srcdir='$srcdir' +test -n "\$AWK" || AWK=awk +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# The default lists apply if the user does not specify any file. +ac_need_defaults=: +while test $# != 0 +do + case $1 in + --*=?*) + ac_option=`expr "X$1" : 'X\([^=]*\)='` + ac_optarg=`expr "X$1" : 'X[^=]*=\(.*\)'` + ac_shift=: + ;; + --*=) + ac_option=`expr "X$1" : 'X\([^=]*\)='` + ac_optarg= + ac_shift=: + ;; + *) + ac_option=$1 + ac_optarg=$2 + ac_shift=shift + ;; + esac + + case $ac_option in + # Handling of the options. + -recheck | --recheck | --rechec | --reche | --rech | --rec | --re | --r) + ac_cs_recheck=: ;; + --version | --versio | --versi | --vers | --ver | --ve | --v | -V ) + $as_echo "$ac_cs_version"; exit ;; + --config | --confi | --conf | --con | --co | --c ) + $as_echo "$ac_cs_config"; exit ;; + --debug | --debu | --deb | --de | --d | -d ) + debug=: ;; + --file | --fil | --fi | --f ) + $ac_shift + case $ac_optarg in + *\'*) ac_optarg=`$as_echo "$ac_optarg" | sed "s/'/'\\\\\\\\''/g"` ;; + '') as_fn_error $? "missing file argument" ;; + esac + as_fn_append CONFIG_FILES " '$ac_optarg'" + ac_need_defaults=false;; + --he | --h | --help | --hel | -h ) + $as_echo "$ac_cs_usage"; exit ;; + -q | -quiet | --quiet | --quie | --qui | --qu | --q \ + | -silent | --silent | --silen | --sile | --sil | --si | --s) + ac_cs_silent=: ;; + + # This is an error. + -*) as_fn_error $? "unrecognized option: \`$1' +Try \`$0 --help' for more information." ;; + + *) as_fn_append ac_config_targets " $1" + ac_need_defaults=false ;; + + esac + shift +done + +ac_configure_extra_args= + +if $ac_cs_silent; then + exec 6>/dev/null + ac_configure_extra_args="$ac_configure_extra_args --silent" +fi + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +if \$ac_cs_recheck; then + set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion + shift + \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6 + CONFIG_SHELL='$SHELL' + export CONFIG_SHELL + exec "\$@" +fi + +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +exec 5>>config.log +{ + echo + sed 'h;s/./-/g;s/^.../## /;s/...$/ ##/;p;x;p;x' <<_ASBOX +## Running $as_me. ## +_ASBOX + $as_echo "$ac_log" +} >&5 + +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 + +# Handling of arguments. +for ac_config_target in $ac_config_targets +do + case $ac_config_target in + "Makefile") CONFIG_FILES="$CONFIG_FILES Makefile" ;; + + *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; + esac +done + + +# If the user did not use the arguments to specify the items to instantiate, +# then the envvar interface is used. Set only those that are not. +# We use the long form for the default assignment because of an extremely +# bizarre bug on SunOS 4.1.3. +if $ac_need_defaults; then + test "${CONFIG_FILES+set}" = set || CONFIG_FILES=$config_files +fi + +# Have a temporary directory for convenience. Make it in the build tree +# simply because there is no reason against having it here, and in addition, +# creating and moving files from /tmp can sometimes cause problems. +# Hook for its removal unless debugging. +# Note that there is a small window in which the directory will not be cleaned: +# after its creation but before its name has been assigned to `$tmp'. +$debug || +{ + tmp= ac_tmp= + trap 'exit_status=$? + : "${ac_tmp:=$tmp}" + { test ! -d "$ac_tmp" || rm -fr "$ac_tmp"; } && exit $exit_status +' 0 + trap 'as_fn_exit 1' 1 2 13 15 +} +# Create a (secure) tmp directory for tmp files. + +{ + tmp=`(umask 077 && mktemp -d "./confXXXXXX") 2>/dev/null` && + test -d "$tmp" +} || +{ + tmp=./conf$$-$RANDOM + (umask 077 && mkdir "$tmp") +} || as_fn_error $? "cannot create a temporary directory in ." "$LINENO" 5 +ac_tmp=$tmp + +# Set up the scripts for CONFIG_FILES section. +# No need to generate them if there are no CONFIG_FILES. +# This happens for instance with `./config.status config.h'. +if test -n "$CONFIG_FILES"; then + + +ac_cr=`echo X | tr X '\015'` +# On cygwin, bash can eat \r inside `` if the user requested igncr. +# But we know of no other shell where ac_cr would be empty at this +# point, so we can use a bashism as a fallback. +if test "x$ac_cr" = x; then + eval ac_cr=\$\'\\r\' +fi +ac_cs_awk_cr=`$AWK 'BEGIN { print "a\rb" }' </dev/null 2>/dev/null` +if test "$ac_cs_awk_cr" = "a${ac_cr}b"; then + ac_cs_awk_cr='\\r' +else + ac_cs_awk_cr=$ac_cr +fi + +echo 'BEGIN {' >"$ac_tmp/subs1.awk" && +_ACEOF + + +{ + echo "cat >conf$$subs.awk <<_ACEOF" && + echo "$ac_subst_vars" | sed 's/.*/&!$&$ac_delim/' && + echo "_ACEOF" +} >conf$$subs.sh || + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 +ac_delim_num=`echo "$ac_subst_vars" | grep -c '^'` +ac_delim='%!_!# ' +for ac_last_try in false false false false false :; do + . ./conf$$subs.sh || + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 + + ac_delim_n=`sed -n "s/.*$ac_delim\$/X/p" conf$$subs.awk | grep -c X` + if test $ac_delim_n = $ac_delim_num; then + break + elif $ac_last_try; then + as_fn_error $? "could not make $CONFIG_STATUS" "$LINENO" 5 + else + ac_delim="$ac_delim!$ac_delim _$ac_delim!! " + fi +done +rm -f conf$$subs.sh + +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +cat >>"\$ac_tmp/subs1.awk" <<\\_ACAWK && +_ACEOF +sed -n ' +h +s/^/S["/; s/!.*/"]=/ +p +g +s/^[^!]*!// +:repl +t repl +s/'"$ac_delim"'$// +t delim +:nl +h +s/\(.\{148\}\)..*/\1/ +t more1 +s/["\\]/\\&/g; s/^/"/; s/$/\\n"\\/ +p +n +b repl +:more1 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t nl +:delim +h +s/\(.\{148\}\)..*/\1/ +t more2 +s/["\\]/\\&/g; s/^/"/; s/$/"/ +p +b +:more2 +s/["\\]/\\&/g; s/^/"/; s/$/"\\/ +p +g +s/.\{148\}// +t delim +' <conf$$subs.awk | sed ' +/^[^""]/{ + N + s/\n// +} +' >>$CONFIG_STATUS || ac_write_fail=1 +rm -f conf$$subs.awk +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +_ACAWK +cat >>"\$ac_tmp/subs1.awk" <<_ACAWK && + for (key in S) S_is_set[key] = 1 + FS = "" + +} +{ + line = $ 0 + nfields = split(line, field, "@") + substed = 0 + len = length(field[1]) + for (i = 2; i < nfields; i++) { + key = field[i] + keylen = length(key) + if (S_is_set[key]) { + value = S[key] + line = substr(line, 1, len) "" value "" substr(line, len + keylen + 3) + len += length(value) + length(field[++i]) + substed = 1 + } else + len += 1 + keylen + } + + print line +} + +_ACAWK +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +if sed "s/$ac_cr//" < /dev/null > /dev/null 2>&1; then + sed "s/$ac_cr\$//; s/$ac_cr/$ac_cs_awk_cr/g" +else + cat +fi < "$ac_tmp/subs1.awk" > "$ac_tmp/subs.awk" \ + || as_fn_error $? "could not setup config files machinery" "$LINENO" 5 +_ACEOF + +# VPATH may cause trouble with some makes, so we remove sole $(srcdir), +# ${srcdir} and @srcdir@ entries from VPATH if srcdir is ".", strip leading and +# trailing colons and then remove the whole line if VPATH becomes empty +# (actually we leave an empty line to preserve line numbers). +if test "x$srcdir" = x.; then + ac_vpsub='/^[ ]*VPATH[ ]*=[ ]*/{ +h +s/// +s/^/:/ +s/[ ]*$/:/ +s/:\$(srcdir):/:/g +s/:\${srcdir}:/:/g +s/:@srcdir@:/:/g +s/^:*// +s/:*$// +x +s/\(=[ ]*\).*/\1/ +G +s/\n// +s/^[^=]*=[ ]*$// +}' +fi + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +fi # test -n "$CONFIG_FILES" + + +eval set X " :F $CONFIG_FILES " +shift +for ac_tag +do + case $ac_tag in + :[FHLC]) ac_mode=$ac_tag; continue;; + esac + case $ac_mode$ac_tag in + :[FHL]*:*);; + :L* | :C*:*) as_fn_error $? "invalid tag \`$ac_tag'" "$LINENO" 5;; + :[FH]-) ac_tag=-:-;; + :[FH]*) ac_tag=$ac_tag:$ac_tag.in;; + esac + ac_save_IFS=$IFS + IFS=: + set x $ac_tag + IFS=$ac_save_IFS + shift + ac_file=$1 + shift + + case $ac_mode in + :L) ac_source=$1;; + :[FH]) + ac_file_inputs= + for ac_f + do + case $ac_f in + -) ac_f="$ac_tmp/stdin";; + *) # Look for the file first in the build tree, then in the source tree + # (if the path is not absolute). The absolute path cannot be DOS-style, + # because $ac_f cannot contain `:'. + test -f "$ac_f" || + case $ac_f in + [\\/$]*) false;; + *) test -f "$srcdir/$ac_f" && ac_f="$srcdir/$ac_f";; + esac || + as_fn_error 1 "cannot find input file: \`$ac_f'" "$LINENO" 5;; + esac + case $ac_f in *\'*) ac_f=`$as_echo "$ac_f" | sed "s/'/'\\\\\\\\''/g"`;; esac + as_fn_append ac_file_inputs " '$ac_f'" + done + + # Let's still pretend it is `configure' which instantiates (i.e., don't + # use $as_me), people would be surprised to read: + # /* config.h. Generated by config.status. */ + configure_input='Generated from '` + $as_echo "$*" | sed 's|^[^:]*/||;s|:[^:]*/|, |g' + `' by configure.' + if test x"$ac_file" != x-; then + configure_input="$ac_file. $configure_input" + { $as_echo "$as_me:${as_lineno-$LINENO}: creating $ac_file" >&5 +$as_echo "$as_me: creating $ac_file" >&6;} + fi + # Neutralize special characters interpreted by sed in replacement strings. + case $configure_input in #( + *\&* | *\|* | *\\* ) + ac_sed_conf_input=`$as_echo "$configure_input" | + sed 's/[\\\\&|]/\\\\&/g'`;; #( + *) ac_sed_conf_input=$configure_input;; + esac + + case $ac_tag in + *:-:* | *:-) cat >"$ac_tmp/stdin" \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 ;; + esac + ;; + esac + + ac_dir=`$as_dirname -- "$ac_file" || +$as_expr X"$ac_file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$ac_file" : 'X\(//\)[^/]' \| \ + X"$ac_file" : 'X\(//\)$' \| \ + X"$ac_file" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$ac_file" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ + s//\1/ + q + } + /^X\(\/\/\)[^/].*/{ + s//\1/ + q + } + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ + s//\1/ + q + } + s/.*/./; q'` + as_dir="$ac_dir"; as_fn_mkdir_p + ac_builddir=. + +case "$ac_dir" in +.) ac_dir_suffix= ac_top_builddir_sub=. ac_top_build_prefix= ;; +*) + ac_dir_suffix=/`$as_echo "$ac_dir" | sed 's|^\.[\\/]||'` + # A ".." for each directory in $ac_dir_suffix. + ac_top_builddir_sub=`$as_echo "$ac_dir_suffix" | sed 's|/[^\\/]*|/..|g;s|/||'` + case $ac_top_builddir_sub in + "") ac_top_builddir_sub=. ac_top_build_prefix= ;; + *) ac_top_build_prefix=$ac_top_builddir_sub/ ;; + esac ;; +esac +ac_abs_top_builddir=$ac_pwd +ac_abs_builddir=$ac_pwd$ac_dir_suffix +# for backward compatibility: +ac_top_builddir=$ac_top_build_prefix + +case $srcdir in + .) # We are building in place. + ac_srcdir=. + ac_top_srcdir=$ac_top_builddir_sub + ac_abs_top_srcdir=$ac_pwd ;; + [\\/]* | ?:[\\/]* ) # Absolute name. + ac_srcdir=$srcdir$ac_dir_suffix; + ac_top_srcdir=$srcdir + ac_abs_top_srcdir=$srcdir ;; + *) # Relative name. + ac_srcdir=$ac_top_build_prefix$srcdir$ac_dir_suffix + ac_top_srcdir=$ac_top_build_prefix$srcdir + ac_abs_top_srcdir=$ac_pwd/$srcdir ;; +esac +ac_abs_srcdir=$ac_abs_top_srcdir$ac_dir_suffix + + + case $ac_mode in + :F) + # + # CONFIG_FILE + # + +_ACEOF + +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +# If the template does not know about datarootdir, expand it. +# FIXME: This hack should be removed a few years after 2.60. +ac_datarootdir_hack=; ac_datarootdir_seen= +ac_sed_dataroot=' +/datarootdir/ { + p + q +} +/@datadir@/p +/@docdir@/p +/@infodir@/p +/@localedir@/p +/@mandir@/p' +case `eval "sed -n \"\$ac_sed_dataroot\" $ac_file_inputs"` in +*datarootdir*) ac_datarootdir_seen=yes;; +*@datadir@*|*@docdir@*|*@infodir@*|*@localedir@*|*@mandir@*) + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&5 +$as_echo "$as_me: WARNING: $ac_file_inputs seems to ignore the --datarootdir setting" >&2;} +_ACEOF +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 + ac_datarootdir_hack=' + s&@datadir@&$datadir&g + s&@docdir@&$docdir&g + s&@infodir@&$infodir&g + s&@localedir@&$localedir&g + s&@mandir@&$mandir&g + s&\\\${datarootdir}&$datarootdir&g' ;; +esac +_ACEOF + +# Neutralize VPATH when `$srcdir' = `.'. +# Shell code in configure.ac might set extrasub. +# FIXME: do we really want to maintain this feature? +cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 +ac_sed_extra="$ac_vpsub +$extrasub +_ACEOF +cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 +:t +/@[a-zA-Z_][a-zA-Z_0-9]*@/!b +s|@configure_input@|$ac_sed_conf_input|;t t +s&@top_builddir@&$ac_top_builddir_sub&;t t +s&@top_build_prefix@&$ac_top_build_prefix&;t t +s&@srcdir@&$ac_srcdir&;t t +s&@abs_srcdir@&$ac_abs_srcdir&;t t +s&@top_srcdir@&$ac_top_srcdir&;t t +s&@abs_top_srcdir@&$ac_abs_top_srcdir&;t t +s&@builddir@&$ac_builddir&;t t +s&@abs_builddir@&$ac_abs_builddir&;t t +s&@abs_top_builddir@&$ac_abs_top_builddir&;t t +$ac_datarootdir_hack +" +eval sed \"\$ac_sed_extra\" "$ac_file_inputs" | $AWK -f "$ac_tmp/subs.awk" \ + >$ac_tmp/out || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + +test -z "$ac_datarootdir_hack$ac_datarootdir_seen" && + { ac_out=`sed -n '/\${datarootdir}/p' "$ac_tmp/out"`; test -n "$ac_out"; } && + { ac_out=`sed -n '/^[ ]*datarootdir[ ]*:*=/p' \ + "$ac_tmp/out"`; test -z "$ac_out"; } && + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: $ac_file contains a reference to the variable \`datarootdir' +which seems to be undefined. Please make sure it is defined" >&5 +$as_echo "$as_me: WARNING: $ac_file contains a reference to the variable \`datarootdir' +which seems to be undefined. Please make sure it is defined" >&2;} + + rm -f "$ac_tmp/stdin" + case $ac_file in + -) cat "$ac_tmp/out" && rm -f "$ac_tmp/out";; + *) rm -f "$ac_file" && mv "$ac_tmp/out" "$ac_file";; + esac \ + || as_fn_error $? "could not create $ac_file" "$LINENO" 5 + ;; + + + + esac + +done # for ac_tag + + +as_fn_exit 0 +_ACEOF +ac_clean_files=$ac_clean_files_save + +test $ac_write_fail = 0 || + as_fn_error $? "write failure creating $CONFIG_STATUS" "$LINENO" 5 + + +# configure is writing to config.log, and then calls config.status. +# config.status does its own redirection, appending to config.log. +# Unfortunately, on DOS this fails, as config.log is still kept open +# by configure, so config.status won't be able to write to it; its +# output is simply discarded. So we exec the FD to /dev/null, +# effectively closing config.log, so it can be properly (re)opened and +# appended to by config.status. When coming back to configure, we +# need to make the FD available again. +if test "$no_create" != yes; then + ac_cs_success=: + ac_config_status_args= + test "$silent" = yes && + ac_config_status_args="$ac_config_status_args --quiet" + exec 5>/dev/null + $SHELL $CONFIG_STATUS $ac_config_status_args || ac_cs_success=false + exec 5>>config.log + # Use ||, not &&, to avoid exiting from the if with $? = 1, which + # would make configure fail if this is the last instruction. + $ac_cs_success || as_fn_exit 1 +fi +if test -n "$ac_unrecognized_opts" && test "$enable_option_checking" != no; then + { $as_echo "$as_me:${as_lineno-$LINENO}: WARNING: unrecognized options: $ac_unrecognized_opts" >&5 +$as_echo "$as_me: WARNING: unrecognized options: $ac_unrecognized_opts" >&2;} +fi + diff --git a/contrib/perftcpdns/configure.in b/contrib/perftcpdns/configure.in new file mode 100644 index 0000000..35dc820 --- /dev/null +++ b/contrib/perftcpdns/configure.in @@ -0,0 +1,65 @@ +# 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. + +AC_INIT(perftcpdns.c) + +AC_PREREQ(2.13) + +AC_PROG_CC + +AC_CHECK_LIB(m, sqrt) +AC_CHECK_LIB(rt, clock_gettime) + +AC_MSG_CHECKING(epoll support) +AC_TRY_RUN([ +#include <sys/epoll.h> +int main() { + if (epoll_create(1) < 0) + return (1); + return (0); +} +], [AC_MSG_RESULT(yes)], [AC_MSG_ERROR(epoll not found)]) + +case "$host" in + *-freebsd*) + # We don't want to set -lpthread as that break + # the ability to choose threads library at final + # link time and is not valid for all architectures. + + PTHREAD= + if test "X$GCC" = "Xyes"; then + saved_cc="$CC" + CC="$CC -pthread" + AC_MSG_CHECKING(for gcc -pthread support); + AC_TRY_LINK([#include <pthread.h>], + [printf("%x\n", pthread_create);], + PTHREAD="yes" + AC_MSG_RESULT(yes), + AC_MSG_RESULT(no)) + CC="$saved_cc" + fi + if test "X$PTHREAD" != "Xyes"; then + AC_CHECK_LIB(pthread, pthread_create,, + AC_CHECK_LIB(thr, thread_create,, + AC_CHECK_LIB(c_r, pthread_create,, + AC_CHECK_LIB(c, pthread_create,, + AC_MSG_ERROR("could not find thread libraries"))))) + fi + ;; + *) + AC_CHECK_LIB(pthread, pthread_create,, + AC_CHECK_LIB(pthread, __pthread_create,, + AC_CHECK_LIB(pthread, __pthread_create_system,, + AC_CHECK_LIB(c_r, pthread_create,, + AC_CHECK_LIB(c, pthread_create,, + AC_MSG_ERROR("could not find thread libraries")))))) + ;; +esac + +AC_OUTPUT(Makefile) diff --git a/contrib/perftcpdns/perftcpdns.c b/contrib/perftcpdns/perftcpdns.c new file mode 100644 index 0000000..55486e4 --- /dev/null +++ b/contrib/perftcpdns/perftcpdns.c @@ -0,0 +1,2472 @@ +/* + * 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. + */ + +/* + * TCP DNS perf tool + * + * main parameters are -r<rate> and <server> + * standard options are 4|6 (IPv4|IPv6), rate computations, terminaisons, + * EDNS0, NOERROR|NXDOMAIN, template (for your own query), diags, + * alternate server port and UDP version. + * + * To help to crush kernels (unfortunately both client and server :-) + * this version of the tool is multi-threaded: + * - the master thread inits, monitors the activity each millisecond, + * and report results when finished + * - the connecting thread computes the date of the next connection, + * creates a socket, makes it non blocking, binds it if wanted, + * connects it and pushes it on the output epoll queue + * - the sending thread gets by epoll connected sockets, timeouts + * embryonic connections, sends queries and pushes sockets on + * the input epoll queue + * - the receiving thread gets by epoll sockets with a pending + * response, receives responses, timeouts unanswered queries, + * and recycles (by closing them) all sockets. + * + * Rate computation details: + * - the target rate is in query+response per second. + * - rating is done by the connecting thread. + * - of course the tool is always late so the target rate is never + * reached. BTW there is no attempt to internally adjust the + * effective rate to the target one: this must be by tuning + * the rate related parameters, first the -r<rate> itself. + * - at the beginning of the connecting thread iteration loop + * (second "loops" counter) the date of the due (aka next) connect() + * call is computed from the last one with 101% of the rate. + * - the due date is compared with the current date (aka now). + * - if the due is before, lateconn counter is incremented, else + * the thread sleeps for the difference, + * - the next step is to reget the current date, if it is still + * before the due date (e.g., because the sleep was interrupted) + * the first shortwait counter is incremented. + * - if it is after (common case) the number of connect calls is + * computed from the difference between now and due divided by rate, + * rounded to the next number, + * - this number of connect() calls is bounded by the -a<aggressiveness> + * parameter to avoid too many back to back new connection attempts. + * - the compconn counter is incremented, errors (other than EINPROGRESS + * from not blocking connect()) are printed. When an error is + * related to a local limit (e.g., EMFILE, EADDRNOTAVAIL or the + * internal ENOMEM) the locallimit counter is incremented. + */ + +#ifdef __linux__ +#define _GNU_SOURCE +#endif + +#include <sys/types.h> +#include <sys/epoll.h> +#include <sys/prctl.h> +#include <sys/select.h> +#include <sys/socket.h> +#include <sys/wait.h> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <ctype.h> +#include <errno.h> +#include <fcntl.h> +#include <math.h> +#include <netdb.h> +#include <pthread.h> +#include <signal.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <unistd.h> + +/* DNS defines */ + +#define NS_TYPE_A 1 +#define NS_TYPE_NS 2 +#define NS_TYPE_CNAME 5 +#define NS_TYPE_SOA 6 +#define NS_TYPE_NULL 10 +#define NS_TYPE_PTR 12 +#define NS_TYPE_MX 15 +#define NS_TYPE_TXT 16 +#define NS_TYPE_AAAA 28 +#define NS_TYPE_OPT 41 +#define NS_TYPE_DS 43 +#define NS_TYPE_RRSIG 46 +#define NS_TYPE_NSEC 47 +#define NS_TYPE_DNSKEY 48 +#define NS_TYPE_NSEC3 50 +#define NS_TYPE_NSEC3PARAM 51 +#define NS_TYPE_TSIG 250 +#define NS_TYPE_IXFR 251 +#define NS_TYPE_AXFR 252 +#define NS_TYPE_ANY 255 + +#define NS_CLASS_IN 1 +#define NS_CLASS_ANY 255 + +#define NS_OFF_ID 0 +#define NS_OFF_FLAGS 2 +#define NS_OFF_QDCOUNT 4 +#define NS_OFF_ANCOUNT 6 +#define NS_OFF_NSCOUNT 8 +#define NS_OFF_ARCOUNT 10 +#define NS_OFF_QUESTION 12 + +#define NS_FLAG_QR 0x8000U +#define NS_FLAG_AA 0x0400U +#define NS_FLAG_TC 0x0200U +#define NS_FLAG_RD 0x0100U +#define NS_FLAG_RA 0x0080U +#define NS_FLAG_AD 0x0020U +#define NS_FLAG_CD 0x0010U + +#define NS_XFLAG_DO 0x8000U + +#define NS_OPCODE_MASK 0x7000U +#define NS_OPCODE_QUERY 0 + +#define NS_RCODE_MASK 0x000fU +#define NS_RCODE_NOERROR 0 +#define NS_RCODE_FORMERR 1 +#define NS_RCODE_SERVFAIL 2 +#define NS_RCODE_NXDOMAIN 3 +#define NS_RCODE_NOIMP 4 +#define NS_RCODE_REFUSED 5 +#define NS_RCODE_LAST 6 + +/* chaining macros */ + +#define ISC_INIT(head, headl) do { \ + (head) = -1; \ + (headl) = &(head); \ +} while (0) + +#define ISC_INSERT(head, headl, elm) do { \ + (elm)->next = -1; \ + (elm)->prev = (headl); \ + *(headl) = (elm) - xlist; \ + (headl) = &((elm)->next); \ +} while (0) + +#define ISC_REMOVE(headl, elm) do { \ + if ((elm)->next != -1) \ + xlist[(elm)->next].prev = (elm)->prev; \ + else \ + (headl) = (elm)->prev; \ + *(elm)->prev = (elm)->next; \ +} while (0) + +/* + * Data structures + */ + +/* + * exchange: + * - per exchange values: + * * order (for debugging) + * * id + * * random (for debugging) + * * time-stamps + * + * sent/rcvd chain, "next to be received" on entry cache. + */ + +struct exchange { /* per exchange structure */ + int sock; /* socket descriptor */ + int next, *prev; /* chaining */ +#define X_FREE 0 +#define X_CONN 1 +#define X_READY 2 +#define X_SENT 3 + int state; /* state */ + uint16_t id; /* ID */ + uint64_t order; /* number of this exchange */ + struct timespec ts0, ts1, ts2, ts3; /* timespecs */ +}; +struct exchange *xlist; /* exchange list */ +int xlast; /* number of exchanges */ +int xconn, *xconnl; /* connecting list */ +int xready, *xreadyl; /* connected list */ +int xsent, *xsentl; /* sent list */ +int xfree, *xfreel; /* free list */ +int xused; /* next to be used list */ +pthread_mutex_t mtxconn, mtxsent, mtxfree; /* mutexes */ +uint64_t xccount; /* connected counters */ +uint64_t xscount; /* sent counters */ +uint64_t xrcount; /* received counters */ + +/* + * statictics counters and accumulators + */ + +uint64_t recverr, tooshort, locallimit; /* error counters */ +uint64_t loops[4], shortwait[3]; /* rate stats */ +uint64_t lateconn, compconn; /* rate stats (cont) */ +uint64_t badconn, collconn, badsent, collsent; /* rate stats (cont) */ +uint64_t badid, notresp; /* bad response counters */ +uint64_t rcodes[NS_RCODE_LAST + 1]; /* rcode counters */ +double dmin = 999999999.; /* minimum delay */ +double dmax = 0.; /* maximum delay */ +double dsum = 0.; /* delay sum */ +double dsumsq = 0.; /* square delay sum */ + +/* + * command line parameters + */ + +int edns0; /* EDNS0 DO flag */ +int ipversion = 0; /* IP version */ +int rate; /* rate in connections per second */ +int noreport; /* disable auto reporting */ +int report; /* delay between two reports */ +uint32_t range; /* randomization range */ +uint32_t maxrandom; /* maximum random value */ +int basecnt; /* base count */ +char *base[2]; /* bases */ +int gotnumreq = -1; /* numreq[0] was set */ +int numreq[2]; /* number of exchanges */ +int period; /* test period */ +int gotlosttime = -1; /* losttime[0] was set */ +double losttime[2] = {.5, 1.}; /* delay for a timeout */ +int gotmaxloss = -1; /* max{p}loss[0] was set */ +int maxloss[2]; /* maximum number of losses */ +double maxploss[2] = {0., 0.}; /* maximum percentage */ +char *localname; /* local address or interface */ +int aggressiveness = 1; /* back to back connections */ +int seeded; /* is a seed provided */ +unsigned int seed; /* randomization seed */ +char *templatefile; /* template file name */ +int rndoffset = -1; /* template offset (random) */ +char *diags; /* diagnostic selectors */ +char *servername; /* server */ +int ixann; /* ixann NXDOMAIN */ +int udp; /* use UDP in place of TCP */ +int minport, maxport, curport; /* port range */ + +/* + * global variables + */ + +struct sockaddr_storage localaddr; /* local socket address */ +struct sockaddr_storage serveraddr; /* server socket address */ +in_port_t port = 53; /* server socket port */ + +int epoll_ifd, epoll_ofd; /* epoll file descriptors */ +#ifndef EVENTS_CNT +#define EVENTS_CNT 16 +#endif +struct epoll_event ievents[EVENTS_CNT]; /* polled input events */ +struct epoll_event oevents[EVENTS_CNT]; /* polled output events */ +int interrupted, fatal; /* to finish flags */ + +uint8_t obuf[4098], ibuf[4098]; /* I/O buffers */ +char tbuf[4098]; /* template buffer */ + +struct timespec boot; /* the date of boot */ +struct timespec last; /* the date of last connect */ +struct timespec due; /* the date of next connect */ +struct timespec dreport; /* the date of next reporting */ +struct timespec finished; /* the date of finish */ + +/* + * template + */ + +size_t length_query; +uint8_t template_query[4096]; +size_t random_query; + +/* + * threads + */ + +pthread_t master, connector, sender, receiver; + +/* + * initialize data structures handling exchanges + */ + +void +inits(void) +{ + int idx; + + ISC_INIT(xconn, xconnl); + ISC_INIT(xready, xreadyl); + ISC_INIT(xsent, xsentl); + ISC_INIT(xfree, xfreel); + + if ((pthread_mutex_init(&mtxconn, NULL) != 0) || + (pthread_mutex_init(&mtxsent, NULL) != 0) || + (pthread_mutex_init(&mtxfree, NULL) != 0)) { + fprintf(stderr, "pthread_mutex_init failed\n"); + exit(1); + } + + epoll_ifd = epoll_create(EVENTS_CNT); + if (epoll_ifd < 0) { + perror("epoll_create(input)"); + exit(1); + } + epoll_ofd = epoll_create(EVENTS_CNT); + if (epoll_ofd < 0) { + perror("epoll_create(output)"); + exit(1); + } + + xlist = (struct exchange *) malloc(xlast * sizeof(struct exchange)); + if (xlist == NULL) { + perror("malloc(exchanges)"); + exit(1); + } + memset(xlist, 0, xlast * sizeof(struct exchange)); + + for (idx = 0; idx < xlast; idx++) + xlist[idx].sock = xlist[idx].next = -1; +} + +/* + * build a TCP DNS QUERY + */ + +void +build_template_query(void) +{ + uint8_t *p = template_query; + uint16_t v; + + /* flags */ + p += NS_OFF_FLAGS; + v = NS_FLAG_RD; + *p++ = v >> 8; + *p++ = v & 0xff; + /* qdcount */ + v = 1; + *p++ = v >> 8; + *p++ = v & 0xff; + /* ancount */ + v = 0; + *p++ = v >> 8; + *p++ = v & 0xff; + /* nscount */ + v = 0; + *p++ = v >> 8; + *p++ = v & 0xff; + /* arcount */ + v = edns0; + *p++ = v >> 8; + *p++ = v & 0xff; + /* icann.link (or ixann.link) */ + *p++ = 5; + *p++ = 'i'; + if (ixann == 0) + *p++ = 'c'; + else + *p++ = 'x'; + *p++ = 'a'; + *p++ = 'n'; + *p++ = 'n'; + *p++ = 4; + *p++ = 'l'; + *p++ = 'i'; + *p++ = 'n'; + *p++ = 'k'; + *p++ = 0; + /* type A/AAAA */ + if (ipversion == 4) + v = NS_TYPE_A; + else + v = NS_TYPE_AAAA; + *p++ = v >> 8; + *p++ = v & 0xff; + /* class IN */ + v = NS_CLASS_IN; + *p++ = v >> 8; + *p++ = v & 0xff; + /* EDNS0 OPT with DO */ + if (edns0) { + /* root name */ + *p++ = 0; + /* type OPT */ + v = NS_TYPE_OPT; + *p++ = v >> 8; + *p++ = v & 0xff; + /* class UDP length */ + v = 4096; + *p++ = v >> 8; + *p++ = v & 0xff; + /* extended rcode 0 */ + *p++ = 0; + /* version 0 */ + *p++ = 0; + /* extended flags DO */ + v = NS_XFLAG_DO; + *p++ = v >> 8; + *p++ = v & 0xff; + /* rdlength */ + v = 0; + *p++ = v >> 8; + *p++ = v & 0xff; + } + /* length */ + length_query = p - template_query; +} + +/* + * get a TCP DNS client QUERY template + * from the file given in the command line (-T<template-file>) + * and rnd offset (-O<random-offset>) + */ + +void +get_template_query(void) +{ + uint8_t *p = template_query; + int fd, cc, i, j; + + fd = open(templatefile, O_RDONLY); + if (fd < 0) { + fprintf(stderr, "open(%s): %s\n", + templatefile, strerror(errno)); + exit(2); + } + cc = read(fd, tbuf, sizeof(tbuf)); + (void) close(fd); + if (cc < 0) { + fprintf(stderr, "read(%s): %s\n", + templatefile, strerror(errno)); + exit(1); + } + if (cc < NS_OFF_QUESTION + 6) { + fprintf(stderr,"file '%s' too small\n", templatefile); + exit(2); + } + if (cc > 4096) { + fprintf(stderr,"file '%s' too large\n", templatefile); + exit(2); + } + j = 0; + for (i = 0; i < cc; i++) { + if (isspace((int) tbuf[i])) + continue; + if (!isxdigit((int) tbuf[i])) { + fprintf(stderr, + "illegal char[%d]='%c' in file '%s'\n", + i, (int) tbuf[i], templatefile); + exit(2); + } + tbuf[j] = tbuf[i]; + j++; + } + cc = j; + if ((cc & 1) != 0) { + fprintf(stderr, + "odd number of hexadecimal digits in file '%s'\n", + templatefile); + exit(2); + } + length_query = cc >> 1; + for (i = 0; i < cc; i += 2) + (void) sscanf(tbuf + i, "%02hhx", &p[i >> 1]); + if (rndoffset >= 0) + random_query = (size_t) rndoffset; + if (random_query > length_query) { + fprintf(stderr, + "random (at %zu) outside the template (length %zu)?\n", + random_query, length_query); + exit(2); + } +} + +#if 0 +/* + * randomize the value of the given field: + * - offset of the field + * - random seed (used as it when suitable) + * - returns the random value which was used + */ + +uint32_t +randomize(size_t offset, uint32_t r) +{ + uint32_t v; + + if (range == 0) + return 0; + if (range == UINT32_MAX) + return r; + if (maxrandom != 0) + while (r >= maxrandom) + r = (uint32_t) random(); + r %= range + 1; + v = r; + v += obuf[offset]; + obuf[offset] = v; + if (v < 256) + return r; + v >>= 8; + v += obuf[offset - 1]; + obuf[offset - 1] = v; + if (v < 256) + return r; + v >>= 8; + v += obuf[offset - 2]; + obuf[offset - 2] = v; + if (v < 256) + return r; + v >>= 8; + v += obuf[offset - 3]; + obuf[offset - 3] = v; + return r; +} +#endif + +/* + * flush/timeout connect + */ + +void +flushconnect(void) +{ + struct exchange *x; + struct timespec now; + int idx = xconn; + int cnt = 10; + double waited; + + if (clock_gettime(CLOCK_REALTIME, &now) < 0) { + perror("clock_gettime(flushconnect)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return; + } + + while (--cnt >= 0) { + if (idx < 0) + return; + x = xlist + idx; + idx = x->next; + if (x->state != X_CONN) + abort(); + /* check for a timed-out connection */ + waited = now.tv_sec - x->ts0.tv_sec; + waited += (now.tv_nsec - x->ts0.tv_nsec) / 1e9; + if (waited < losttime[0]) + return; + /* garbage collect timed-out connections */ + if (pthread_mutex_lock(&mtxconn) != 0) { + fprintf(stderr, "pthread_mutex_lock(flushconnect)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return; + } + ISC_REMOVE(xconnl, x); + if (pthread_mutex_unlock(&mtxconn) != 0) { + fprintf(stderr, "pthread_mutex_unlock(flushconnect)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return; + } + (void) close(x->sock); + x->sock = -1; + collconn++; + if (pthread_mutex_lock(&mtxfree) != 0) { + fprintf(stderr, "pthread_mutex_lock(flushconnect)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return; + } + x->state = X_FREE; + ISC_INSERT(xfree, xfreel, x); + if (pthread_mutex_unlock(&mtxfree) != 0) { + fprintf(stderr, "pthread_mutex_unlock(flushconnect)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return; + } + } +} + +/* + * poll connected + */ + +void +pollconnect(int topoll) +{ + struct exchange *x; + int evn, idx, err; + socklen_t len = sizeof(int); + + for (evn = 0; evn < topoll; evn++) { + idx = oevents[evn].data.fd; + x = xlist + idx; + if (x->state != X_CONN) + continue; + if (oevents[evn].events == 0) + continue; + if (pthread_mutex_lock(&mtxconn) != 0) { + fprintf(stderr, "pthread_mutex_lock(pollconnect)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return; + } + ISC_REMOVE(xconnl, x); + if (pthread_mutex_unlock(&mtxconn) != 0) { + fprintf(stderr, "pthread_mutex_unlock(pollconnect)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return; + } + oevents[evn].events = 0; + if ((getsockopt(x->sock, SOL_SOCKET, SO_ERROR, + &err, &len) < 0) || + (err != 0)) { + (void) close(x->sock); + x->sock = -1; + badconn++; + if (pthread_mutex_lock(&mtxfree) != 0) { + fprintf(stderr, + "pthread_mutex_lock(pollconnect)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return; + } + x->state = X_FREE; + ISC_INSERT(xfree, xfreel, x); + if (pthread_mutex_unlock(&mtxfree) != 0) { + fprintf(stderr, + "pthread_mutex_unlock(pollconnect)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return; + } + continue; + } + x->state = X_READY; + ISC_INSERT(xready, xreadyl, x); + } +} + +/* + * send the TCP DNS QUERY + */ + +int +sendquery(struct exchange *x) +{ + ssize_t ret; + size_t off; + + if (udp) + off = 0; + else { + off = 2; + /* message length */ + obuf[0] = length_query >> 8; + obuf[1]= length_query & 0xff; + } + /* message from template */ + memcpy(obuf + off, template_query, length_query); + /* ID */ + memcpy(obuf + off + NS_OFF_ID, &x->id, 2); +#if 0 + /* random */ + if (random_query > 0) + x->rnd = randomize(random_query + off, x->rnd); +#endif + /* timestamp */ + errno = 0; + ret = clock_gettime(CLOCK_REALTIME, &x->ts2); + if (ret < 0) { + perror("clock_gettime(send)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return -errno; + } + ret = send(x->sock, obuf, length_query + off, 0); + if ((size_t) ret == length_query + off) + return 0; + return -errno; +} + +/* + * poll ready and send + */ + +void +pollsend(void) +{ + struct exchange *x; + int idx = xready; + struct epoll_event ev; + + memset(&ev, 0, sizeof(ev)); + ev.events = EPOLLIN | EPOLLET | EPOLLONESHOT; + for (;;) { + if (idx < 0) + return; + x = xlist + idx; + ev.data.fd = idx; + idx = x->next; + ISC_REMOVE(xreadyl, x); + if (sendquery(x) < 0) { + (void) close(x->sock); + x->sock = -1; + badsent++; + if (pthread_mutex_lock(&mtxfree) != 0) { + fprintf(stderr, + "pthread_mutex_lock(pollsend)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return; + } + x->state = X_FREE; + ISC_INSERT(xfree, xfreel, x); + if (pthread_mutex_unlock(&mtxfree) != 0) { + fprintf(stderr, + "pthread_mutex_unlock(pollsend)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return; + } + continue; + } + xscount++; + if (pthread_mutex_lock(&mtxsent) != 0) { + fprintf(stderr, "pthread_mutex_lock(pollsend)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return; + } + x->state = X_SENT; + ISC_INSERT(xsent, xsentl, x); + if (pthread_mutex_unlock(&mtxsent) != 0) { + fprintf(stderr, "pthread_mutex_unlock(pollsend)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return; + } + if (epoll_ctl(epoll_ifd, EPOLL_CTL_ADD, x->sock, &ev) < 0) { + perror("epoll_ctl(add input)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return; + } + } +} + +/* + * receive a TCP DNS RESPONSE + */ + +void +receiveresp(struct exchange *x) +{ + struct timespec now; + ssize_t cc; + size_t off; + uint16_t v; + double delta; + + cc = recv(x->sock, ibuf, sizeof(ibuf), 0); + if (cc < 0) { + if ((errno == EAGAIN) || + (errno == EWOULDBLOCK) || + (errno == EINTR) || + (errno == ECONNRESET)) { + recverr++; + return; + } + perror("recv"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return; + } + if (udp) + off = 0; + else + off = 2; + /* enforce a reasonable length */ + if ((size_t) cc < length_query + off) { + tooshort++; + return; + } + /* must match the ID */ + if (memcmp(ibuf + off + NS_OFF_ID, &x->id, 2) != 0) { + badid++; + return; + } + /* must be a response */ + memcpy(&v, ibuf + off + NS_OFF_FLAGS, 2); + v = ntohs(v); + if ((v & NS_FLAG_QR) == 0) { + notresp++; + return; + } + if (clock_gettime(CLOCK_REALTIME, &now) < 0) { + perror("clock_gettime(receive)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return; + } + /* got it: update stats */ + xrcount++; + x->ts3 = now; + delta = x->ts3.tv_sec - x->ts2.tv_sec; + delta += (x->ts3.tv_nsec - x->ts2.tv_nsec) / 1e9; + if (delta < dmin) + dmin = delta; + if (delta > dmax) + dmax = delta; + dsum += delta; + dsumsq += delta * delta; + v &= NS_RCODE_MASK; + if (v >= NS_RCODE_LAST) + v = NS_RCODE_LAST; + rcodes[v] += 1; +} + +/* + * flush/timeout receive + */ + +void +flushrecv(void) +{ + struct exchange *x; + struct timespec now; + int idx = xsent; + int cnt = 5; + double waited; + + if (clock_gettime(CLOCK_REALTIME, &now) < 0) { + perror("clock_gettime(receive)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return; + } + + while (--cnt >= 0) { + if (idx < 0) + return; + x = xlist + idx; + idx = x->next; + if (x->state != X_SENT) + abort(); + /* check for a timed-out exchange */ + waited = now.tv_sec - x->ts2.tv_sec; + waited += (now.tv_nsec - x->ts2.tv_nsec) / 1e9; + if (waited < losttime[1]) + return; + /* garbage collect timed-out exchange */ + if (pthread_mutex_lock(&mtxsent) != 0) { + fprintf(stderr, "pthread_mutex_lock(flushrecv)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return; + } + ISC_REMOVE(xsentl, x); + if (pthread_mutex_unlock(&mtxsent) != 0) { + fprintf(stderr, "pthread_mutex_unlock(flushrecv)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return; + } + (void) close(x->sock); + x->sock = -1; + collsent++; + if (pthread_mutex_lock(&mtxfree) != 0) { + fprintf(stderr, "pthread_mutex_lock(flushrecv)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return; + } + x->state = X_FREE; + ISC_INSERT(xfree, xfreel, x); + if (pthread_mutex_unlock(&mtxfree) != 0) { + fprintf(stderr, "pthread_mutex_unlock(flushrecv)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return; + } + } +} + +/* + * poll receive + */ + +void +pollrecv(int topoll) +{ + struct exchange *x; + int evn, idx; + + for (evn = 0; evn < topoll; evn++) { + idx = ievents[evn].data.fd; + x = xlist + idx; + if (x->state != X_SENT) + continue; + if (ievents[evn].events == 0) + continue; + if (pthread_mutex_lock(&mtxsent) != 0) { + fprintf(stderr, "pthread_mutex_lock(pollrecv)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return; + } + ISC_REMOVE(xsentl, x); + if (pthread_mutex_unlock(&mtxsent) != 0) { + fprintf(stderr, "pthread_mutex_unlock(pollrecv)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return; + } + receiveresp(x); + ievents[evn].events = 0; + (void) close(x->sock); + x->sock = -1; + if (pthread_mutex_lock(&mtxfree) != 0) { + fprintf(stderr, "pthread_mutex_lock(pollrecv)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return; + } + x->state = X_FREE; + ISC_INSERT(xfree, xfreel, x); + if (pthread_mutex_unlock(&mtxfree) != 0) { + fprintf(stderr, "pthread_mutex_unlock(pollrecv)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return; + } + } +} + +/* + * get the TCP DNS socket descriptor (IPv4) + */ + +int +getsock4(void) +{ + int sock; + int flags; + + errno = 0; + if (udp) + sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); + else + sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); + if (sock < 0) + return -errno; + + /* make the socket descriptor not blocking */ + flags = fcntl(sock, F_GETFL, 0); + if (flags == -1) { + (void) close(sock); + return -errno; + } + if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) == -1) { + (void) close(sock); + return -errno; + } + + /* bind if wanted */ + if (localname != NULL) { + if (curport) { + struct sockaddr_in *l4; + + l4 = (struct sockaddr_in *) &localaddr; + l4->sin_port = htons((uint16_t) curport); + curport++; + if (curport > maxport) + curport = minport; + } + if (bind(sock, + (struct sockaddr *) &localaddr, + sizeof(struct sockaddr_in)) < 0) { + (void) close(sock); + return -errno; + } + } + + /* connect */ + if (connect(sock, + (struct sockaddr *) &serveraddr, + sizeof(struct sockaddr_in)) < 0) { + if (errno != EINPROGRESS) { + (void) close(sock); + return -errno; + } + } + return sock; +} + +/* + * connect the TCP DNS QUERY (IPv4) + */ + +int +connect4(void) +{ + struct exchange *x; + int ret; + int idx; + struct epoll_event ev; + + ret = clock_gettime(CLOCK_REALTIME, &last); + if (ret < 0) { + perror("clock_gettime(connect)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return -errno; + } + + if (xfree >= 0) { + idx = xfree; + x = xlist + idx; + ret = pthread_mutex_lock(&mtxfree); + if (ret != 0) { + fprintf(stderr, "pthread_mutex_lock(connect4)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return -ret; + } + ISC_REMOVE(xfreel, x); + ret = pthread_mutex_unlock(&mtxfree); + if (ret != 0) { + fprintf(stderr, "pthread_mutex_unlock(connect4)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return -ret; + } + } else if (xused < xlast) { + idx = xused; + x = xlist + idx; + xused++; + } else + return -ENOMEM; + + if ((x->state != X_FREE) || (x->sock != -1)) + abort(); + + memset(x, 0, sizeof(*x)); + memset(&ev, 0, sizeof(ev)); + x->next = -1; + x->prev = NULL; + x->ts0 = last; + x->sock = getsock4(); + if (x->sock < 0) { + int result = x->sock; + + x->sock = -1; + ret = pthread_mutex_lock(&mtxfree); + if (ret != 0) { + fprintf(stderr, "pthread_mutex_lock(connect4)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return -ret; + } + ISC_INSERT(xfree, xfreel, x); + ret = pthread_mutex_unlock(&mtxfree); + if (ret != 0) { + fprintf(stderr, "pthread_mutex_unlock(connect4)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return -ret; + } + return result; + } + ret = pthread_mutex_lock(&mtxconn); + if (ret != 0) { + fprintf(stderr, "pthread_mutex_lock(connect4)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return -ret; + } + x->state = X_CONN; + ISC_INSERT(xconn, xconnl, x); + ret = pthread_mutex_unlock(&mtxconn); + if (ret != 0) { + fprintf(stderr, "pthread_mutex_unlock(connect4)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return -ret; + } + ev.events = EPOLLOUT | EPOLLET | EPOLLONESHOT; + ev.data.fd = idx; + if (epoll_ctl(epoll_ofd, EPOLL_CTL_ADD, x->sock, &ev) < 0) { + perror("epoll_ctl(add output)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return -errno; + } + x->order = xccount++; + x->id = (uint16_t) random(); +#if 0 + if (random_query > 0) + x->rnd = (uint32_t) random(); +#endif + return idx; +} + +/* + * get the TCP DNS socket descriptor (IPv6) + */ + +int +getsock6(void) +{ + int sock; + int flags; + + errno = 0; + if (udp) + sock = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP); + else + sock = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP); + if (sock < 0) + return -errno; + + /* make the socket descriptor not blocking */ + flags = fcntl(sock, F_GETFL, 0); + if (flags == -1) { + (void) close(sock); + return -errno; + } + if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) == -1) { + (void) close(sock); + return -errno; + } + + /* bind if wanted */ + if (localname != NULL) { + if (curport) { + struct sockaddr_in6 *l6; + + l6 = (struct sockaddr_in6 *) &localaddr; + l6->sin6_port = htons((uint16_t) curport); + curport++; + if (curport > maxport) + curport = minport; + } + if (bind(sock, + (struct sockaddr *) &localaddr, + sizeof(struct sockaddr_in6)) < 0) { + (void) close(sock); + return -errno; + } + } + + /* connect */ + if (connect(sock, + (struct sockaddr *) &serveraddr, + sizeof(struct sockaddr_in6)) < 0) { + if (errno != EINPROGRESS) { + (void) close(sock); + return -errno; + } + } + return sock; +} + +/* + * connect the TCP DNS QUERY (IPv6) + */ + +int +connect6(void) +{ + struct exchange *x; + int ret; + int idx; + struct epoll_event ev; + + ret = clock_gettime(CLOCK_REALTIME, &last); + if (ret < 0) { + perror("clock_gettime(connect)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return -errno; + } + + if (xfree >= 0) { + idx = xfree; + x = xlist + idx; + ret = pthread_mutex_lock(&mtxfree); + if (ret != 0) { + fprintf(stderr, "pthread_mutex_lock(connect6)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return -ret; + } + ISC_REMOVE(xfreel, x); + ret = pthread_mutex_unlock(&mtxfree); + if (ret != 0) { + fprintf(stderr, "pthread_mutex_unlock(connect6)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return -ret; + } + } else if (xused < xlast) { + idx = xused; + x = xlist + idx; + xused++; + } else + return -ENOMEM; + + memset(x, 0, sizeof(*x)); + memset(&ev, 0, sizeof(ev)); + x->next = -1; + x->prev = NULL; + x->ts0 = last; + x->sock = getsock6(); + if (x->sock < 0) { + int result = x->sock; + + x->sock = -1; + ret = pthread_mutex_lock(&mtxfree); + if (ret != 0) { + fprintf(stderr, "pthread_mutex_lock(connect6)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return -ret; + } + ISC_INSERT(xfree, xfreel, x); + ret = pthread_mutex_unlock(&mtxfree); + if (ret != 0) { + fprintf(stderr, "pthread_mutex_unlock(connect6)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return -ret; + } + return result; + } + ret = pthread_mutex_lock(&mtxconn); + if (ret != 0) { + fprintf(stderr, "pthread_mutex_lock(connect6)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return -ret; + } + x->state = X_CONN; + ISC_INSERT(xconn, xconnl, x); + ret = pthread_mutex_unlock(&mtxconn); + if (ret != 0) { + fprintf(stderr, "pthread_mutex_unlock(connect6)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return -ret; + } + ev.events = EPOLLOUT | EPOLLET | EPOLLONESHOT; + ev.data.fd = idx; + if (epoll_ctl(epoll_ofd, EPOLL_CTL_ADD, x->sock, &ev) < 0) { + perror("epoll_ctl(add output)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + return -errno; + } + x->order = xccount++; + x->id = (uint16_t) random(); +#if 0 + if (random_query > 0) + x->rnd = (uint32_t) random(); +#endif + return idx; +} + +/* + * connector working routine + */ + +void * +connecting(void *dummy) +{ + struct timespec now, ts; + int ret; + int i; + char name[16]; + + dummy = dummy; + + /* set conn-name */ + memset(name, 0, sizeof(name)); + ret = prctl(PR_GET_NAME, name, 0, 0, 0); + if (ret < 0) + perror("prctl(PR_GET_NAME)"); + else { + memmove(name + 5, name, 11); + memcpy(name, "conn-", 5); + ret = prctl(PR_SET_NAME, name, 0, 0, 0); + if (ret < 0) + perror("prctl(PR_SET_NAME"); + } + + for (;;) { + if (fatal) + break; + + loops[1]++; + + /* compute the delay for the next connection */ + if (clock_gettime(CLOCK_REALTIME, &now) < 0) { + perror("clock_gettime(connecting)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + break; + } + + due = last; + if (rate == 1) + due.tv_sec += 1; + else + due.tv_nsec += 1010000000 / rate; + while (due.tv_nsec >= 1000000000) { + due.tv_sec += 1; + due.tv_nsec -= 1000000000; + } + ts = due; + ts.tv_sec -= now.tv_sec; + ts.tv_nsec -= now.tv_nsec; + while (ts.tv_nsec < 0) { + ts.tv_sec -= 1; + ts.tv_nsec += 1000000000; + } + /* the connection was already due? */ + if (ts.tv_sec < 0) { + ts.tv_sec = ts.tv_nsec = 0; + lateconn++; + } else { + /* wait until */ + ret = clock_nanosleep(CLOCK_REALTIME, 0, &ts, NULL); + if (ret != 0) { + if (ret == EINTR) + continue; + fprintf(stderr, "clock_nanosleep: %s\n", + strerror(ret)); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + break; + } + } + + /* compute how many connections to open */ + if (clock_gettime(CLOCK_REALTIME, &now) < 0) { + perror("clock_gettime(connecting)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + break; + } + + if ((now.tv_sec > due.tv_sec) || + ((now.tv_sec == due.tv_sec) && + (now.tv_nsec >= due.tv_nsec))) { + double toconnect; + + toconnect = (now.tv_nsec - due.tv_nsec) / 1e9; + toconnect += now.tv_sec - due.tv_sec; + toconnect *= rate; + toconnect++; + if (toconnect > (double) aggressiveness) + i = aggressiveness; + else + i = (int) toconnect; + compconn += i; + /* open connections */ + while (i-- > 0) { + if (ipversion == 4) + ret = connect4(); + else + ret = connect6(); + if (ret < 0) { + if ((ret == -EAGAIN) || + (ret == -EWOULDBLOCK) || + (ret == -ENOBUFS) || + (ret == -ENFILE) || + (ret == -EMFILE) || + (ret == -EADDRNOTAVAIL) || + (ret == -ENOMEM)) + locallimit++; + fprintf(stderr, + "connect: %s\n", + strerror(-ret)); + break; + } + } + } else + /* there was no connection to open */ + shortwait[0]++; + } + + return NULL; +} + +/* + * sender working routine + */ + +void * +sending(void *dummy) +{ + int ret; + int nfds; + char name[16]; + + dummy = dummy; + + /* set send-name */ + memset(name, 0, sizeof(name)); + ret = prctl(PR_GET_NAME, name, 0, 0, 0); + if (ret < 0) + perror("prctl(PR_GET_NAME)"); + else { + memmove(name + 5, name, 11); + memcpy(name, "send-", 5); + ret = prctl(PR_SET_NAME, name, 0, 0, 0); + if (ret < 0) + perror("prctl(PR_SET_NAME"); + } + + for (;;) { + if (fatal) + break; + + loops[2]++; + + /* epoll_wait() */ + memset(oevents, 0, sizeof(oevents)); + nfds = epoll_wait(epoll_ofd, oevents, EVENTS_CNT, 1); + if (nfds < 0) { + if (errno == EINTR) + continue; + perror("epoll_wait(output)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + break; + } + + /* connection(s) to finish */ + if (nfds == 0) + shortwait[1]++; + else + pollconnect(nfds); + if (fatal) + break; + flushconnect(); + if (fatal) + break; + + /* packet(s) to send */ + pollsend(); + if (fatal) + break; + } + + return NULL; +} + +/* + * receiver working routine + */ + +void * +receiving(void *dummy) +{ + int ret; + int nfds; + char name[16]; + + dummy = dummy; + + /* set recv-name */ + memset(name, 0, sizeof(name)); + ret = prctl(PR_GET_NAME, name, 0, 0, 0); + if (ret < 0) + perror("prctl(PR_GET_NAME)"); + else { + memmove(name + 5, name, 11); + memcpy(name, "recv-", 5); + ret = prctl(PR_SET_NAME, name, 0, 0, 0); + if (ret < 0) + perror("prctl(PR_SET_NAME"); + } + + for (;;) { + if (fatal) + break; + + loops[3]++; + + /* epoll_wait() */ + memset(ievents, 0, sizeof(ievents)); + nfds = epoll_wait(epoll_ifd, ievents, EVENTS_CNT, 1); + if (nfds < 0) { + if (errno == EINTR) + continue; + perror("epoll_wait(input)"); + fatal = 1; + (void) pthread_kill(master, SIGTERM); + break; + } + + /* packet(s) to receive */ + if (nfds == 0) + shortwait[2]++; + else + pollrecv(nfds); + if (fatal) + break; + flushrecv(); + if (fatal) + break; + } + + return NULL; +} + +/* + * get the server socket address from the command line: + * - flags: inherited from main, 0 or AI_NUMERICHOST (for literals) + */ + +void +getserveraddr(const int flags) +{ + struct addrinfo hints, *res; + int ret; + + memset(&hints, 0, sizeof(hints)); + if (ipversion == 4) + hints.ai_family = AF_INET; + else + hints.ai_family = AF_INET6; + if (udp) { + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = IPPROTO_UDP; + } else { + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + } + hints.ai_flags = AI_ADDRCONFIG | flags; + + ret = getaddrinfo(servername, NULL, &hints, &res); + if (ret != 0) { + fprintf(stderr, "bad server=%s: %s\n", + servername, gai_strerror(ret)); + exit(2); + } + if (res->ai_next != NULL) { + fprintf(stderr, "ambiguous server=%s\n", servername); + exit(2); + } + memcpy(&serveraddr, res->ai_addr, res->ai_addrlen); + freeaddrinfo(res); + if (ipversion == 4) + ((struct sockaddr_in *)&serveraddr)->sin_port = htons(port); + else + ((struct sockaddr_in6 *)&serveraddr)->sin6_port = htons(port); +} + +/* + * get the local socket address from the command line + */ + +void +getlocaladdr(void) +{ + struct addrinfo hints, *res; + int ret; + + memset(&hints, 0, sizeof(hints)); + if (ipversion == 4) + hints.ai_family = AF_INET; + else + hints.ai_family = AF_INET6; + if (udp) { + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = IPPROTO_UDP; + } else { + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + } + hints.ai_flags = AI_ADDRCONFIG; + + ret = getaddrinfo(localname, NULL, &hints, &res); + if (ret != 0) { + fprintf(stderr, + "bad -l<local-addr=%s>: %s\n", + localname, + gai_strerror(ret)); + exit(2); + } + /* refuse multiple addresses */ + if (res->ai_next != NULL) { + fprintf(stderr, + "ambiguous -l<local-addr=%s>\n", + localname); + exit(2); + } + memcpy(&localaddr, res->ai_addr, res->ai_addrlen); + freeaddrinfo(res); +} + +/* + * intermediate reporting + * (note: an in-transit packet can be reported as lost) + */ + +void +reporting(void) +{ + dreport.tv_sec += report; + + if (xccount != 0) { + printf("connect: %llu, sent: %llu, received: %llu " + "(embryonics: %lld, drops: %lld)", + (unsigned long long) xccount, + (unsigned long long) xscount, + (unsigned long long) xrcount, + (long long) (xccount - xscount), + (long long) (xscount - xrcount)); + if (xrcount != 0) { + double avg; + + avg = dsum / xrcount; + printf(" average: %.3f ms", avg * 1e3); + } + } + printf("\n"); +} + +/* + * SIGCHLD handler + */ + +void +reapchild(int sig) +{ + int status; + + sig = sig; + while (wait3(&status, WNOHANG, NULL) > 0) + /* continue */; +} + +/* + * SIGINT handler + */ + +void +interrupt(int sig) +{ + sig = sig; + interrupted = 1; +} + +/* + * SIGTERM handler + */ + +void +terminate(int sig) +{ + sig = sig; + fatal = 1; +} + +/* + * '-v' handler + */ + +void +version(void) +{ + fprintf(stderr, "version 0.01\n"); +} + +/* + * usage (from the wiki) + */ + +void +usage(void) +{ + fprintf(stderr, "%s", +"perftcpdns [-huvX0] [-4|-6] [-r<rate>] [-t<report>] [-p<test-period>]\n" +" [-n<num-request>]* [-d<lost-time>]* [-D<max-loss>]* [-T<template-file>]\n" +" [-l<local-addr>] [-L<local-port>]* [-a<aggressiveness>] [-s<seed>]\n" +" [-M<memory>] [-x<diagnostic-selector>] [-P<port>] server\n" +"\f\n" +"The server argument is the name/address of the DNS server to contact.\n" +"\n" +"Options:\n" +"-0: Add EDNS0 option with DO flag.\n" +"-4: TCP/IPv4 operation (default). This is incompatible with the -6 option.\n" +"-6: TCP/IPv6 operation. This is incompatible with the -4 option.\n" +"-a<aggressiveness>: When the target sending rate is not yet reached,\n" +" control how many connections are initiated before the next pause.\n" +"-d<lost-time>: Specify the time after which a connection or a query is\n" +" treated as having been lost. The value is given in seconds and\n" +" may contain a fractional component. The default is 1 second.\n" +"-h: Print this help.\n" +"-l<local-addr>: Specify the local hostname/address to use when\n" +" communicating with the server.\n" +"-L<local-port>: Specify the (minimal and maximal) local port number\n" +"-M<memory>: Size of the tables (default 60000)\n" +"-P<port>: Specify an alternate (i.e., not 53) port\n" +"-r<rate>: Initiate <rate> TCP DNS connections per second. A periodic\n" +" report is generated showing the number of exchanges which were not\n" +" completed, as well as the average response latency. The program\n" +" continues until interrupted, at which point a final report is\n" +" generated.\n" +"-s<seed>: Specify the seed for randomization, making it repeatable.\n" +"-t<report>: Delay in seconds between two periodic reports.\n" +"-T<template-file>: The name of a file containing the template to use\n" +" as a stream of hexadecimal digits.\n" +"-u: Use UDP in place of TCP.\n" +"-v: Report the version number of this program.\n" +"-X: change default template to get NXDOMAIN responses.\n" +"-x<diagnostic-selector>: Include extended diagnostics in the output.\n" +" <diagnostic-selector> is a string of single-keywords specifying\n" +" the operations for which verbose output is desired. The selector\n" +" keyletters are:\n" +" * 'a': print the decoded command line arguments\n" +" * 'e': print the exit reason\n" +" * 'i': print rate processing details\n" +" * 'T': when finished, print templates\n" +"\n" +"Stopping conditions:\n" +"-D<max-loss>: Abort the test if more than <max-loss> connections or\n" +" queries have been lost. If <max-loss> includes the suffix '%', it\n" +" specifies a maximum percentage of losses before stopping.\n" +" In this case, testing of the threshold begins after 10\n" +" connections/responses have been expected to be accepted/received.\n" +"-n<num-request>: Initiate <num-request> transactions. No report is\n" +" generated until all transactions have been initiated/waited-for,\n" +" after which a report is generated and the program terminates.\n" +"-p<test-period>: Send requests for the given test period, which is\n" +" specified in the same manner as -d. This can be used as an\n" +" alternative to -n, or both options can be given, in which case the\n" +" testing is completed when either limit is reached.\n" +"\n" +"Errors:\n" +"- locallimit: reached to local system limits when sending a message.\n" +"- badconn: connection failed (from getsockopt(SO_ERROR))\n" +"- collconn: connect() timed out\n" +"- badsent: send() failed\n" +"- callsent: timed out waiting from a response\n" +"- recverr: recv() system call failed\n" +"- tooshort: received a too short message\n" +"- badid: the id mismatches between the query and the response\n" +"- notresp: doesn't receive a response\n" +"Rate stats:\n" +"- loops: number of thread loop iterations\n" +"- shortwait: no direct activity in a thread iteration\n" +"- compconn: computed number of connect() calls\n" +"- lateconn: connect() already dued when computing delay to the next one\n" +"\n" +"Exit status:\n" +"The exit status is:\n" +"0 on complete success.\n" +"1 for a general error.\n" +"2 if an error is found in the command line arguments.\n" +"3 if there are no general failures in operation, but one or more\n" +" exchanges are not successfully completed.\n"); +} + +/* + * main function / entry point + */ + +int +main(const int argc, char * const argv[]) +{ + int opt, flags = 0, ret, i; + long long r; + char *pc; + double d; + extern char *optarg; + extern int optind; + +#define OPTIONS "hv46u0XM:r:t:R:b:n:p:d:D:l:L:a:s:T:O:x:P:" + + /* decode options */ + while ((opt = getopt(argc, argv, OPTIONS)) != -1) + switch (opt) { + case 'h': + usage(); + exit(0); + + case 'u': + udp = 1; + break; + + case 'v': + version(); + exit(0); + + case '0': + edns0 = 1; + break; + + case '4': + if (ipversion == 6) { + fprintf(stderr, "IP version already set to 6\n"); + usage(); + exit(2); + } + ipversion = 4; + break; + + case '6': + if (ipversion == 4) { + fprintf(stderr, "IP version already set to 4\n"); + usage(); + exit(2); + } + ipversion = 6; + break; + + case 'X': + ixann = 1; + break; + + case 'M': + xlast = atoi(optarg); + if (xlast <= 1000) { + fprintf(stderr, "memory must be greater than 1000\n"); + usage(); + exit(2); + } + break; + + case 'r': + rate = atoi(optarg); + if (rate <= 0) { + fprintf(stderr, "rate must be a positive integer\n"); + usage(); + exit(2); + } + break; + + case 't': + report = atoi(optarg); + if (report <= 0) { + fprintf(stderr, "report must be a positive integer\n"); + usage(); + exit(2); + } + break; + + case 'R': + r = atoll(optarg); + if (r < 0) { + fprintf(stderr, + "range must not be a negative integer\n"); + usage(); + exit(2); + } + range = (uint32_t) r; + if ((range != 0) && (range != UINT32_MAX)) { + uint32_t s = range + 1; + uint64_t b = UINT32_MAX + 1, m; + + m = (b / s) * s; + if (m == b) + maxrandom = 0; + else + maxrandom = (uint32_t) m; + } + break; + + case 'b': + if (basecnt > 1) { + fprintf(stderr, "too many bases\n"); + usage(); + exit(2); + } + base[basecnt] = optarg; + /* decodebase(); */ + basecnt++; + break; + + case 'n': + noreport = 1; + gotnumreq++; + if (gotnumreq > 1) { + fprintf(stderr, "too many num-request's\n"); + usage(); + exit(2); + } + numreq[gotnumreq] = atoi(optarg); + if ((numreq[gotnumreq] < 0) || + ((numreq[gotnumreq] == 0) && (gotnumreq == 1))) { + fprintf(stderr, + "num-request must be a positive integer\n"); + usage(); + exit(2); + } + break; + + case 'p': + noreport = 1; + period = atoi(optarg); + if (period <= 0) { + fprintf(stderr, + "test-period must be a positive integer\n"); + usage(); + exit(2); + } + break; + + case 'd': + gotlosttime++; + if (gotlosttime > 1) { + fprintf(stderr, "too many lost-time's\n"); + usage(); + exit(2); + } + d = atof(optarg); + if ((d < 0.) || ((d == 0.) && (gotlosttime == 1))) { + fprintf(stderr, + "lost-time must be a positive number\n"); + usage(); + exit(2); + } + if (d > 0.) + losttime[gotlosttime] = d; + break; + + case 'D': + noreport = 1; + gotmaxloss++; + if (gotmaxloss > 1) { + fprintf(stderr, "too many max-loss's\n"); + usage(); + exit(2); + } + pc = strchr(optarg, '%'); + if (pc != NULL) { + *pc = '\0'; + maxploss[gotmaxloss] = atof(optarg); + if ((maxploss[gotmaxloss] < 0) || + (maxploss[gotmaxloss] >= 100)) { + fprintf(stderr, + "invalid max-loss percentage\n"); + usage(); + exit(2); + } + } else { + maxloss[gotmaxloss] = atoi(optarg); + if ((maxloss[gotmaxloss] < 0) || + ((maxloss[gotmaxloss] == 0) && + (gotmaxloss == 1))) { + fprintf(stderr, + "max-loss must be a " + "positive integer\n"); + usage(); + exit(2); + } + } + break; + + case 'l': + localname = optarg; + break; + + case 'L': + i = atoi(optarg); + if ((i <= 0) || (i >65535)) { + fprintf(stderr, + "local-port must be a small positive integer\n"); + usage(); + exit(2); + } + if (maxport != 0) { + fprintf(stderr, "too many local-port's\n"); + usage(); + exit(2); + } + if (curport == 0) + minport = curport = i; + else + maxport = i; + break; + + case 'a': + aggressiveness = atoi(optarg); + if (aggressiveness <= 0) { + fprintf(stderr, + "aggressiveness must be a positive integer\n"); + usage(); + exit(2); + } + break; + + case 's': + seeded = 1; + seed = (unsigned int) atol(optarg); + break; + + case 'T': + if (templatefile != NULL) { + fprintf(stderr, "template-file is already set\n"); + usage(); + exit(2); + } + templatefile = optarg; + break; + + case 'O': + rndoffset = atoi(optarg); + if (rndoffset < 14) { + fprintf(stderr, + "random-offset must be greater than 14\n"); + usage(); + exit(2); + } + break; + + case 'x': + diags = optarg; + break; + + case 'P': + i = atoi(optarg); + if ((i <= 0) || (i > 65535)) { + fprintf(stderr, + "port must be a positive short integer\n"); + usage(); + exit(2); + } + port = (in_port_t) i; + break; + + default: + usage(); + exit(2); + } + + /* adjust some global variables */ + if (ipversion == 0) + ipversion = 4; + if (rate == 0) + rate = 100; + if (xlast == 0) + xlast = 60000; + if (noreport == 0) + report = 1; + if ((curport != 0) && (maxport == 0)) + maxport = 65535; + + /* when required, print the internal view of the command line */ + if ((diags != NULL) && (strchr(diags, 'a') != NULL)) { + if (udp) + printf("UDP "); + printf("IPv%d", ipversion); + printf(" rate=%d", rate); + if (edns0 != 0) + printf(" EDNS0"); + if (report != 0) + printf(" report=%d", report); + if (range != 0) { + if (strchr(diags, 'r') != NULL) + printf(" range=0..%d [0x%x]", + range, + (unsigned int) maxrandom); + else + printf(" range=0..%d", range); + } + if (basecnt != 0) + for (i = 0; i < basecnt; i++) + printf(" base[%d]='%s'", i, base[i]); + if (gotnumreq >= 0) { + if ((numreq[0] == 0) && (numreq[1] != 0)) + printf(" num-request=*,%d", numreq[1]); + if ((numreq[0] != 0) && (numreq[1] == 0)) + printf(" num-request=%d,*", numreq[0]); + if ((numreq[0] != 0) && (numreq[1] != 0)) + printf(" num-request=%d,%d", + numreq[0], numreq[1]); + } + if (period != 0) + printf(" test-period=%d", period); + printf(" lost-time=%g,%g", losttime[0], losttime[1]); + if (gotmaxloss == 0) { + if (maxloss[0] != 0) + printf(" max-loss=%d,*", maxloss[0]); + if (maxploss[0] != 0.) + printf(" max-loss=%2.2f%%,*", maxploss[0]); + } else if (gotmaxloss == 1) { + if (maxloss[0] != 0) + printf(" max-loss=%d,", maxloss[0]); + else if (maxploss[0] != 0.) + printf(" max-loss=%2.2f%%,", maxploss[0]); + else + printf(" max-loss=*,"); + if (maxloss[1] != 0) + printf("%d", maxloss[1]); + else if (maxploss[1] != 0.) + printf("%2.2f%%", maxploss[1]); + else + printf("*"); + } + printf(" aggressiveness=%d", aggressiveness); + if (seeded) + printf(" seed=%u", seed); + if (templatefile != NULL) + printf(" template-file='%s'", templatefile); + else if (ixann != 0) + printf(" Xflag"); + if (rndoffset >= 0) + printf(" rnd-offset=%d", rndoffset); + printf(" diagnotic-selectors='%s'", diags); + printf("\n"); + } + + /* check local address options */ + if ((localname == NULL) && (curport != 0)) { + fprintf(stderr, + "-l<local-addr> must be set to use -L<local-port>\n"); + usage(); + exit(2); + } + + /* check template file options */ + if ((templatefile == NULL) && (rndoffset >= 0)) { + fprintf(stderr, + "-T<template-file> must be set to " + "use -O<random-offset>\n"); + usage(); + exit(2); + } + + /* check various template file(s) and other condition(s) options */ + if ((templatefile != NULL) && (range > 0) && (rndoffset < 0)) { + fprintf(stderr, + "-O<random-offset> must be set when " + "-T<template-file> and -R<range> are used\n"); + usage(); + exit(2); + } + + /* get the server argument */ + if (optind < argc - 1) { + fprintf(stderr, "extra arguments?\n"); + usage(); + exit(2); + } + if (optind == argc - 1) + servername = argv[optind]; + + /* handle the local '-l' address/interface */ + if (localname != NULL) { + /* given */ + getlocaladdr(); + if ((diags != NULL) && (strchr(diags, 'a') != NULL)) { + printf("local-addr='%s'", localname); + if (curport != 0) + printf(" local-port='%d..%d'", + minport, maxport); + printf("\n"); + } + } + + /* get the server socket address */ + if (servername == NULL) { + fprintf(stderr, "server is required\n"); + usage(); + exit(2); + } + getserveraddr(flags); + + /* finish local/server socket address stuff and print it */ + if ((diags != NULL) && (strchr(diags, 'a') != NULL)) + printf("server='%s'\n", servername); + if ((localname != NULL) && + (diags != NULL) && (strchr(diags, 'a') != NULL)) { + char addr[NI_MAXHOST]; + + ret = getnameinfo((struct sockaddr *) &localaddr, + sizeof(localaddr), + addr, + NI_MAXHOST, + NULL, + 0, + NI_NUMERICHOST); + if (ret != 0) { + fprintf(stderr, + "can't get the local address: %s\n", + gai_strerror(ret)); + exit(1); + } + printf("local address='%s'\n", addr); + } + + /* initialize exchange structures */ + inits(); + + /* get the socket descriptor and template(s) */ + if (templatefile == NULL) + build_template_query(); + else + get_template_query(); + + /* boot is done! */ + if (clock_gettime(CLOCK_REALTIME, &boot) < 0) { + perror("clock_gettime(boot)"); + exit(1); + } + + /* compute the next intermediate reporting date */ + if (report != 0) { + dreport.tv_sec = boot.tv_sec + report; + dreport.tv_nsec = boot.tv_nsec; + } + + /* seed the random generator */ + if (seeded == 0) + seed = (unsigned int) (boot.tv_sec + boot.tv_nsec); + srandom(seed); + + /* required only before the interrupted flag check */ + (void) signal(SIGINT, interrupt); + (void) signal(SIGTERM, terminate); + + /* threads */ + master = pthread_self(); + ret = pthread_create(&connector, NULL, connecting, NULL); + if (ret != 0) { + fprintf(stderr, "pthread_create: %s\n", strerror(ret)); + exit(1); + } + ret = pthread_create(&sender, NULL, sending, NULL); + if (ret != 0) { + fprintf(stderr, "pthread_create: %s\n", strerror(ret)); + exit(1); + } + ret = pthread_create(&receiver, NULL, receiving, NULL); + if (ret != 0) { + fprintf(stderr, "pthread_create: %s\n", strerror(ret)); + exit(1); + } + + /* main loop */ + for (;;) { + struct timespec now, ts; + + /* immediate loop exit conditions */ + if (interrupted) { + if ((diags != NULL) && (strchr(diags, 'e') != NULL)) + printf("interrupted\n"); + break; + } + if (fatal) { + if ((diags != NULL) && (strchr(diags, 'e') != NULL)) + printf("got a fatal error\n"); + break; + } + + loops[0]++; + + /* get the date and use it */ + if (clock_gettime(CLOCK_REALTIME, &now) < 0) { + perror("clock_gettime(now)"); + fatal = 1; + continue; + } + if ((period != 0) && + ((boot.tv_sec + period < now.tv_sec) || + ((boot.tv_sec + period == now.tv_sec) && + (boot.tv_nsec < now.tv_nsec)))) { + if ((diags != NULL) && (strchr(diags, 'e') != NULL)) + printf("reached test-period\n"); + break; + } + if ((report != 0) && + ((dreport.tv_sec < now.tv_sec) || + ((dreport.tv_sec == now.tv_sec) && + (dreport.tv_nsec < now.tv_nsec)))) + reporting(); + + /* check receive loop exit conditions */ + if ((numreq[0] != 0) && ((int) xccount >= numreq[0])) { + if ((diags != NULL) && (strchr(diags, 'e') != NULL)) + printf("reached num-connection\n"); + break; + } + if ((numreq[1] != 0) && ((int) xscount >= numreq[1])) { + if ((diags != NULL) && (strchr(diags, 'e') != NULL)) + printf("reached num-query\n"); + break; + } + if ((maxloss[0] != 0) && + ((int) (xccount - xscount) > maxloss[0])) { + if ((diags != NULL) && (strchr(diags, 'e') != NULL)) + printf("reached max-loss " + "(connection/absolute)\n"); + break; + } + if ((maxloss[1] != 0) && + ((int) (xscount - xrcount) > maxloss[1])) { + if ((diags != NULL) && (strchr(diags, 'e') != NULL)) + printf("reached max-loss " + "(query/absolute)\n"); + break; + } + if ((maxploss[0] != 0.) && + (xccount > 10) && + (((100. * (xccount - xscount)) / xccount) > maxploss[1])) { + if ((diags != NULL) && (strchr(diags, 'e') != NULL)) + printf("reached max-loss " + "(connection/percent)\n"); + break; + } + if ((maxploss[1] != 0.) && + (xscount > 10) && + (((100. * (xscount - xrcount)) / xscount) > maxploss[1])) { + if ((diags != NULL) && (strchr(diags, 'e') != NULL)) + printf("reached max-loss " + "(query/percent)\n"); + break; + } + + /* waiting 1ms */ + memset(&ts, 0, sizeof(ts)); + ts.tv_nsec = 1000000; + (void) clock_nanosleep(CLOCK_REALTIME, 0, &ts, NULL); + } + + /* after main loop: finished */ + if (clock_gettime(CLOCK_REALTIME, &finished) < 0) + perror("clock_gettime(finished)"); + + /* threads */ + (void) pthread_cancel(connector); + (void) pthread_cancel(sender); + (void) pthread_cancel(receiver); + + /* main statictics */ + printf("connect: %llu, sent: %llu, received: %llu\n", + (unsigned long long) xccount, + (unsigned long long) xscount, + (unsigned long long) xrcount); + printf("embryonics: %lld (%.1f%%)\n", + (long long) (xccount - xscount), + (100. * (xccount - xscount)) / xccount); + printf("drops: %lld (%.1f%%)\n", + (long long) (xscount - xrcount), + (100. * (xscount - xrcount)) / xscount); + printf("total losses: %lld (%.1f%%)\n", + (long long) (xccount - xrcount), + (100. * (xccount - xrcount)) / xccount); + printf("local limits: %llu, bad connects: %llu, " + "connect timeouts: %llu\n", + (unsigned long long) locallimit, + (unsigned long long) badconn, + (unsigned long long) collconn); + printf("bad sends: %llu, bad recvs: %llu, recv timeouts: %llu\n", + (unsigned long long) badsent, + (unsigned long long) recverr, + (unsigned long long) collsent); + printf("too shorts: %llu, bad IDs: %llu, not responses: %llu\n", + (unsigned long long) tooshort, + (unsigned long long) badid, + (unsigned long long) notresp); + printf("rcode counters:\n noerror: %llu, formerr: %llu, " + "servfail: %llu\n " + "nxdomain: %llu, noimp: %llu, refused: %llu, others: %llu\n", + (unsigned long long) rcodes[NS_RCODE_NOERROR], + (unsigned long long) rcodes[NS_RCODE_FORMERR], + (unsigned long long) rcodes[NS_RCODE_SERVFAIL], + (unsigned long long) rcodes[NS_RCODE_NXDOMAIN], + (unsigned long long) rcodes[NS_RCODE_NOIMP], + (unsigned long long) rcodes[NS_RCODE_REFUSED], + (unsigned long long) rcodes[NS_RCODE_LAST]); + + /* print the rates */ + if (finished.tv_sec != 0) { + double dall, erate[3]; + + dall = (finished.tv_nsec - boot.tv_nsec) / 1e9; + dall += finished.tv_sec - boot.tv_sec; + erate[0] = xccount / dall; + erate[1] = xscount / dall; + erate[2] = xrcount / dall; + printf("rates: %.0f,%.0f,%.0f (target %d)\n", + erate[0], erate[1], erate[2], rate); + } + + /* rate processing instrumentation */ + if ((diags != NULL) && (strchr(diags, 'i') != NULL)) { + printf("loops: %llu,%llu,%llu,%llu\n", + (unsigned long long) loops[0], + (unsigned long long) loops[1], + (unsigned long long) loops[2], + (unsigned long long) loops[3]); + printf("shortwait: %llu,%llu,%llu\n", + (unsigned long long) shortwait[0], + (unsigned long long) shortwait[1], + (unsigned long long) shortwait[2]); + printf("compconn: %llu, lateconn: %llu\n", + (unsigned long long) compconn, + (unsigned long long) lateconn); + printf("badconn: %llu, collconn: %llu, " + "recverr: %llu, collsent: %llu\n", + (unsigned long long) badconn, + (unsigned long long) collconn, + (unsigned long long) recverr, + (unsigned long long) collsent); + printf("memory: used(%d) / allocated(%d)\n", + xused, xlast); + } + + /* round-time trip statistics */ + if (xrcount != 0) { + double avg, stddev; + + avg = dsum / xrcount; + stddev = sqrt(dsumsq / xrcount - avg * avg); + printf("RTT: min/avg/max/stddev: %.3f/%.3f/%.3f/%.3f ms\n", + dmin * 1e3, avg * 1e3, dmax * 1e3, stddev * 1e3); + } + printf("\n"); + + /* template(s) */ + if ((diags != NULL) && (strchr(diags, 'T') != NULL)) { + size_t n; + + printf("length = 0x%zx\n", length_query); + if (random_query > 0) + printf("random offset = %zu\n", random_query); + printf("content:\n"); + for (n = 0; n < length_query; n++) { + printf("%s%02hhx", + (n & 15) == 0 ? "" : " ", + template_query[n]); + if ((n & 15) == 15) + printf("\n"); + } + if ((n & 15) != 15) + printf("\n"); + printf("\n"); + } + + /* compute the exit code (and exit) */ + if (fatal) + exit(1); + else if ((xccount == xscount) && (xscount == xrcount)) + exit(0); + else + exit(3); +} diff --git a/contrib/scripts/check-secure-delegation.pl.in b/contrib/scripts/check-secure-delegation.pl.in new file mode 100644 index 0000000..528900a --- /dev/null +++ b/contrib/scripts/check-secure-delegation.pl.in @@ -0,0 +1,114 @@ +#!@PERL@ +# +# 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. + +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..78b0a4c --- /dev/null +++ b/contrib/scripts/check5011.pl @@ -0,0 +1,199 @@ +#!/usr/bin/perl + +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..f93ac9f --- /dev/null +++ b/contrib/scripts/dnssec-keyset.sh @@ -0,0 +1,210 @@ +#!/bin/sh +# Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC") +# +# Permission to use, copy, modify, and/or distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, +# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +# PERFORMANCE OF THIS SOFTWARE. +# +# Original script contributed by Jeffry A. Spain <spainj@countryday.net> + +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..29e4105 --- /dev/null +++ b/contrib/scripts/named-bootconf.sh @@ -0,0 +1,299 @@ +#!/bin/sh +# +# Portions 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. + +# $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..cb93441 --- /dev/null +++ b/contrib/scripts/nanny.pl @@ -0,0 +1,47 @@ +#!/usr/bin/perl +# +# 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. + +# 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..18132f9 --- /dev/null +++ b/contrib/scripts/zone-edit.sh.in @@ -0,0 +1,151 @@ +#!/bin/sh +# +# Copyright (C) Internet Systems Consortium, Inc. ("ISC") +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# +# See the COPYRIGHT file distributed with this work for additional +# information regarding copyright ownership. + +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 diff --git a/contrib/sdb/bdb/README b/contrib/sdb/bdb/README new file mode 100644 index 0000000..d2acf4e --- /dev/null +++ b/contrib/sdb/bdb/README @@ -0,0 +1,37 @@ +(Message trash:37216) + +Date: Wed, 15 May 2002 20:44:45 +0100 +To: <bind-workers@isc.org> +From: Nuno Miguel Rodrigues <nmr@co.sapo.pt> +Subject: Berkeley DB BIND9 SDB + +Replied: Thu, 16 May 2002 11:47:35 +1000 +Replied: Nuno Miguel Rodrigues <nmr@co.sapo.pt> +Return-Path: <bind-workers-bounce@isc.org> +X-X-Sender: <nmr@angelina.sl.pt> +MIME-Version: 1.0 +X-ecartis-version: Ecartis v1.0.0 +Sender: bind-workers-bounce@isc.org +Errors-To: bind-workers-bounce@isc.org +X-original-sender: nmr@co.sapo.pt +Precedence: bulk +X-list: bind-workers +Status: + + + +Hello all, + +I'm making available a BIND9 SDB using Berkeley DB 4.0. + +You can get it at http://www.dhis.org/~nmr/bind9_berkeleydb_sdb-1.0.tar + +Thanks, + + +-- +Nuno M. Rodrigues <nmr@co.sapo.pt> + + + + diff --git a/contrib/sdb/bdb/bdb.c b/contrib/sdb/bdb/bdb.c new file mode 100644 index 0000000..6dbc187 --- /dev/null +++ b/contrib/sdb/bdb/bdb.c @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2002 Nuno M. Rodrigues. + * + * 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 NUNO M. RODRIGUES + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * INTERNET SOFTWARE CONSORTIUM 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.1.x simple database driver + * implementation, using Berkeley DB. + */ + +#include <errno.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include <isc/file.h> +#include <isc/log.h> +#include <isc/lib.h> +#include <isc/mem.h> +#include <isc/msgs.h> +#include <isc/msgcat.h> +#include <isc/region.h> +#include <isc/result.h> +#include <isc/types.h> +#include <isc/util.h> + +#include <dns/sdb.h> +#include <dns/log.h> +#include <dns/lib.h> +#include <dns/ttl.h> + +#include <named/bdb.h> +#include <named/globals.h> +#include <named/config.h> + +#include <db.h> + +#define DRIVERNAME "bdb" + +static dns_sdbimplementation_t *bdb_imp; + +static isc_result_t +bdb_create(const char *zone, int argc, char **argv, + void *unused, void **dbdata) +{ + int ret; + + UNUSED(zone); + UNUSED(unused); + + if (argc < 1) + return ISC_R_FAILURE; /* database path must be given */ + + if (db_create((DB **)dbdata, NULL, 0) != 0) { + /* + * XXX Should use dns_msgcat et al + * but seems to be unavailable. + */ + isc_log_iwrite(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_SDB, ISC_LOG_CRITICAL, isc_msgcat, + ISC_MSGSET_GENERAL, ISC_MSG_FATALERROR, + "db_create"); + return ISC_R_FAILURE; + } + + if (isc_file_exists(*argv) != true) { + isc_log_iwrite(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_SDB, ISC_LOG_CRITICAL, isc_msgcat, + ISC_MSGSET_GENERAL, ISC_MSG_FATALERROR, + "isc_file_exists: %s", *argv); + return ISC_R_FAILURE; + } + + if ((ret = (*(DB **)dbdata)->open(*(DB **)dbdata, *argv, NULL, DB_HASH, + DB_RDONLY, 0)) != 0) { + isc_log_iwrite(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_SDB, ISC_LOG_CRITICAL, + isc_msgcat, ISC_MSGSET_GENERAL, + ISC_MSG_FATALERROR, "DB->open: %s", + db_strerror(ret)); + return ISC_R_FAILURE; + } + return ISC_R_SUCCESS; +} + +static isc_result_t +#ifdef DNS_CLIENTINFO_VERSION +bdb_lookup(const char *zone, const char *name, void *dbdata, + dns_sdblookup_t *l, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) +#else +bdb_lookup(const char *zone, const char *name, void *dbdata, + dns_sdblookup_t *l) +#endif /* DNS_CLIENTINFO_VERSION */ +{ + int ret; + char *type, *rdata; + dns_ttl_t ttl; + isc_consttextregion_t ttltext; + DBC *c; + DBT key, data; + + UNUSED(zone); +#ifdef DNS_CLIENTINFO_VERSION + UNUSED(methods); + UNUSED(clientinfo); +#endif /* DNS_CLIENTINFO_VERSION */ + + if ((ret = ((DB *)dbdata)->cursor((DB *)dbdata, NULL, &c, 0)) != 0) { + isc_log_iwrite(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_SDB, ISC_LOG_ERROR, + isc_msgcat, ISC_MSGSET_GENERAL, + ISC_MSG_FAILED, "DB->cursor: %s", + db_strerror(ret)); + return ISC_R_FAILURE; + } + + memset(&key, 0, sizeof(DBT)); + memset(&data, 0, sizeof(DBT)); + + (const char *)key.data = name; + key.size = strlen(name); + + ret = c->c_get(c, &key, &data, DB_SET); + while (ret == 0) { + ((char *)key.data)[key.size] = 0; + ((char *)data.data)[data.size] = 0; + ttltext.base = strtok((char *)data.data, " "); + ttltext.length = strlen(ttltext.base); + dns_ttl_fromtext((isc_textregion_t *)&ttltext, &ttl); + type = strtok(NULL, " "); + rdata = type + strlen(type) + 1; + + if (dns_sdb_putrr(l, type, ttl, rdata) != ISC_R_SUCCESS) { + isc_log_iwrite(dns_lctx, + DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_SDB, ISC_LOG_ERROR, + isc_msgcat, ISC_MSGSET_GENERAL, + ISC_MSG_FAILED, "dns_sdb_putrr"); + return ISC_R_FAILURE; + } + ret = c->c_get(c, &key, &data, DB_NEXT_DUP); + } + + c->c_close(c); + return ISC_R_SUCCESS; +} + +static isc_result_t +bdb_allnodes(const char *zone, void *dbdata, dns_sdballnodes_t *n) +{ + int ret; + char *type, *rdata; + dns_ttl_t ttl; + isc_consttextregion_t ttltext; + DBC *c; + DBT key, data; + + UNUSED(zone); + + if ((ret = ((DB *)dbdata)->cursor((DB *)dbdata, NULL, &c, 0)) != 0) { + isc_log_iwrite(dns_lctx, DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_SDB, ISC_LOG_ERROR, + isc_msgcat, ISC_MSGSET_GENERAL, + ISC_MSG_FAILED, "DB->cursor: %s", + db_strerror(ret)); + return ISC_R_FAILURE; + } + + memset(&key, 0, sizeof(DBT)); + memset(&data, 0, sizeof(DBT)); + + while (c->c_get(c, &key, &data, DB_NEXT) == 0) { + ((char *)key.data)[key.size] = 0; + ((char *)data.data)[data.size] = 0; + ttltext.base = strtok((char *)data.data, " "); + ttltext.length = strlen(ttltext.base); + dns_ttl_fromtext((isc_textregion_t *)&ttltext, &ttl); + type = strtok(NULL, " "); + rdata = type + strlen(type) + 1; + + if (dns_sdb_putnamedrr(n, key.data, type, ttl, rdata) != + ISC_R_SUCCESS) { + isc_log_iwrite(dns_lctx, + DNS_LOGCATEGORY_DATABASE, + DNS_LOGMODULE_SDB, ISC_LOG_ERROR, + isc_msgcat, ISC_MSGSET_GENERAL, + ISC_MSG_FAILED, "dns_sdb_putnamedrr"); + return ISC_R_FAILURE; + } + + } + + c->c_close(c); + return ISC_R_SUCCESS; +} + +static isc_result_t +bdb_destroy(const char *zone, void *unused, void **dbdata) +{ + + UNUSED(zone); + UNUSED(unused); + + (*(DB **)dbdata)->close(*(DB **)dbdata, 0); + + return ISC_R_SUCCESS; +} + +isc_result_t +bdb_init(void) +{ + static dns_sdbmethods_t bdb_methods = { + bdb_lookup, + NULL, + bdb_allnodes, + bdb_create, + bdb_destroy, + NULL /* lookup2 */ + }; + + return dns_sdb_register(DRIVERNAME, &bdb_methods, NULL, 0, ns_g_mctx, + &bdb_imp); +} + +void +bdb_clear(void) +{ + + if (bdb_imp != NULL) + dns_sdb_unregister(&bdb_imp); +} diff --git a/contrib/sdb/bdb/bdb.h b/contrib/sdb/bdb/bdb.h new file mode 100644 index 0000000..9857380 --- /dev/null +++ b/contrib/sdb/bdb/bdb.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2002 Nuno M. Rodrigues. + * + * 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 NUNO M. RODRIGUES + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * INTERNET SOFTWARE CONSORTIUM 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 BDB_H +#define BDB_H 1 + +#include <isc/types.h> + +/* + * Prototypes. + */ +isc_result_t bdb_init(void); +void bdb_clear(void); + +#endif /* BDB_H */ diff --git a/contrib/sdb/bdb/zone2bdb.c b/contrib/sdb/bdb/zone2bdb.c new file mode 100644 index 0000000..6599db1 --- /dev/null +++ b/contrib/sdb/bdb/zone2bdb.c @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2002 Nuno M. Rodrigues. + * + * 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 NUNO M. RODRIGUES + * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL + * INTERNET SOFTWARE CONSORTIUM 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 <stdio.h> + +#include <isc/mem.h> +#include <isc/result.h> +#include <isc/types.h> +#include <isc/util.h> + +#include <dns/db.h> +#include <dns/dbiterator.h> +#include <dns/fixedname.h> +#include <dns/name.h> +#include <dns/rdata.h> +#include <dns/rdataset.h> +#include <dns/rdatasetiter.h> +#include <dns/rdatatype.h> +#include <dns/ttl.h> +#include <dns/types.h> + +#include <db.h> + +#define MAX_RDATATEXT 63 + 4 + 65535 + 2 /* ttl + type + rdata + sep */ + +/* + * Returns a valid 'DB' handle. + * + * Requires: + * 'file' is a valid non-existant path. + */ +DB * +bdb_init(const char *file) +{ + DB *db; + + REQUIRE(db_create(&db, NULL, 0) == 0); + REQUIRE(db->set_flags(db, DB_DUP) == 0); + REQUIRE(db->open(db, file, NULL, DB_HASH, DB_CREATE | DB_EXCL, 0) == 0); + + return db; +} + +/* + * Puts resource record data on 'db'. + */ +isc_result_t +bdb_putrdata(DB *db, dns_name_t *name, dns_ttl_t ttl, dns_rdata_t *rdata) +{ + static DBT key, data; + isc_buffer_t keybuf, databuf; + char nametext[DNS_NAME_MAXTEXT]; + char rdatatext[MAX_RDATATEXT]; + + isc_buffer_init(&keybuf, nametext, DNS_NAME_MAXTEXT); + + dns_name_totext(name, true, &keybuf); + + key.data = isc_buffer_base(&keybuf); + key.size = isc_buffer_usedlength(&keybuf); + + isc_buffer_init(&databuf, rdatatext, MAX_RDATATEXT); + + dns_ttl_totext(ttl, false, &databuf); + *(char *)isc_buffer_used(&databuf) = ' '; + isc_buffer_add(&databuf, 1); + + dns_rdatatype_totext(rdata->type, &databuf); /* XXX private data */ + *(char *)isc_buffer_used(&databuf) = ' '; + isc_buffer_add(&databuf, 1); + + dns_rdata_totext(rdata, NULL, &databuf); + + data.data = isc_buffer_base(&databuf); + data.size = isc_buffer_usedlength(&databuf); + + REQUIRE(db->put(db, NULL, &key, &data, 0) == 0); + + return ISC_R_SUCCESS; +} + +isc_result_t +bdb_destroy(DB *db) +{ + + return (db->close(db, 0) == 0) ? ISC_R_SUCCESS : ISC_R_FAILURE; +} + +void +usage(const char *prog) +{ + + fprintf(stderr, "Usage: %s <origin> <zonefile> <db>\n", prog); + exit(1); +} + +int +main(int argc, char *argv[]) +{ + isc_mem_t *mctx = NULL; + isc_buffer_t b; + int n; + dns_fixedname_t origin, name; + dns_db_t *db = NULL; + dns_dbiterator_t *dbiter = NULL; + isc_result_t res; + dns_dbnode_t *node = NULL; + dns_rdataset_t rdataset; + dns_rdatasetiter_t *rdatasetiter = NULL; + dns_rdata_t rdata; + DB *bdb; + + if (argc != 4) usage(*argv); + + REQUIRE(isc_mem_create(0, 0, &mctx) == ISC_R_SUCCESS); + + n = strlen(argv[1]); + isc_buffer_init(&b, argv[1], n); + isc_buffer_add(&b, n); + + dns_fixedname_init(&origin); + + REQUIRE(dns_name_fromtext(dns_fixedname_name(&origin), &b, dns_rootname, + 0, NULL) == ISC_R_SUCCESS); + REQUIRE(dns_db_create(mctx, "rbt", dns_fixedname_name(&origin), + dns_dbtype_zone, dns_rdataclass_in, 0, NULL, + &db) == ISC_R_SUCCESS); + + REQUIRE(dns_db_load(db, argv[2]) == ISC_R_SUCCESS); + + REQUIRE(dns_db_createiterator(db, 0, &dbiter) == ISC_R_SUCCESS); + + dns_rdataset_init(&rdataset); + dns_rdata_init(&rdata); + dns_fixedname_init(&name); + bdb = bdb_init(argv[3]); + + for (res = dns_dbiterator_first(dbiter); res == ISC_R_SUCCESS; + res = dns_dbiterator_next(dbiter)) { + dns_dbiterator_current(dbiter, &node, dns_fixedname_name(&name)); + REQUIRE(dns_db_allrdatasets(db, node, NULL, 0, &rdatasetiter) + == ISC_R_SUCCESS); + + for (res = dns_rdatasetiter_first(rdatasetiter); + res == ISC_R_SUCCESS; + res = dns_rdatasetiter_next(rdatasetiter)) { + dns_rdatasetiter_current(rdatasetiter, &rdataset); + + res = dns_rdataset_first(&rdataset); + while (res == ISC_R_SUCCESS) { + dns_rdataset_current(&rdataset, &rdata); + REQUIRE(bdb_putrdata(bdb, + dns_fixedname_name(&name), + rdataset.ttl, &rdata) + == ISC_R_SUCCESS); + + dns_rdata_reset(&rdata); + res = dns_rdataset_next(&rdataset); + } + + dns_rdataset_disassociate(&rdataset); + } + dns_rdatasetiter_destroy(&rdatasetiter); + dns_db_detachnode(db, &node); + } + dns_dbiterator_destroy(&dbiter); + + REQUIRE(bdb_destroy(bdb) == ISC_R_SUCCESS); + + return 0; +} diff --git a/contrib/sdb/dir/dirdb.c b/contrib/sdb/dir/dirdb.c new file mode 100644 index 0000000..a83c4f3 --- /dev/null +++ b/contrib/sdb/dir/dirdb.c @@ -0,0 +1,200 @@ +/* + * 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. + */ + + +/* + * A simple database driver that returns basic information about + * files and directories in the Unix file system as DNS data. + */ + +#include <config.h> + +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/sysmacros.h> + +#include <isc/mem.h> +#include <isc/print.h> +#include <isc/result.h> +#include <isc/util.h> + +#include <dns/sdb.h> + +#include <named/globals.h> + +#include "dirdb.h" + +static dns_sdbimplementation_t *dirdb = NULL; + +#define CHECK(op) \ + do { result = (op); \ + if (result != ISC_R_SUCCESS) return (result); \ + } while (0) + +#define CHECKN(op) \ + do { n = (op); \ + if (n < 0) return (ISC_R_FAILURE); \ + } while (0) + + +/* + * This database operates on relative names. + * + * Any name will be interpreted as a pathname offset from the directory + * specified in the configuration file. + */ +#ifdef DNS_CLIENTINFO_VERSION +static isc_result_t +dirdb_lookup(const char *zone, const char *name, void *dbdata, + dns_sdblookup_t *lookup, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) +#else +static isc_result_t +dirdb_lookup(const char *zone, const char *name, void *dbdata, + dns_sdblookup_t *lookup) +#endif /* DNS_CLIENTINFO_VERSION */ +{ + char filename[255]; + char filename2[255]; + char buf[1024]; + struct stat statbuf; + isc_result_t result; + int n; + + UNUSED(zone); + UNUSED(dbdata); +#ifdef DNS_CLIENTINFO_VERSION + UNUSED(methods); + UNUSED(clientinfo); +#endif /* DNS_CLIENTINFO_VERSION */ + + if (strcmp(name, "@") == 0) + snprintf(filename, sizeof(filename), "%s", (char *)dbdata); + else + snprintf(filename, sizeof(filename), "%s/%s", + (char *)dbdata, name); + CHECKN(lstat(filename, &statbuf)); + + if (S_ISDIR(statbuf.st_mode)) + CHECK(dns_sdb_putrr(lookup, "txt", 3600, "dir")); + else if (S_ISCHR(statbuf.st_mode) || S_ISBLK(statbuf.st_mode)) { + CHECKN(snprintf(buf, sizeof(buf), + "\"%sdev\" \"major %d\" \"minor %d\"", + S_ISCHR(statbuf.st_mode) ? "chr" : "blk", + major(statbuf.st_rdev), + minor(statbuf.st_rdev))); + CHECK(dns_sdb_putrr(lookup, "txt", 3600, buf)); + } else if (S_ISFIFO(statbuf.st_mode)) + CHECK(dns_sdb_putrr(lookup, "txt", 3600, "pipe")); + else if (S_ISSOCK(statbuf.st_mode)) + CHECK(dns_sdb_putrr(lookup, "txt", 3600, "socket")); + else if (S_ISLNK(statbuf.st_mode)) { + CHECKN(readlink(filename, filename2, sizeof(filename2) - 1)); + buf[n] = 0; + CHECKN(snprintf(buf, sizeof(buf), "\"symlink\" \"%s\"", + filename2)); + CHECK(dns_sdb_putrr(lookup, "txt", 3600, buf)); + } else if (!S_ISREG(statbuf.st_mode)) + CHECK(dns_sdb_putrr(lookup, "txt", 3600, "unknown")); + else { + CHECKN(snprintf(buf, sizeof(buf), "\"file\" \"size = %u\"", + (unsigned int)statbuf.st_size)); + CHECK(dns_sdb_putrr(lookup, "txt", 3600, buf)); + } + + return (ISC_R_SUCCESS); +} + +/* + * lookup () does not return SOA or NS records, so authority() must be defined. + */ +static isc_result_t +dirdb_authority(const char *zone, void *dbdata, dns_sdblookup_t *lookup) { + isc_result_t result; + + UNUSED(zone); + UNUSED(dbdata); + + result = dns_sdb_putsoa(lookup, "ns", "hostmaster", 0); + INSIST(result == ISC_R_SUCCESS); + result = dns_sdb_putrr(lookup, "ns", 86400, "ns1"); + INSIST(result == ISC_R_SUCCESS); + result = dns_sdb_putrr(lookup, "ns", 86400, "ns2"); + INSIST(result == ISC_R_SUCCESS); + return (ISC_R_SUCCESS); +} + +/* + * Each database stores the top-level directory as the dbdata opaque + * object. The create() function allocates it. argv[0] holds the top + * level directory. + */ +static isc_result_t +dirdb_create(const char *zone, int argc, char **argv, + void *driverdata, void **dbdata) +{ + UNUSED(zone); + UNUSED(driverdata); + + if (argc < 1) + return (ISC_R_FAILURE); + *dbdata = isc_mem_strdup((isc_mem_t *)driverdata, argv[0]); + if (*dbdata == NULL) + return (ISC_R_NOMEMORY); + return (ISC_R_SUCCESS); +} + +/* + * The destroy() function frees the memory allocated by create(). + */ +static void +dirdb_destroy(const char *zone, void *driverdata, void **dbdata) { + UNUSED(zone); + UNUSED(driverdata); + isc_mem_free((isc_mem_t *)driverdata, *dbdata); +} + +/* + * This zone does not support zone transfer, so allnodes() is NULL. + */ +static dns_sdbmethods_t dirdb_methods = { + dirdb_lookup, + dirdb_authority, + NULL, /* allnodes */ + dirdb_create, + dirdb_destroy, + NULL /* lookup2 */ +}; + +/* + * Wrapper around dns_sdb_register(). Note that the first ns_g_mctx is + * being passed as the "driverdata" parameter, so that will it will be + * passed to create() and destroy(). + */ +isc_result_t +dirdb_init(void) { + unsigned int flags; + flags = DNS_SDBFLAG_RELATIVEOWNER | DNS_SDBFLAG_RELATIVERDATA | + DNS_SDBFLAG_THREADSAFE; + return (dns_sdb_register("dir", &dirdb_methods, ns_g_mctx, flags, + ns_g_mctx, &dirdb)); +} + +/* + * Wrapper around dns_sdb_unregister(). + */ +void +dirdb_clear(void) { + if (dirdb != NULL) + dns_sdb_unregister(&dirdb); +} diff --git a/contrib/sdb/dir/dirdb.h b/contrib/sdb/dir/dirdb.h new file mode 100644 index 0000000..e72dd69 --- /dev/null +++ b/contrib/sdb/dir/dirdb.h @@ -0,0 +1,18 @@ +/* + * 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. + */ + + +#include <isc/types.h> + +isc_result_t dirdb_init(void); + +void dirdb_clear(void); + diff --git a/contrib/sdb/ldap/INSTALL.ldap b/contrib/sdb/ldap/INSTALL.ldap new file mode 100644 index 0000000..9151129 --- /dev/null +++ b/contrib/sdb/ldap/INSTALL.ldap @@ -0,0 +1,83 @@ +This is the INSTALL file for 1.0-beta. See +http://www.venaas.no/ldap/bind-sdb/ for updates or other information. + +BUILDING + +You need the source for BIND 9.1.0 or newer (for zone transfers you +will need at least 9.1.1rc3 due to a bug). Basically you need to follow +the instructions in doc/misc/sdb, if my instructions don't make sense, +please have a look at those as well. + +Copy ldapdb.c to bin/named and ldapdb.h to bin/named/include in the +source tree. + +Next alter bin/named/Makefile.in. Add ldapdb.@O@ to DBDRIVER_OBJS and +ldapdb.c to DBDRIVER_SRCS. You also need to add something like +-I/usr/local/include to DBDRIVER_INCLUDES and +-L/usr/local/lib -lldap -llber -lresolv to DBDRIVER_LIBS +depending on what LDAP library you have and where you installed it. + +Finally you need to edit bin/named/main.c. Below where it says +"#include "xxdb.h"", add the line "#include <ldapdb.h>". Below where +it says "xxdb_init();" add the line "ldapdb_init();", and finally +below where it says "xxdb_clear();", add "ldapdb_clear();". + +Now you should hopefully be able to build as usual; first configure +and then make. If you get an error message about ldap_memfree() not +being defined, you're probably using an LDAP library with the +interface defined in RFC 1823. To build, uncomment the "#define +LDAPDB_RFC1823API" line near the top of ldapdb.c. + +Also, if you're using an LDAPv2 only server, you need to change +the line "#define LDAPDB_LDAP_VERSION 3" in ldapdb.c. Simply +replace 3 with 2. Instead of editing the file, you may define +LDAPDB_LDAP_VERSION yourself. + +If you want to use TLS, you need to uncommed the #define LDAPDB_TLS" +line near the top of ldapdb.c. + +CONFIGURING + +Before you do any configuring of LDAP stuff, please try to configure +and start bind as usual to see if things work. + +To do anything useful, you need to store a zone in some LDAP server. +You must use a schema called dNSZone. Note that it relies on some +attribute definitions in the Cosine schema, so that must be included +as well. The Cosine schema probably comes with your LDAP server. You +can find dNSZone and further details on how to store the data in your +LDAP server at http://www.venaas.no/ldap/bind-sdb/ + +To make BIND use a zone stored in LDAP, you will have to put something +like this in named.conf: + +zone "venaas.com" { + type master; + database "ldap ldap://158.38.160.245/dc=venaas,dc=com,o=DNS,dc=venaas,dc=no 172800"; +}; + +When doing lookups BIND will do a sub-tree search below the base in the +URL. The number 172800 is the TTL which will be used for all entries that +haven't got the dNSTTL attribute. It is also possible to add a filter to +the URL, say "ldap://host/base???(o=internal)". + +Version 1.0 also has support for simple LDAP bind, that is, binding to +LDAP using plain text authentication. The bind dn and password is coded +into the URL as extensions, according to RFC 2255. If you want simple +bind with say dn "cn=Manager,dc=venaas,dc=no" and password "secret", the +URL will be something like this: + +ldap://158.38.160.245/dc=venaas,dc=com,o=DNS,dc=venaas,dc=no????!bindname=cn=Manager%2cdc=venaas%2cdc=no,!x-bindpw=secret + +This URL may also include a filter part if you need it. Note that in +the bind dn, "," is hex-escaped as "%2c". This is necessary since "," +is the separator between the extension elements. The "!" in front of +"bindname" and "x-bindpw" can be omitted if you prefer. "x-bindpw" is +not standardized, but it's used by several other LDAP applications. See +RFC 2255 for details. + +Finally, if you enabled TLS when compiling, you can also use TLS if +you like. To do this you use the extension "x-tls", e.g. +ldap://158.38.160.245/dc=venaas,dc=com,o=DNS,dc=venaas,dc=no????!bindname=cn=Manager%2cdc=venaas%2cdc=no,!x-bindpw=secret,x-tls + +Stig Venaas <venaas@uninett.no> 2004-08-15 diff --git a/contrib/sdb/ldap/README.ldap b/contrib/sdb/ldap/README.ldap new file mode 100644 index 0000000..b4ea18a --- /dev/null +++ b/contrib/sdb/ldap/README.ldap @@ -0,0 +1,48 @@ +This is an attempt at an LDAP back-end for BIND 9 using the new simplified +database interface "sdb". This is release 1.0-beta and should be pretty +stable. Note that since version 0.4 a new schema is used. It is not +backwards compatible with versions before 0.4. + +1.0-beta fixes a large memory leak. An extension x-tls for enabling TLS +has been added. + +1.0-alpha uses LDAPv3 by default and also supports LDAP simple bind. That +is, one can use plain text password for authentication. The bind dn and +password is coded into the URL using extensions bindname and x-bindpw +per RFC 2255. + +In 0.9 the code has been cleaned up a bit and should be slightly faster +than previous versions. It also fixes an error with zone transfers (AXFR) +and entries with multiple relativeDomainName values. The problem was +that it would only use the first value in the result. There's no need +to upgrade unless you use such entries. + +0.8 uses asynchronous LDAP search which should give better performance. +Thanks to Ashley Burston for providing patch. Another new feature is +allowing filters in URLs. The syntax is as in RFC 2255. Few people will +need this, but if you have say an internal and external version of the +same zone, you could stick say o=internal and o=external into different +entries, and specify for instance ldap://host/base???(o=internal) +Some error logging has also been added. + +0.7 allows space and other characters to be used in URLs by use of %-quoting. +For instance space can be written as %20. It also fixes a problem with some +servers and/or APIs that do not preserve attribute casing. + +0.6 fixes some memory leaks present in older versions unless compiled with +the RFC 1823 API. + +The big changes in 0.5 are thread support and improved connection handling. +Multiple threads can now access the back-end simultaneously, and rather than +having one connection per zone, there is now one connection per thread per +LDAP server. This should help people with multiple CPUs and people with a +huge number of zones. One final change is support for literal IPv6 addresses +in LDAP URLs. At least OpenLDAP 2 has IPv6 support, so if you use OpenLDAP 2 +libraries and server, you got all you need. + +If you have bug reports, fixes, comments, questions or whatever, please +contact me. See also http://www.venaas.no/ldap/bind-sdb/ for information. + +See INSTALL for how to build, install and use. + +Stig Venaas <venaas@uninett.no> 2004-08-15 diff --git a/contrib/sdb/ldap/README.zone2ldap b/contrib/sdb/ldap/README.zone2ldap new file mode 100644 index 0000000..dacb56b --- /dev/null +++ b/contrib/sdb/ldap/README.zone2ldap @@ -0,0 +1,17 @@ +INSTALLATION + +To Compile zone2ldap from contrib/sdb directory: + + gcc -g `../../../isc-config.sh --cflags isc dns` -c zone2ldap.c + gcc -g -o zone2ldap zone2ldap.o `../../../isc-config.sh --libs isc dns` -lldap -llber -lresolv + +USAGE: + +See zone2ldap.1 + +BUGS: + +Jeff McNeil <jeff@snapcase.g-rock.net> + + + diff --git a/contrib/sdb/ldap/ldapdb.c b/contrib/sdb/ldap/ldapdb.c new file mode 100644 index 0000000..c43342c --- /dev/null +++ b/contrib/sdb/ldap/ldapdb.c @@ -0,0 +1,690 @@ +/* + * ldapdb.c version 1.0-beta + * + * Copyright (C) 2002, 2004 Stig Venaas + * + * 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. + * + * Contributors: Jeremy C. McDermond + */ + +/* + * If you want to use TLS, uncomment the define below + */ +/* #define LDAPDB_TLS */ + +/* + * If you are using an old LDAP API uncomment the define below. Only do this + * if you know what you're doing or get compilation errors on ldap_memfree(). + * This also forces LDAPv2. + */ +/* #define LDAPDB_RFC1823API */ + +/* Using LDAPv3 by default, change this if you want v2 */ +#ifndef LDAPDB_LDAP_VERSION +#define LDAPDB_LDAP_VERSION 3 +#endif + +#include <config.h> + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <ctype.h> + +#include <isc/mem.h> +#include <isc/print.h> +#include <isc/result.h> +#include <isc/util.h> +#include <isc/thread.h> + +#include <dns/sdb.h> + +#include <named/globals.h> +#include <named/log.h> + +#include <ldap.h> +#include "ldapdb.h" + +/* + * A simple database driver for LDAP + */ + +/* enough for name with 8 labels of max length */ +#define MAXNAMELEN 519 + +static dns_sdbimplementation_t *ldapdb = NULL; + +struct ldapdb_data { + char *hostport; + char *hostname; + int portno; + char *base; + int defaultttl; + char *filterall; + int filteralllen; + char *filterone; + int filteronelen; + char *filtername; + char *bindname; + char *bindpw; +#ifdef LDAPDB_TLS + int tls; +#endif +}; + +/* used by ldapdb_getconn */ + +struct ldapdb_entry { + void *index; + size_t size; + void *data; + struct ldapdb_entry *next; +}; + +static struct ldapdb_entry *ldapdb_find(struct ldapdb_entry *stack, + const void *index, size_t size) { + while (stack != NULL) { + if (stack->size == size && !memcmp(stack->index, index, size)) + return stack; + stack = stack->next; + } + return NULL; +} + +static void ldapdb_insert(struct ldapdb_entry **stack, + struct ldapdb_entry *item) { + item->next = *stack; + *stack = item; +} + +static void ldapdb_lock(int what) { + static isc_mutex_t lock; + + switch (what) { + case 0: + isc_mutex_init(&lock); + break; + case 1: + LOCK(&lock); + break; + case -1: + UNLOCK(&lock); + break; + } +} + +/* data == NULL means cleanup */ +static LDAP ** +ldapdb_getconn(struct ldapdb_data *data) +{ + static struct ldapdb_entry *allthreadsdata = NULL; + struct ldapdb_entry *threaddata, *conndata; + unsigned long threadid; + + if (data == NULL) { + /* cleanup */ + /* lock out other threads */ + ldapdb_lock(1); + while (allthreadsdata != NULL) { + threaddata = allthreadsdata; + free(threaddata->index); + while (threaddata->data != NULL) { + conndata = threaddata->data; + if (conndata->data != NULL) + ldap_unbind((LDAP *)conndata->data); + threaddata->data = conndata->next; + free(conndata); + } + allthreadsdata = threaddata->next; + free(threaddata); + } + ldapdb_lock(-1); + return (NULL); + } + + /* look for connection data for current thread */ + threadid = isc_thread_self(); + threaddata = ldapdb_find(allthreadsdata, &threadid, sizeof(threadid)); + if (threaddata == NULL) { + /* no data for this thread, create empty connection list */ + threaddata = malloc(sizeof(*threaddata)); + if (threaddata == NULL) + return (NULL); + threaddata->index = malloc(sizeof(threadid)); + if (threaddata->index == NULL) { + free(threaddata); + return (NULL); + } + *(unsigned long *)threaddata->index = threadid; + threaddata->size = sizeof(threadid); + threaddata->data = NULL; + + /* need to lock out other threads here */ + ldapdb_lock(1); + ldapdb_insert(&allthreadsdata, threaddata); + ldapdb_lock(-1); + } + + /* threaddata points at the connection list for current thread */ + /* look for existing connection to our server */ + conndata = ldapdb_find((struct ldapdb_entry *)threaddata->data, + data->hostport, strlen(data->hostport)); + if (conndata == NULL) { + /* no connection data structure for this server, create one */ + conndata = malloc(sizeof(*conndata)); + if (conndata == NULL) + return (NULL); + conndata->index = data->hostport; + conndata->size = strlen(data->hostport); + conndata->data = NULL; + ldapdb_insert((struct ldapdb_entry **)&threaddata->data, + conndata); + } + + return (LDAP **)&conndata->data; +} + +static void +ldapdb_bind(struct ldapdb_data *data, LDAP **ldp) +{ +#ifndef LDAPDB_RFC1823API + const int ver = LDAPDB_LDAP_VERSION; +#endif + + if (*ldp != NULL) + ldap_unbind(*ldp); + *ldp = ldap_open(data->hostname, data->portno); + if (*ldp == NULL) + return; + +#ifndef LDAPDB_RFC1823API + ldap_set_option(*ldp, LDAP_OPT_PROTOCOL_VERSION, &ver); +#endif + +#ifdef LDAPDB_TLS + if (data->tls) { + ldap_start_tls_s(*ldp, NULL, NULL); + } +#endif + + if (ldap_simple_bind_s(*ldp, data->bindname, data->bindpw) != LDAP_SUCCESS) { + ldap_unbind(*ldp); + *ldp = NULL; + } +} + +#ifdef DNS_CLIENTINFO_VERSION +static isc_result_t +ldapdb_search(const char *zone, const char *name, void *dbdata, void *retdata, + dns_clientinfomethods_t *methods, dns_clientinfo_t *clientinfo) +#else +static isc_result_t +ldapdb_search(const char *zone, const char *name, void *dbdata, void *retdata, + void *methods, void *clientinfo) +#endif /* DNS_CLIENTINFO_VERSION */ +{ + struct ldapdb_data *data = dbdata; + isc_result_t result = ISC_R_NOTFOUND; + LDAP **ldp; + LDAPMessage *res, *e; + char *fltr, *a, **vals = NULL, **names = NULL; + char type[64]; +#ifdef LDAPDB_RFC1823API + void *ptr; +#else + BerElement *ptr; +#endif + int i, j, errno, msgid; + + UNUSED(methods); + UNUSED(clientinfo); + + ldp = ldapdb_getconn(data); + if (ldp == NULL) + return (ISC_R_FAILURE); + if (*ldp == NULL) { + ldapdb_bind(data, ldp); + if (*ldp == NULL) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "LDAP sdb zone '%s': bind failed", zone); + return (ISC_R_FAILURE); + } + } + + if (name == NULL) { + fltr = data->filterall; + } else { + if (strlen(name) > MAXNAMELEN) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "LDAP sdb zone '%s': name %s too long", zone, name); + return (ISC_R_FAILURE); + } + sprintf(data->filtername, "%s))", name); + fltr = data->filterone; + } + + msgid = ldap_search(*ldp, data->base, LDAP_SCOPE_SUBTREE, fltr, NULL, 0); + if (msgid == -1) { + ldapdb_bind(data, ldp); + if (*ldp != NULL) + msgid = ldap_search(*ldp, data->base, LDAP_SCOPE_SUBTREE, fltr, NULL, 0); + } + + if (*ldp == NULL || msgid == -1) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "LDAP sdb zone '%s': search failed, filter %s", zone, fltr); + return (ISC_R_FAILURE); + } + + /* Get the records one by one as they arrive and return them to bind */ + while ((errno = ldap_result(*ldp, msgid, 0, NULL, &res)) != LDAP_RES_SEARCH_RESULT ) { + LDAP *ld = *ldp; + int ttl = data->defaultttl; + + /* not supporting continuation references at present */ + if (errno != LDAP_RES_SEARCH_ENTRY) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "LDAP sdb zone '%s': ldap_result returned %d", zone, errno); + ldap_msgfree(res); + return (ISC_R_FAILURE); + } + + /* only one entry per result message */ + e = ldap_first_entry(ld, res); + if (e == NULL) { + ldap_msgfree(res); + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "LDAP sdb zone '%s': ldap_first_entry failed", zone); + return (ISC_R_FAILURE); + } + + if (name == NULL) { + names = ldap_get_values(ld, e, "relativeDomainName"); + if (names == NULL) + continue; + } + + vals = ldap_get_values(ld, e, "dNSTTL"); + if (vals != NULL) { + ttl = atoi(vals[0]); + ldap_value_free(vals); + } + + for (a = ldap_first_attribute(ld, e, &ptr); a != NULL; a = ldap_next_attribute(ld, e, ptr)) { + char *s; + + for (s = a; *s; s++) + *s = toupper(*s); + s = strstr(a, "RECORD"); + if ((s == NULL) || (s == a) || (s - a >= (signed int)sizeof(type))) { +#ifndef LDAPDB_RFC1823API + ldap_memfree(a); +#endif + continue; + } + + strncpy(type, a, s - a); + type[s - a] = '\0'; + vals = ldap_get_values(ld, e, a); + if (vals != NULL) { + for (i = 0; vals[i] != NULL; i++) { + if (name != NULL) { + result = dns_sdb_putrr(retdata, type, ttl, vals[i]); + } else { + for (j = 0; names[j] != NULL; j++) { + result = dns_sdb_putnamedrr(retdata, names[j], type, ttl, vals[i]); + if (result != ISC_R_SUCCESS) + break; + } + } +; if (result != ISC_R_SUCCESS) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "LDAP sdb zone '%s': dns_sdb_put... failed for %s", zone, vals[i]); + ldap_value_free(vals); +#ifndef LDAPDB_RFC1823API + ldap_memfree(a); + if (ptr != NULL) + ber_free(ptr, 0); +#endif + if (name == NULL) + ldap_value_free(names); + ldap_msgfree(res); + return (ISC_R_FAILURE); + } + } + ldap_value_free(vals); + } +#ifndef LDAPDB_RFC1823API + ldap_memfree(a); +#endif + } +#ifndef LDAPDB_RFC1823API + if (ptr != NULL) + ber_free(ptr, 0); +#endif + if (name == NULL) + ldap_value_free(names); + + /* free this result */ + ldap_msgfree(res); + } + + /* free final result */ + ldap_msgfree(res); + return (result); +} + + +/* callback routines */ +#ifdef DNS_CLIENTINFO_VERSION +static isc_result_t +ldapdb_lookup(const char *zone, const char *name, void *dbdata, + dns_sdblookup_t *lookup, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) +{ + UNUSED(methods); + UNUSED(clientinfo); + return (ldapdb_search(zone, name, dbdata, lookup, NULL, NULL)); +} +#else +static isc_result_t +ldapdb_lookup(const char *zone, const char *name, void *dbdata, + dns_sdblookup_t *lookup) +{ + return (ldapdb_search(zone, name, dbdata, lookup, methods, + clientinfo)); +} +#endif /* DNS_CLIENTINFO_VERSION */ + +static isc_result_t +ldapdb_allnodes(const char *zone, void *dbdata, + dns_sdballnodes_t *allnodes) +{ + return (ldapdb_search(zone, NULL, dbdata, allnodes, NULL, NULL)); +} + +static char * +unhex(char *in) +{ + static const char hexdigits[] = "0123456789abcdef"; + char *p, *s = in; + int d1, d2; + + while ((s = strchr(s, '%'))) { + if (!(s[1] && s[2])) + return NULL; + if ((p = strchr(hexdigits, tolower(s[1]))) == NULL) + return NULL; + d1 = p - hexdigits; + if ((p = strchr(hexdigits, tolower(s[2]))) == NULL) + return NULL; + d2 = p - hexdigits; + *s++ = d1 << 4 | d2; + memmove(s, s + 2, strlen(s) - 1); + } + return in; +} + +/* returns 0 for ok, -1 for bad syntax, -2 for unknown critical extension */ +static int +parseextensions(char *extensions, struct ldapdb_data *data) +{ + char *s, *next, *name, *value; + int critical; + + while (extensions != NULL) { + s = strchr(extensions, ','); + if (s != NULL) { + *s++ = '\0'; + next = s; + } else { + next = NULL; + } + + if (*extensions != '\0') { + s = strchr(extensions, '='); + if (s != NULL) { + *s++ = '\0'; + value = *s != '\0' ? s : NULL; + } else { + value = NULL; + } + name = extensions; + + critical = *name == '!'; + if (critical) { + name++; + } + if (*name == '\0') { + return -1; + } + + if (!strcasecmp(name, "bindname")) { + data->bindname = value; + } else if (!strcasecmp(name, "x-bindpw")) { + data->bindpw = value; +#ifdef LDAPDB_TLS + } else if (!strcasecmp(name, "x-tls")) { + data->tls = value == NULL || !strcasecmp(value, "true"); +#endif + } else if (critical) { + return -2; + } + } + extensions = next; + } + return 0; +} + +static void +free_data(struct ldapdb_data *data) +{ + if (data->hostport != NULL) + isc_mem_free(ns_g_mctx, data->hostport); + if (data->hostname != NULL) + isc_mem_free(ns_g_mctx, data->hostname); + if (data->filterall != NULL) + isc_mem_put(ns_g_mctx, data->filterall, data->filteralllen); + if (data->filterone != NULL) + isc_mem_put(ns_g_mctx, data->filterone, data->filteronelen); + isc_mem_put(ns_g_mctx, data, sizeof(struct ldapdb_data)); +} + + +static isc_result_t +ldapdb_create(const char *zone, int argc, char **argv, + void *driverdata, void **dbdata) +{ + struct ldapdb_data *data; + char *s, *filter = NULL, *extensions = NULL; + int defaultttl; + + UNUSED(driverdata); + + /* we assume that only one thread will call create at a time */ + /* want to do this only once for all instances */ + + if ((argc < 2) + || (argv[0] != strstr( argv[0], "ldap://")) + || ((defaultttl = atoi(argv[1])) < 1)) + return (ISC_R_FAILURE); + data = isc_mem_get(ns_g_mctx, sizeof(struct ldapdb_data)); + if (data == NULL) + return (ISC_R_NOMEMORY); + + memset(data, 0, sizeof(struct ldapdb_data)); + data->hostport = isc_mem_strdup(ns_g_mctx, argv[0] + strlen("ldap://")); + if (data->hostport == NULL) { + free_data(data); + return (ISC_R_NOMEMORY); + } + + data->defaultttl = defaultttl; + + s = strchr(data->hostport, '/'); + if (s != NULL) { + *s++ = '\0'; + data->base = s; + /* attrs, scope, filter etc? */ + s = strchr(s, '?'); + if (s != NULL) { + *s++ = '\0'; + /* ignore attributes */ + s = strchr(s, '?'); + if (s != NULL) { + *s++ = '\0'; + /* ignore scope */ + s = strchr(s, '?'); + if (s != NULL) { + *s++ = '\0'; + /* filter */ + filter = s; + s = strchr(s, '?'); + if (s != NULL) { + *s++ = '\0'; + /* extensions */ + extensions = s; + s = strchr(s, '?'); + if (s != NULL) { + *s++ = '\0'; + } + if (*extensions == '\0') { + extensions = NULL; + } + } + if (*filter == '\0') { + filter = NULL; + } + } + } + } + if (*data->base == '\0') { + data->base = NULL; + } + } + + /* parse extensions */ + if (extensions != NULL) { + int err; + + err = parseextensions(extensions, data); + if (err < 0) { + /* err should be -1 or -2 */ + free_data(data); + if (err == -1) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "LDAP sdb zone '%s': URL: extension syntax error", zone); + } else if (err == -2) { + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "LDAP sdb zone '%s': URL: unknown critical extension", zone); + } + return (ISC_R_FAILURE); + } + } + + if ((data->base != NULL && unhex(data->base) == NULL) || + (filter != NULL && unhex(filter) == NULL) || + (data->bindname != NULL && unhex(data->bindname) == NULL) || + (data->bindpw != NULL && unhex(data->bindpw) == NULL)) { + free_data(data); + isc_log_write(ns_g_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_SERVER, ISC_LOG_ERROR, + "LDAP sdb zone '%s': URL: bad hex values", zone); + return (ISC_R_FAILURE); + } + + /* compute filterall and filterone once and for all */ + if (filter == NULL) { + data->filteralllen = strlen(zone) + strlen("(zoneName=)") + 1; + data->filteronelen = strlen(zone) + strlen("(&(zoneName=)(relativeDomainName=))") + MAXNAMELEN + 1; + } else { + data->filteralllen = strlen(filter) + strlen(zone) + strlen("(&(zoneName=))") + 1; + data->filteronelen = strlen(filter) + strlen(zone) + strlen("(&(zoneName=)(relativeDomainName=))") + MAXNAMELEN + 1; + } + + data->filterall = isc_mem_get(ns_g_mctx, data->filteralllen); + if (data->filterall == NULL) { + free_data(data); + return (ISC_R_NOMEMORY); + } + data->filterone = isc_mem_get(ns_g_mctx, data->filteronelen); + if (data->filterone == NULL) { + free_data(data); + return (ISC_R_NOMEMORY); + } + + if (filter == NULL) { + sprintf(data->filterall, "(zoneName=%s)", zone); + sprintf(data->filterone, "(&(zoneName=%s)(relativeDomainName=", zone); + } else { + sprintf(data->filterall, "(&%s(zoneName=%s))", filter, zone); + sprintf(data->filterone, "(&%s(zoneName=%s)(relativeDomainName=", filter, zone); + } + data->filtername = data->filterone + strlen(data->filterone); + + /* support URLs with literal IPv6 addresses */ + data->hostname = isc_mem_strdup(ns_g_mctx, data->hostport + (*data->hostport == '[' ? 1 : 0)); + if (data->hostname == NULL) { + free_data(data); + return (ISC_R_NOMEMORY); + } + + if (*data->hostport == '[' && + (s = strchr(data->hostname, ']')) != NULL ) + *s++ = '\0'; + else + s = data->hostname; + s = strchr(s, ':'); + if (s != NULL) { + *s++ = '\0'; + data->portno = atoi(s); + } else + data->portno = LDAP_PORT; + + *dbdata = data; + return (ISC_R_SUCCESS); +} + +static void +ldapdb_destroy(const char *zone, void *driverdata, void **dbdata) { + struct ldapdb_data *data = *dbdata; + + UNUSED(zone); + UNUSED(driverdata); + + free_data(data); +} + +static dns_sdbmethods_t ldapdb_methods = { + ldapdb_lookup, + NULL, /* authority */ + ldapdb_allnodes, + ldapdb_create, + ldapdb_destroy, + NULL /* lookup2 */ +}; + +/* Wrapper around dns_sdb_register() */ +isc_result_t +ldapdb_init(void) { + unsigned int flags = + DNS_SDBFLAG_RELATIVEOWNER | + DNS_SDBFLAG_RELATIVERDATA | + DNS_SDBFLAG_THREADSAFE; + + ldapdb_lock(0); + return (dns_sdb_register("ldap", &ldapdb_methods, NULL, flags, + ns_g_mctx, &ldapdb)); +} + +/* Wrapper around dns_sdb_unregister() */ +void +ldapdb_clear(void) { + if (ldapdb != NULL) { + /* clean up thread data */ + ldapdb_getconn(NULL); + dns_sdb_unregister(&ldapdb); + } +} diff --git a/contrib/sdb/ldap/ldapdb.h b/contrib/sdb/ldap/ldapdb.h new file mode 100644 index 0000000..a08eb20 --- /dev/null +++ b/contrib/sdb/ldap/ldapdb.h @@ -0,0 +1,6 @@ +#include <isc/types.h> + +isc_result_t ldapdb_init(void); + +void ldapdb_clear(void); + diff --git a/contrib/sdb/ldap/zone2ldap.1 b/contrib/sdb/ldap/zone2ldap.1 new file mode 100644 index 0000000..781114b --- /dev/null +++ b/contrib/sdb/ldap/zone2ldap.1 @@ -0,0 +1,64 @@ +.TH zone2ldap 1 "8 March 2001" +.SH NAME +zone2ldap /- Load BIND 9 Zone files into LDAP Directory +.SH SYNOPSIS +zone2ldap [-D Bind DN] [-w Bind Password] [-b Base DN] [-z Zone] [-f Zone File ] [-h Ldap Host] [-cd] [-v] +.SH DESCRIPTION +zone2ldap will parse a complete BIND 9 format DNS zone file, and load +the contents into an LDAP directory, for use with the LDAP sdb back-end. + +If the zone already exists, zone2ldap will exit succesfully. If the zone does not exists, or +partially exists, zone2ldap will attempt to add all/missing zone data. + +.SS Options +.TP +-b +LDAP Base DN. LDAP systems require a "base dn", which is generally considered the LDAP Directory root. +If the zone you are loading is different from the base, then you will need to tell zone2ldap what your LDAP +base is. +.TP +-v +Print version information, and immediatly exit. +.TP +-f +Zone file. Bind 9.1 compatible zone file, from which zone information will be read. +.TP +-d +Dump debug information to standard out. +.TP +-w +LDAP Bind password, corresponding the the value of "-b". +.TP +-h +LDAP Directory host. This is the hostname of the LDAP system you wish to store zone information on. +An LDAP server should be listening on port 389 of the target system. This may be ommited, and will default +to "localhost". +.TP +-c +This will create the zone portion of the DN you are importing. For instance, if you are creating a domain.com zone, +zone2ldap should first create "dc=domain,dc=com". This is useful if you are creating multiple domains. +.TP +-z +This is the name of the zone specified in the SOA record. +.SH EXAMPLES +Following are brief examples of how to import a zone file into your LDAP DIT. +.SS Loading zone domain.com, with an LDAP Base DN of dc=domain,dc=com +zone2ldap -D dc=root -w secret -h localhost -z domain.com -f domain.com.zone + +This will add Resource Records into an ALREADY EXISTING dc=domain,dc=com. The final SOA DN in this case, will be +dc=@,dc=domain,dc=com + +.SS Loading customer.com, if your LDAP Base DN is dc=provider,dc=net. +zone2ldap -D dc=root -w secret -h localhost -z customer.com -b dc=provider,dc=net -f customer.com.zone -c + +This will create dc=customer,dc=com under dc=provider,dc=net, and add all necessary Resource Records. The final +root DN to the SOA will be dc=@,dc=customer,dc=com,dc=provider,dc=net. + +.SH "SEE ALSO" +named(8) ldap(3) +http://www.venaas.no/ldap/bind-sdb/ +.SH "BUGS" +Send all bug reports to Jeff McNeil <jeff@snapcase.g-rock.net> +.SH AUTHOR +Jeff McNeil <jeff@snapcase.g-rock.net> + diff --git a/contrib/sdb/ldap/zone2ldap.c b/contrib/sdb/ldap/zone2ldap.c new file mode 100644 index 0000000..301265a --- /dev/null +++ b/contrib/sdb/ldap/zone2ldap.c @@ -0,0 +1,772 @@ +/* + * Copyright (C) 2001 Jeff McNeil <jeff@snapcase.g-rock.net> + * + * 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. + * + * Change Log + * + * Tue May 1 19:19:54 EDT 2001 - Jeff McNeil + * Update to objectClass code, and add_to_rr_list function + * (I need to rename that) to support the dNSZone schema, + * ditched dNSDomain2 schema support. Version 0.3-ALPHA + */ + +#include <errno.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <getopt.h> + +#include <isc/buffer.h> +#include <isc/entropy.h> +#include <isc/hash.h> +#include <isc/mem.h> +#include <isc/print.h> +#include <isc/result.h> +#include <isc/string.h> + +#include <dns/db.h> +#include <dns/dbiterator.h> +#include <dns/fixedname.h> +#include <dns/name.h> +#include <dns/rdata.h> +#include <dns/rdataset.h> +#include <dns/rdatasetiter.h> +#include <dns/result.h> +#include <dns/rdatatype.h> + +#define LDAP_DEPRECATED 1 + +#include <ldap.h> + +#define DNS_OBJECT 6 +#define DNS_TOP 2 + +#define VERSION "0.4-ALPHA" + +#define NO_SPEC 0 +#define WI_SPEC 1 + +/* Global Zone Pointer */ +char *gbl_zone = NULL; + +typedef struct LDAP_INFO +{ + char *dn; + LDAPMod **attrs; + struct LDAP_INFO *next; + int attrcnt; +} +ldap_info; + +/* usage Info */ +void usage (); + +/* Add to the ldap dit */ +void add_ldap_values (ldap_info * ldinfo); + +/* Init an ldap connection */ +void init_ldap_conn (); + +/* Ldap error checking */ +void ldap_result_check (char *msg, char *dn, int err); + +/* Put a hostname into a char ** array */ +char **hostname_to_dn_list (char *hostname, char *zone, unsigned int flags); + +/* Find out how many items are in a char ** array */ +int get_attr_list_size (char **tmp); + +/* Get a DN */ +char *build_dn_from_dc_list (char **dc_list, unsigned int ttl, int flag); + +/* Add to RR list */ +void add_to_rr_list (char *dn, char *name, char *type, char *data, + unsigned int ttl, unsigned int flags); + +/* Error checking */ +void isc_result_check (isc_result_t res, char *errorstr); + +/* Generate LDIF Format files */ +void generate_ldap (dns_name_t * dnsname, dns_rdata_t * rdata, + unsigned int ttl); + +/* head pointer to the list */ +ldap_info *ldap_info_base = NULL; + +char *argzone, *ldapbase, *binddn, *bindpw = NULL; +char *ldapsystem = "localhost"; +static char *objectClasses[] = + { "top", "dNSZone", NULL }; +static char *topObjectClasses[] = { "top", NULL }; +LDAP *conn; +unsigned int debug = 0; + +#ifdef DEBUG +debug = 1; +#endif + +static void +fatal(const char *msg) { + perror(msg); + if (conn != NULL) + ldap_unbind_s(conn); + exit(1); +} + +int +main (int argc, char **argv) +{ + isc_mem_t *mctx = NULL; + isc_entropy_t *ectx = NULL; + isc_result_t result; + char *basedn; + ldap_info *tmp; + LDAPMod *base_attrs[2]; + LDAPMod base; + isc_buffer_t buff; + char *zonefile; + char fullbasedn[1024]; + char *ctmp; + dns_fixedname_t fixedzone, fixedname; + dns_rdataset_t rdataset; + char **dc_list; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdatasetiter_t *riter; + dns_name_t *zone, *name; + dns_db_t *db = NULL; + dns_dbiterator_t *dbit = NULL; + dns_dbnode_t *node; + extern char *optarg; + extern int optind, opterr, optopt; + int create_base = 0; + int topt; + + if (argc < 2) + { + usage (); + exit (-1); + } + + while ((topt = getopt (argc, argv, "D:w:b:z:f:h:?dcv")) != -1) + { + switch (topt) + { + case 'v': + printf("%s\n", VERSION); + exit(0); + case 'c': + create_base++; + break; + case 'd': + debug++; + break; + case 'D': + binddn = strdup (optarg); + if (binddn == NULL) + fatal("strdup"); + break; + case 'w': + bindpw = strdup (optarg); + if (bindpw == NULL) + fatal("strdup"); + break; + case 'b': + ldapbase = strdup (optarg); + if (ldapbase == NULL) + fatal("strdup"); + break; + case 'z': + argzone = strdup (optarg); + // We wipe argzone all to hell when we parse it for the DN */ + gbl_zone = strdup(argzone); + if (argzone == NULL || gbl_zone == NULL) + fatal("strdup"); + break; + case 'f': + zonefile = strdup (optarg); + if (zonefile == NULL) + fatal("strdup"); + break; + case 'h': + ldapsystem = strdup (optarg); + if (ldapsystem == NULL) + fatal("strdup"); + break; + case '?': + default: + usage (); + exit (0); + } + } + + if ((argzone == NULL) || (zonefile == NULL)) + { + usage (); + exit (-1); + } + + if (debug) + printf ("Initializing ISC Routines, parsing zone file\n"); + + result = isc_mem_create (0, 0, &mctx); + isc_result_check (result, "isc_mem_create"); + + result = isc_entropy_create(mctx, &ectx); + isc_result_check (result, "isc_entropy_create"); + + result = isc_hash_create(mctx, ectx, DNS_NAME_MAXWIRE); + isc_result_check (result, "isc_hash_create"); + + isc_buffer_init (&buff, argzone, strlen (argzone)); + isc_buffer_add (&buff, strlen (argzone)); + zone = dns_fixedname_initname(&fixedzone); + result = dns_name_fromtext (zone, &buff, dns_rootname, 0, NULL); + isc_result_check (result, "dns_name_fromtext"); + + result = dns_db_create (mctx, "rbt", zone, dns_dbtype_zone, + dns_rdataclass_in, 0, NULL, &db); + isc_result_check (result, "dns_db_create"); + + result = dns_db_load (db, zonefile); + isc_result_check (result, "Check Zone Syntax: dns_db_load"); + + result = dns_db_createiterator (db, 0, &dbit); + isc_result_check (result, "dns_db_createiterator"); + + result = dns_dbiterator_first (dbit); + isc_result_check (result, "dns_dbiterator_first"); + + name = dns_fixedname_initname(&fixedname); + dns_rdataset_init (&rdataset); + dns_rdata_init (&rdata); + + while (result == ISC_R_SUCCESS) + { + node = NULL; + result = dns_dbiterator_current (dbit, &node, name); + + if (result == ISC_R_NOMORE) + break; + + isc_result_check (result, "dns_dbiterator_current"); + + riter = NULL; + result = dns_db_allrdatasets (db, node, NULL, 0, &riter); + isc_result_check (result, "dns_db_allrdatasets"); + + result = dns_rdatasetiter_first (riter); + //isc_result_check(result, "dns_rdatasetiter_first"); + + while (result == ISC_R_SUCCESS) + { + dns_rdatasetiter_current (riter, &rdataset); + result = dns_rdataset_first (&rdataset); + isc_result_check (result, "dns_rdatasetiter_current"); + + while (result == ISC_R_SUCCESS) + { + dns_rdataset_current (&rdataset, &rdata); + generate_ldap (name, &rdata, rdataset.ttl); + dns_rdata_reset (&rdata); + result = dns_rdataset_next (&rdataset); + } + dns_rdataset_disassociate (&rdataset); + result = dns_rdatasetiter_next (riter); + + } + dns_rdatasetiter_destroy (&riter); + result = dns_dbiterator_next (dbit); + + } + + /* Initialize the LDAP Connection */ + if (debug) + printf ("Initializing LDAP Connection to %s as %s\n", ldapsystem, binddn); + + init_ldap_conn (); + + if (create_base) + { + if (debug) + printf ("Creating base zone DN %s\n", argzone); + + dc_list = hostname_to_dn_list (argzone, argzone, DNS_TOP); + basedn = build_dn_from_dc_list (dc_list, 0, NO_SPEC); + + for (ctmp = &basedn[strlen (basedn)]; ctmp >= &basedn[0]; ctmp--) + { + if ((*ctmp == ',') || (ctmp == &basedn[0])) + { + base.mod_op = LDAP_MOD_ADD; + base.mod_type = "objectClass"; + base.mod_values = topObjectClasses; + base_attrs[0] = &base; + base_attrs[1] = NULL; + + if (ldapbase) + { + if (ctmp != &basedn[0]) + sprintf (fullbasedn, "%s,%s", ctmp + 1, ldapbase); + else + sprintf (fullbasedn, "%s,%s", ctmp, ldapbase); + + } + else + { + if (ctmp != &basedn[0]) + sprintf (fullbasedn, "%s", ctmp + 1); + else + sprintf (fullbasedn, "%s", ctmp); + } + result = ldap_add_s (conn, fullbasedn, base_attrs); + ldap_result_check ("intial ldap_add_s", fullbasedn, result); + } + + } + } + else + { + if (debug) + printf ("Skipping zone base dn creation for %s\n", argzone); + } + + for (tmp = ldap_info_base; tmp != NULL; tmp = tmp->next) + { + + if (debug) + printf ("Adding DN: %s\n", tmp->dn); + + add_ldap_values (tmp); + } + + if (debug) + printf("Operation Complete.\n"); + + /* Cleanup */ + isc_hash_destroy(); + isc_entropy_detach(&ectx); + isc_mem_destroy(&mctx); + if (zonefile) + free(zonefile); + + return 0; +} + + +/* Check the status of an isc_result_t after any isc routines. + * I should probably rename this function, as not to cause any + * confusion with the isc* routines. Will exit on error. */ +void +isc_result_check (isc_result_t res, char *errorstr) +{ + if (res != ISC_R_SUCCESS) + { + fprintf (stderr, " %s: %s\n", errorstr, isc_result_totext (res)); + exit (-1); + } +} + + +/* Takes DNS information, in bind data structure format, and adds textual + * zone information to the LDAP run queue. */ +void +generate_ldap (dns_name_t * dnsname, dns_rdata_t * rdata, unsigned int ttl) +{ + char name[DNS_NAME_MAXTEXT + 1]; + unsigned int len; + char type[20]; + char data[2048]; + char **dc_list; + char *dn; + + isc_buffer_t buff; + isc_result_t result; + + isc_buffer_init (&buff, name, sizeof (name)); + result = dns_name_totext (dnsname, true, &buff); + isc_result_check (result, "dns_name_totext"); + name[isc_buffer_usedlength (&buff)] = 0; + + isc_buffer_init (&buff, type, sizeof (type)); + result = dns_rdatatype_totext (rdata->type, &buff); + isc_result_check (result, "dns_rdatatype_totext"); + type[isc_buffer_usedlength (&buff)] = 0; + + isc_buffer_init (&buff, data, sizeof (data)); + result = dns_rdata_totext (rdata, NULL, &buff); + isc_result_check (result, "dns_rdata_totext"); + data[isc_buffer_usedlength (&buff)] = 0; + + dc_list = hostname_to_dn_list (name, argzone, DNS_OBJECT); + len = (get_attr_list_size (dc_list) - 2); + dn = build_dn_from_dc_list (dc_list, ttl, WI_SPEC); + + if (debug) + printf ("Adding %s (%s %s) to run queue list.\n", dn, type, data); + + add_to_rr_list (dn, dc_list[len], type, data, ttl, DNS_OBJECT); +} + + +/* Locate an item in the Run queue linked list, by DN. Used by functions + * which add items to the run queue. + */ +ldap_info * +locate_by_dn (char *dn) +{ + ldap_info *tmp; + for (tmp = ldap_info_base; tmp != (ldap_info *) NULL; tmp = tmp->next) + { + if (!strncmp (tmp->dn, dn, strlen (dn))) + return tmp; + } + return (ldap_info *) NULL; +} + + + +/* Take textual zone data, and add to the LDAP Run queue. This works like so: + * If locate_by_dn does not return, alloc a new ldap_info structure, and then + * calloc a LDAPMod array, fill in the default "everyone needs this" information, + * including object classes and dc's. If it locate_by_dn does return, then we'll + * realloc for more LDAPMod structs, and appened the new data. If an LDAPMod exists + * for the parameter we're adding, then we'll realloc the mod_values array, and + * add the new value to the existing LDAPMod. Finnaly, it assures linkage exists + * within the Run queue linked ilst*/ + +void +add_to_rr_list (char *dn, char *name, char *type, + char *data, unsigned int ttl, unsigned int flags) +{ + int i; + int x; + ldap_info *tmp; + int attrlist; + char ldap_type_buffer[128]; + char charttl[64]; + + + if ((tmp = locate_by_dn (dn)) == NULL) + { + + /* There wasn't one already there, so we need to allocate a new one, + * and stick it on the list */ + + tmp = (ldap_info *) malloc (sizeof (ldap_info)); + if (tmp == (ldap_info *) NULL) + fatal("malloc"); + + tmp->dn = strdup (dn); + if (tmp->dn == NULL) + fatal("strdup"); + + tmp->attrs = (LDAPMod **) calloc (sizeof (LDAPMod *), flags); + if (tmp->attrs == (LDAPMod **) NULL) + fatal("calloc"); + + for (i = 0; i < flags; i++) + { + tmp->attrs[i] = (LDAPMod *) malloc (sizeof (LDAPMod)); + if (tmp->attrs[i] == (LDAPMod *) NULL) + fatal("malloc"); + } + tmp->attrs[0]->mod_op = LDAP_MOD_ADD; + tmp->attrs[0]->mod_type = "objectClass"; + + if (flags == DNS_OBJECT) + tmp->attrs[0]->mod_values = objectClasses; + else + { + tmp->attrs[0]->mod_values = topObjectClasses; + tmp->attrs[1] = NULL; + tmp->attrcnt = 2; + tmp->next = ldap_info_base; + ldap_info_base = tmp; + return; + } + + tmp->attrs[1]->mod_op = LDAP_MOD_ADD; + tmp->attrs[1]->mod_type = "relativeDomainName"; + tmp->attrs[1]->mod_values = (char **) calloc (sizeof (char *), 2); + + if (tmp->attrs[1]->mod_values == (char **)NULL) + fatal("calloc"); + + tmp->attrs[1]->mod_values[0] = strdup (name); + tmp->attrs[1]->mod_values[2] = NULL; + + if (tmp->attrs[1]->mod_values[0] == NULL) + fatal("strdup"); + + sprintf (ldap_type_buffer, "%sRecord", type); + + tmp->attrs[2]->mod_op = LDAP_MOD_ADD; + tmp->attrs[2]->mod_type = strdup (ldap_type_buffer); + tmp->attrs[2]->mod_values = (char **) calloc (sizeof (char *), 2); + + if (tmp->attrs[2]->mod_type == NULL || + tmp->attrs[2]->mod_values == (char **)NULL) + fatal("strdup/calloc"); + + tmp->attrs[2]->mod_values[0] = strdup (data); + tmp->attrs[2]->mod_values[1] = NULL; + + if (tmp->attrs[2]->mod_values[0] == NULL) + fatal("strdup"); + + tmp->attrs[3]->mod_op = LDAP_MOD_ADD; + tmp->attrs[3]->mod_type = "dNSTTL"; + tmp->attrs[3]->mod_values = (char **) calloc (sizeof (char *), 2); + + if (tmp->attrs[3]->mod_values == (char **)NULL) + fatal("calloc"); + + sprintf (charttl, "%d", ttl); + tmp->attrs[3]->mod_values[0] = strdup (charttl); + tmp->attrs[3]->mod_values[1] = NULL; + + if (tmp->attrs[3]->mod_values[0] == NULL) + fatal("strdup"); + + tmp->attrs[4]->mod_op = LDAP_MOD_ADD; + tmp->attrs[4]->mod_type = "zoneName"; + tmp->attrs[4]->mod_values = (char **)calloc(sizeof(char *), 2); + + if (tmp->attrs[4]->mod_values == (char **)NULL) + fatal("calloc"); + + tmp->attrs[4]->mod_values[0] = gbl_zone; + tmp->attrs[4]->mod_values[1] = NULL; + + tmp->attrs[5] = NULL; + tmp->attrcnt = flags; + tmp->next = ldap_info_base; + ldap_info_base = tmp; + } + else + { + + for (i = 0; tmp->attrs[i] != NULL; i++) + { + sprintf (ldap_type_buffer, "%sRecord", type); + if (!strncmp + (ldap_type_buffer, tmp->attrs[i]->mod_type, + strlen (tmp->attrs[i]->mod_type))) + { + attrlist = get_attr_list_size (tmp->attrs[i]->mod_values); + tmp->attrs[i]->mod_values = + (char **) realloc (tmp->attrs[i]->mod_values, + sizeof (char *) * (attrlist + 1)); + + if (tmp->attrs[i]->mod_values == (char **) NULL) + fatal("realloc"); + + for (x = 0; tmp->attrs[i]->mod_values[x] != NULL; x++); + + tmp->attrs[i]->mod_values[x] = strdup (data); + if (tmp->attrs[i]->mod_values[x] == NULL) + fatal("strdup"); + tmp->attrs[i]->mod_values[x + 1] = NULL; + + return; + } + } + tmp->attrs = + (LDAPMod **) realloc (tmp->attrs, + sizeof (LDAPMod) * ++(tmp->attrcnt)); + if (tmp->attrs == NULL) + fatal("realloc"); + + for (x = 0; tmp->attrs[x] != NULL; x++); + tmp->attrs[x] = (LDAPMod *) malloc (sizeof (LDAPMod)); + if (tmp->attrs[x] == NULL) + fatal("malloc"); + tmp->attrs[x]->mod_op = LDAP_MOD_ADD; + tmp->attrs[x]->mod_type = strdup (ldap_type_buffer); + tmp->attrs[x]->mod_values = (char **) calloc (sizeof (char *), 2); + + if (tmp->attrs[x]->mod_type == NULL || + tmp->attrs[x]->mod_values == (char **)NULL) + fatal("strdup/calloc"); + + tmp->attrs[x]->mod_values[0] = strdup (data); + if (tmp->attrs[x]->mod_values[0] == NULL) + fatal("strdup"); + tmp->attrs[x]->mod_values[1] = NULL; + tmp->attrs[x + 1] = NULL; + } +} + +/* Size of a mod_values list, plus the terminating NULL field. */ +int +get_attr_list_size (char **tmp) +{ + int i = 0; + char **ftmp = tmp; + while (*ftmp != NULL) + { + i++; + ftmp++; + } + return ++i; +} + + +/* take a hostname, and split it into a char ** of the dc parts, + * example, we have www.domain.com, this function will return: + * array[0] = com, array[1] = domain, array[2] = www. */ + +char ** +hostname_to_dn_list (char *hostname, char *zone, unsigned int flags) +{ + char *tmp; + static char *dn_buffer[64]; + int i = 0; + char *zname; + char *hnamebuff; + + zname = strdup (hostname); + if (zname == NULL) + fatal("strdup"); + + if (flags == DNS_OBJECT) + { + + if (strlen (zname) != strlen (zone)) + { + tmp = &zname[strlen (zname) - strlen (zone)]; + *--tmp = '\0'; + hnamebuff = strdup (zname); + if (hnamebuff == NULL) + fatal("strdup"); + zname = ++tmp; + } + else + hnamebuff = "@"; + } + else + { + zname = zone; + hnamebuff = NULL; + } + + for (tmp = strrchr (zname, '.'); tmp != (char *) 0; + tmp = strrchr (zname, '.')) + { + *tmp++ = '\0'; + dn_buffer[i++] = tmp; + } + dn_buffer[i++] = zname; + dn_buffer[i++] = hnamebuff; + dn_buffer[i] = NULL; + + return dn_buffer; +} + + +/* build an sdb compatible LDAP DN from a "dc_list" (char **). + * will append dNSTTL information to each RR Record, with the + * exception of "@"/SOA. */ + +char * +build_dn_from_dc_list (char **dc_list, unsigned int ttl, int flag) +{ + int size; + int x; + static char dn[1024]; + char tmp[128]; + + bzero (tmp, sizeof (tmp)); + bzero (dn, sizeof (dn)); + size = get_attr_list_size (dc_list); + for (x = size - 2; x > 0; x--) + { + if (flag == WI_SPEC) + { + if (x == (size - 2) && (strncmp (dc_list[x], "@", 1) == 0) && (ttl)) + sprintf (tmp, "relativeDomainName=%s + dNSTTL=%d,", dc_list[x], ttl); + else if (x == (size - 2)) + sprintf(tmp, "relativeDomainName=%s,",dc_list[x]); + else + sprintf(tmp,"dc=%s,", dc_list[x]); + } + else + { + sprintf(tmp, "dc=%s,", dc_list[x]); + } + + + strlcat (dn, tmp, sizeof (dn)); + } + + sprintf (tmp, "dc=%s", dc_list[0]); + strlcat (dn, tmp, sizeof (dn)); + + fflush(NULL); + return dn; +} + + +/* Initialize LDAP Conn */ +void +init_ldap_conn () +{ + int result; + conn = ldap_open (ldapsystem, LDAP_PORT); + if (conn == NULL) + { + fprintf (stderr, "Error opening Ldap connection: %s\n", + strerror (errno)); + exit (-1); + } + + result = ldap_simple_bind_s (conn, binddn, bindpw); + ldap_result_check ("ldap_simple_bind_s", "LDAP Bind", result); +} + +/* Like isc_result_check, only for LDAP */ +void +ldap_result_check (char *msg, char *dn, int err) +{ + if ((err != LDAP_SUCCESS) && (err != LDAP_ALREADY_EXISTS)) + { + fprintf(stderr, "Error while adding %s (%s):\n", + dn, msg); + ldap_perror (conn, dn); + ldap_unbind_s (conn); + exit (-1); + } +} + + + +/* For running the ldap_info run queue. */ +void +add_ldap_values (ldap_info * ldinfo) +{ + int result; + char dnbuffer[1024]; + + + if (ldapbase != NULL) + sprintf (dnbuffer, "%s,%s", ldinfo->dn, ldapbase); + else + sprintf (dnbuffer, "%s", ldinfo->dn); + + result = ldap_add_s (conn, dnbuffer, ldinfo->attrs); + ldap_result_check ("ldap_add_s", dnbuffer, result); +} + + + + +/* name says it all */ +void +usage () +{ + fprintf (stderr, + "zone2ldap -D [BIND DN] -w [BIND PASSWORD] -b [BASE DN] -z [ZONE] -f [ZONE FILE] -h [LDAP HOST] " + "[-c Create LDAP Base structure][-d Debug Output (lots !)] \n ");} diff --git a/contrib/sdb/pgsql/pgsqldb.c b/contrib/sdb/pgsql/pgsqldb.c new file mode 100644 index 0000000..2551099 --- /dev/null +++ b/contrib/sdb/pgsql/pgsqldb.c @@ -0,0 +1,353 @@ +/* + * 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. + */ + + +#include <config.h> + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include <pgsql/libpq-fe.h> + +#include <isc/mem.h> +#include <isc/print.h> +#include <isc/result.h> +#include <isc/util.h> + +#include <dns/sdb.h> +#include <dns/result.h> + +#include <named/globals.h> + +#include "pgsqldb.h" + +/* + * A simple database driver that interfaces to a PostgreSQL database. This + * is not complete, and not designed for general use. It opens one + * connection to the database per zone, which is inefficient. It also may + * not handle quoting correctly. + * + * The table must contain the fields "name", "rdtype", and "rdata", and + * is expected to contain a properly constructed zone. The program "zonetodb" + * creates such a table. + */ + +static dns_sdbimplementation_t *pgsqldb = NULL; + +struct dbinfo { + PGconn *conn; + char *database; + char *table; + char *host; + char *user; + char *passwd; +}; + +static void +pgsqldb_destroy(const char *zone, void *driverdata, void **dbdata); + +/* + * Canonicalize a string before writing it to the database. + * "dest" must be an array of at least size 2*strlen(source) + 1. + */ +static void +quotestring(const char *source, char *dest) { + while (*source != 0) { + if (*source == '\'') + *dest++ = '\''; + /* SQL doesn't treat \ as special, but PostgreSQL does */ + else if (*source == '\\') + *dest++ = '\\'; + *dest++ = *source++; + } + *dest++ = 0; +} + +/* + * Connect to the database. + */ +static isc_result_t +db_connect(struct dbinfo *dbi) { + dbi->conn = PQsetdbLogin(dbi->host, NULL, NULL, NULL, dbi->database, + dbi->user, dbi->passwd); + + if (PQstatus(dbi->conn) == CONNECTION_OK) + return (ISC_R_SUCCESS); + else + return (ISC_R_FAILURE); +} + +/* + * Check to see if the connection is still valid. If not, attempt to + * reconnect. + */ +static isc_result_t +maybe_reconnect(struct dbinfo *dbi) { + if (PQstatus(dbi->conn) == CONNECTION_OK) + return (ISC_R_SUCCESS); + + return (db_connect(dbi)); +} + +/* + * This database operates on absolute names. + * + * Queries are converted into SQL queries and issued synchronously. Errors + * are handled really badly. + */ +#ifdef DNS_CLIENTINFO_VERSION +static isc_result_t +pgsqldb_lookup(const char *zone, const char *name, void *dbdata, + dns_sdblookup_t *lookup, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) +#else +static isc_result_t +pgsqldb_lookup(const char *zone, const char *name, void *dbdata, + dns_sdblookup_t *lookup) +#endif /* DNS_CLIENTINFO_VERSION */ +{ + isc_result_t result; + struct dbinfo *dbi = dbdata; + PGresult *res; + char str[1500]; + char *canonname; + int i; + + UNUSED(zone); +#ifdef DNS_CLIENTINFO_VERSION + UNUSED(methods); + UNUSED(clientinfo); +#endif /* DNS_CLIENTINFO_VERSION */ + + canonname = isc_mem_get(ns_g_mctx, strlen(name) * 2 + 1); + if (canonname == NULL) + return (ISC_R_NOMEMORY); + quotestring(name, canonname); + snprintf(str, sizeof(str), + "SELECT TTL,RDTYPE,RDATA FROM \"%s\" WHERE " + "lower(NAME) = lower('%s')", dbi->table, canonname); + isc_mem_put(ns_g_mctx, canonname, strlen(name) * 2 + 1); + + result = maybe_reconnect(dbi); + if (result != ISC_R_SUCCESS) + return (result); + + res = PQexec(dbi->conn, str); + if (!res || PQresultStatus(res) != PGRES_TUPLES_OK) { + PQclear(res); + return (ISC_R_FAILURE); + } + if (PQntuples(res) == 0) { + PQclear(res); + return (ISC_R_NOTFOUND); + } + + for (i = 0; i < PQntuples(res); i++) { + char *ttlstr = PQgetvalue(res, i, 0); + char *type = PQgetvalue(res, i, 1); + char *data = PQgetvalue(res, i, 2); + dns_ttl_t ttl; + char *endp; + ttl = strtol(ttlstr, &endp, 10); + if (*endp != '\0') { + PQclear(res); + return (DNS_R_BADTTL); + } + result = dns_sdb_putrr(lookup, type, ttl, data); + if (result != ISC_R_SUCCESS) { + PQclear(res); + return (ISC_R_FAILURE); + } + } + + PQclear(res); + return (ISC_R_SUCCESS); +} + +/* + * Issue an SQL query to return all nodes in the database and fill the + * allnodes structure. + */ +static isc_result_t +pgsqldb_allnodes(const char *zone, void *dbdata, dns_sdballnodes_t *allnodes) { + struct dbinfo *dbi = dbdata; + PGresult *res; + isc_result_t result; + char str[1500]; + int i; + + UNUSED(zone); + + snprintf(str, sizeof(str), + "SELECT TTL,NAME,RDTYPE,RDATA FROM \"%s\" ORDER BY NAME", + dbi->table); + + result = maybe_reconnect(dbi); + if (result != ISC_R_SUCCESS) + return (result); + + res = PQexec(dbi->conn, str); + if (!res || PQresultStatus(res) != PGRES_TUPLES_OK ) { + PQclear(res); + return (ISC_R_FAILURE); + } + if (PQntuples(res) == 0) { + PQclear(res); + return (ISC_R_NOTFOUND); + } + + for (i = 0; i < PQntuples(res); i++) { + char *ttlstr = PQgetvalue(res, i, 0); + char *name = PQgetvalue(res, i, 1); + char *type = PQgetvalue(res, i, 2); + char *data = PQgetvalue(res, i, 3); + dns_ttl_t ttl; + char *endp; + ttl = strtol(ttlstr, &endp, 10); + if (*endp != '\0') { + PQclear(res); + return (DNS_R_BADTTL); + } + result = dns_sdb_putnamedrr(allnodes, name, type, ttl, data); + if (result != ISC_R_SUCCESS) { + PQclear(res); + return (ISC_R_FAILURE); + } + } + + PQclear(res); + return (ISC_R_SUCCESS); +} + +/* + * Create a connection to the database and save any necessary information + * in dbdata. + * + * argv[0] is the name of the database + * argv[1] is the name of the table + * argv[2] (if present) is the name of the host to connect to + * argv[3] (if present) is the name of the user to connect as + * argv[4] (if present) is the name of the password to connect with + */ +static isc_result_t +pgsqldb_create(const char *zone, int argc, char **argv, + void *driverdata, void **dbdata) +{ + struct dbinfo *dbi; + isc_result_t result; + + UNUSED(zone); + UNUSED(driverdata); + + if (argc < 2) + return (ISC_R_FAILURE); + + dbi = isc_mem_get(ns_g_mctx, sizeof(struct dbinfo)); + if (dbi == NULL) + return (ISC_R_NOMEMORY); + dbi->conn = NULL; + dbi->database = NULL; + dbi->table = NULL; + dbi->host = NULL; + dbi->user = NULL; + dbi->passwd = NULL; + +#define STRDUP_OR_FAIL(target, source) \ + do { \ + target = isc_mem_strdup(ns_g_mctx, source); \ + if (target == NULL) { \ + result = ISC_R_NOMEMORY; \ + goto cleanup; \ + } \ + } while (0); + + STRDUP_OR_FAIL(dbi->database, argv[0]); + STRDUP_OR_FAIL(dbi->table, argv[1]); + if (argc > 2) + STRDUP_OR_FAIL(dbi->host, argv[2]); + if (argc > 3) + STRDUP_OR_FAIL(dbi->user, argv[3]); + if (argc > 4) + STRDUP_OR_FAIL(dbi->passwd, argv[4]); + + result = db_connect(dbi); + if (result != ISC_R_SUCCESS) + goto cleanup; + + *dbdata = dbi; + return (ISC_R_SUCCESS); + + cleanup: + pgsqldb_destroy(zone, driverdata, (void **)&dbi); + return (result); +} + +/* + * Close the connection to the database. + */ +static void +pgsqldb_destroy(const char *zone, void *driverdata, void **dbdata) { + struct dbinfo *dbi = *dbdata; + + UNUSED(zone); + UNUSED(driverdata); + + if (dbi->conn != NULL) + PQfinish(dbi->conn); + if (dbi->database != NULL) + isc_mem_free(ns_g_mctx, dbi->database); + if (dbi->table != NULL) + isc_mem_free(ns_g_mctx, dbi->table); + if (dbi->host != NULL) + isc_mem_free(ns_g_mctx, dbi->host); + if (dbi->user != NULL) + isc_mem_free(ns_g_mctx, dbi->user); + if (dbi->passwd != NULL) + isc_mem_free(ns_g_mctx, dbi->passwd); + if (dbi->database != NULL) + isc_mem_free(ns_g_mctx, dbi->database); + isc_mem_put(ns_g_mctx, dbi, sizeof(struct dbinfo)); +} + +/* + * Since the SQL database corresponds to a zone, the authority data should + * be returned by the lookup() function. Therefore the authority() function + * is NULL. + */ +static dns_sdbmethods_t pgsqldb_methods = { + pgsqldb_lookup, + NULL, /* authority */ + pgsqldb_allnodes, + pgsqldb_create, + pgsqldb_destroy, + NULL /* lookup2 */ +}; + +/* + * Wrapper around dns_sdb_register(). + */ +isc_result_t +pgsqldb_init(void) { + unsigned int flags; + flags = 0; + return (dns_sdb_register("pgsql", &pgsqldb_methods, NULL, flags, + ns_g_mctx, &pgsqldb)); +} + +/* + * Wrapper around dns_sdb_unregister(). + */ +void +pgsqldb_clear(void) { + if (pgsqldb != NULL) + dns_sdb_unregister(&pgsqldb); +} diff --git a/contrib/sdb/pgsql/pgsqldb.h b/contrib/sdb/pgsql/pgsqldb.h new file mode 100644 index 0000000..cd60f14 --- /dev/null +++ b/contrib/sdb/pgsql/pgsqldb.h @@ -0,0 +1,18 @@ +/* + * 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. + */ + + +#include <isc/types.h> + +isc_result_t pgsqldb_init(void); + +void pgsqldb_clear(void); + diff --git a/contrib/sdb/pgsql/zonetodb.c b/contrib/sdb/pgsql/zonetodb.c new file mode 100644 index 0000000..df7e939 --- /dev/null +++ b/contrib/sdb/pgsql/zonetodb.c @@ -0,0 +1,283 @@ +/* + * 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. + */ + + +#include <stdlib.h> +#include <string.h> + +#include <isc/buffer.h> +#include <isc/entropy.h> +#include <isc/hash.h> +#include <isc/mem.h> +#include <isc/print.h> +#include <isc/result.h> + +#include <dns/db.h> +#include <dns/dbiterator.h> +#include <dns/fixedname.h> +#include <dns/name.h> +#include <dns/rdata.h> +#include <dns/rdataset.h> +#include <dns/rdatasetiter.h> +#include <dns/rdatatype.h> +#include <dns/result.h> + +#include <pgsql/libpq-fe.h> + +/* + * Generate a PostgreSQL table from a zone. + * + * This is compiled this with something like the following (assuming bind9 has + * been installed): + * + * gcc -g `isc-config.sh --cflags isc dns` -c zonetodb.c + * gcc -g -o zonetodb zonetodb.o `isc-config.sh --libs isc dns` -lpq + */ + +PGconn *conn = NULL; +char *dbname, *dbtable; +char str[10240]; + +void +closeandexit(int status) { + if (conn != NULL) + PQfinish(conn); + exit(status); +} + +void +check_result(isc_result_t result, const char *message) { + if (result != ISC_R_SUCCESS) { + fprintf(stderr, "%s: %s\n", message, + isc_result_totext(result)); + closeandexit(1); + } +} + +/* + * Canonicalize a string before writing it to the database. + * "dest" must be an array of at least size 2*strlen(source) + 1. + */ +static void +quotestring(const unsigned char *source, unsigned char *dest) { + while (*source != 0) { + if (*source == '\'') + *dest++ = '\''; + else if (*source == '\\') + *dest++ = '\\'; + *dest++ = *source++; + } + *dest++ = 0; +} + +void +addrdata(dns_name_t *name, dns_ttl_t ttl, dns_rdata_t *rdata) { + unsigned char namearray[DNS_NAME_MAXTEXT + 1]; + unsigned char canonnamearray[2 * DNS_NAME_MAXTEXT + 1]; + unsigned char typearray[20]; + unsigned char canontypearray[40]; + unsigned char dataarray[2048]; + unsigned char canondataarray[4096]; + isc_buffer_t b; + isc_result_t result; + PGresult *res; + + isc_buffer_init(&b, namearray, sizeof(namearray) - 1); + result = dns_name_totext(name, true, &b); + check_result(result, "dns_name_totext"); + namearray[isc_buffer_usedlength(&b)] = 0; + quotestring((const unsigned char *)namearray, canonnamearray); + + isc_buffer_init(&b, typearray, sizeof(typearray) - 1); + result = dns_rdatatype_totext(rdata->type, &b); + check_result(result, "dns_rdatatype_totext"); + typearray[isc_buffer_usedlength(&b)] = 0; + quotestring((const unsigned char *)typearray, canontypearray); + + isc_buffer_init(&b, dataarray, sizeof(dataarray) - 1); + result = dns_rdata_totext(rdata, NULL, &b); + check_result(result, "dns_rdata_totext"); + dataarray[isc_buffer_usedlength(&b)] = 0; + quotestring((const unsigned char *)dataarray, canondataarray); + + snprintf(str, sizeof(str), + "INSERT INTO %s (NAME, TTL, RDTYPE, RDATA)" + " VALUES ('%s', %d, '%s', '%s')", + dbtable, canonnamearray, ttl, canontypearray, canondataarray); + printf("%s\n", str); + res = PQexec(conn, str); + if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) { + fprintf(stderr, "INSERT INTO command failed: %s\n", + PQresultErrorMessage(res)); + PQclear(res); + closeandexit(1); + } + PQclear(res); +} + +int +main(int argc, char **argv) { + char *porigin, *zonefile; + dns_fixedname_t forigin, fname; + dns_name_t *origin, *name; + dns_db_t *db = NULL; + dns_dbiterator_t *dbiter; + dns_dbnode_t *node; + dns_rdatasetiter_t *rdsiter; + dns_rdataset_t rdataset; + dns_rdata_t rdata = DNS_RDATA_INIT; + isc_mem_t *mctx = NULL; + isc_entropy_t *ectx = NULL; + isc_buffer_t b; + isc_result_t result; + PGresult *res; + + if (argc != 5) { + printf("usage: %s origin file dbname dbtable\n", argv[0]); + printf("Note that dbname must be an existing database.\n"); + exit(1); + } + + porigin = argv[1]; + zonefile = argv[2]; + dbname = argv[3]; + dbtable = argv[4]; + + dns_result_register(); + + mctx = NULL; + result = isc_mem_create(0, 0, &mctx); + check_result(result, "isc_mem_create"); + + result = isc_entropy_create(mctx, &ectx); + check_result(result, "isc_entropy_create"); + + result = isc_hash_create(mctx, ectx, DNS_NAME_MAXWIRE); + check_result(result, "isc_hash_create"); + + isc_buffer_init(&b, porigin, strlen(porigin)); + isc_buffer_add(&b, strlen(porigin)); + origin = dns_fixedname_initname(&forigin); + result = dns_name_fromtext(origin, &b, dns_rootname, 0, NULL); + check_result(result, "dns_name_fromtext"); + + db = NULL; + result = dns_db_create(mctx, "rbt", origin, dns_dbtype_zone, + dns_rdataclass_in, 0, NULL, &db); + check_result(result, "dns_db_create"); + + result = dns_db_load(db, zonefile); + if (result == DNS_R_SEENINCLUDE) + result = ISC_R_SUCCESS; + check_result(result, "dns_db_load"); + + printf("Connecting to '%s'\n", dbname); + conn = PQsetdb(NULL, NULL, NULL, NULL, dbname); + if (PQstatus(conn) == CONNECTION_BAD) { + fprintf(stderr, "Connection to database '%s' failed: %s\n", + dbname, PQerrorMessage(conn)); + closeandexit(1); + } + + snprintf(str, sizeof(str), + "DROP TABLE %s", dbtable); + printf("%s\n", str); + res = PQexec(conn, str); + if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) + fprintf(stderr, "DROP TABLE command failed: %s\n", + PQresultErrorMessage(res)); + PQclear(res); + + snprintf(str, sizeof(str), "BEGIN"); + printf("%s\n", str); + res = PQexec(conn, str); + if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) { + fprintf(stderr, "BEGIN command failed: %s\n", + PQresultErrorMessage(res)); + PQclear(res); + closeandexit(1); + } + PQclear(res); + + snprintf(str, sizeof(str), + "CREATE TABLE %s " + "(NAME TEXT, TTL INTEGER, RDTYPE TEXT, RDATA TEXT)", + dbtable); + printf("%s\n", str); + res = PQexec(conn, str); + if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) { + fprintf(stderr, "CREATE TABLE command failed: %s\n", + PQresultErrorMessage(res)); + PQclear(res); + closeandexit(1); + } + PQclear(res); + + dbiter = NULL; + result = dns_db_createiterator(db, 0, &dbiter); + check_result(result, "dns_db_createiterator()"); + + result = dns_dbiterator_first(dbiter); + check_result(result, "dns_dbiterator_first"); + + name = dns_fixedname_initname(&fname); + dns_rdataset_init(&rdataset); + dns_rdata_init(&rdata); + + while (result == ISC_R_SUCCESS) { + node = NULL; + result = dns_dbiterator_current(dbiter, &node, name); + if (result == ISC_R_NOMORE) + break; + check_result(result, "dns_dbiterator_current"); + + rdsiter = NULL; + result = dns_db_allrdatasets(db, node, NULL, 0, &rdsiter); + check_result(result, "dns_db_allrdatasets"); + + result = dns_rdatasetiter_first(rdsiter); + + while (result == ISC_R_SUCCESS) { + dns_rdatasetiter_current(rdsiter, &rdataset); + result = dns_rdataset_first(&rdataset); + check_result(result, "dns_rdataset_first"); + while (result == ISC_R_SUCCESS) { + dns_rdataset_current(&rdataset, &rdata); + addrdata(name, rdataset.ttl, &rdata); + dns_rdata_reset(&rdata); + result = dns_rdataset_next(&rdataset); + } + dns_rdataset_disassociate(&rdataset); + result = dns_rdatasetiter_next(rdsiter); + } + dns_rdatasetiter_destroy(&rdsiter); + dns_db_detachnode(db, &node); + result = dns_dbiterator_next(dbiter); + } + + snprintf(str, sizeof(str), "COMMIT TRANSACTION"); + printf("%s\n", str); + res = PQexec(conn, str); + if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) { + fprintf(stderr, "COMMIT command failed: %s\n", + PQresultErrorMessage(res)); + PQclear(res); + closeandexit(1); + } + PQclear(res); + dns_dbiterator_destroy(&dbiter); + dns_db_detach(&db); + isc_hash_destroy(); + isc_entropy_detach(&ectx); + isc_mem_destroy(&mctx); + closeandexit(0); + exit(0); +} diff --git a/contrib/sdb/sqlite/README.sdb_sqlite b/contrib/sdb/sqlite/README.sdb_sqlite new file mode 100644 index 0000000..36128e1 --- /dev/null +++ b/contrib/sdb/sqlite/README.sdb_sqlite @@ -0,0 +1,67 @@ + SQLite BIND SDB driver + +The SQLite BIND SDB "driver" is intended as an alternative both to the +pgsqldb and dirdb drivers, for situations that would like the management +simplicity and convenience of single filesystem files, with the additional +capability of SQL databases. It is also intended as an alternative to +the standard dynamic DNS update capability in bind, which effectively +requires use of DNSSEC keys for authorization and is limited to 'nsupdate' +for updates. An sqlite database, by contrast, uses and requires only +normal filesystem permissions, and may be updated however a typical SQLite +database might be updated, e.g., via a web service with an SQLite backend. + +This driver is not considered suitable for very high volume public +nameserver use, while likely useful for smaller private nameserver +applications, whether or not in a production environment. It should +generally be suitable wherever SQLite is preferable over larger database +engines, and not suitable where SQLite is not preferable. + +Usage: + +o Use the named_sdb process ( put ENABLE_SDB=yes in /etc/sysconfig/named ) + +o Edit your named.conf to contain a database zone, eg.: + +zone "mydomain.net." IN { + type master; + database "sqlite /etc/named.d/mydomain.db mydomain"; + # ^- DB file ^-Table +}; + +o Create the database zone table + The table must contain the columns "name", "rdtype", and "rdata", and + is expected to contain a properly constructed zone. The program + "zone2sqlite" creates such a table. + + zone2sqlite usage: + + zone2sqlite origin zonefile dbfile dbtable + + where + origin : zone origin, eg "mydomain.net." + zonefile : master zone database file, eg. mydomain.net.zone + dbfile : name of SQLite database file + dbtable : name of table in database + +--- +# mydomain.net.zone: +$TTL 1H +@ SOA localhost. root.localhost. ( 1 + 3H + 1H + 1W + 1H ) + NS localhost. +host1 A 192.168.2.1 +host2 A 192.168.2.2 +host3 A 192.168.2.3 +host4 A 192.168.2.4 +host5 A 192.168.2.5 +host6 A 192.168.2.6 +host7 A 192.168.2.7 +--- + +# zone2sqlite mydomain.net. mydomain.net.zone mydomain.net.db mydomain + +will create/update the 'mydomain' table in database file 'mydomain.net.db'. + diff --git a/contrib/sdb/sqlite/sqlitedb.c b/contrib/sdb/sqlite/sqlitedb.c new file mode 100644 index 0000000..dc93db4 --- /dev/null +++ b/contrib/sdb/sqlite/sqlitedb.c @@ -0,0 +1,325 @@ +/* + * Copyright (C) 2007, 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 <config.h> + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> + +#include <sqlite3.h> + +#include <isc/mem.h> +#include <isc/print.h> +#include <isc/result.h> +#include <isc/util.h> + +#include <dns/sdb.h> +#include <dns/result.h> + +#include <named/globals.h> + +#include "sqlitedb.h" + +/* + * A simple database driver that interfaces to a SQLite database. + * + * The table must contain the fields "name", "rdtype", and "rdata", and + * is expected to contain a properly constructed zone. The program "zonetodb" + * creates such a table. + */ + +static dns_sdbimplementation_t *sqlitedb = NULL; + +typedef struct _dbinfo { + sqlite3 *db; + char *filename; + char *table; +} dbinfo_t; + + +static isc_result_t +db_connect(dbinfo_t *dbi) +{ + if (sqlite3_open(dbi->filename, &dbi->db) == SQLITE_OK) { + return (ISC_R_SUCCESS); + } else { + /* a connection is returned even if the open fails */ + sqlite3_close(dbi->db); + dbi->db = NULL; + return (ISC_R_FAILURE); + } +} + + +typedef struct _lookup_parm_t { + int i; + dns_sdblookup_t *lookup; + isc_result_t result; +} lookup_parm_t; + + +static int +sqlitedb_lookup_cb(void *p, int cc, char **cv, char **cn) +{ + lookup_parm_t *parm = p; + dns_ttl_t ttl; + char *endp; + + /* FIXME - check these(num/names); I'm assuming a mapping for now */ + char *ttlstr = cv[0]; + char *type = cv[1]; + char *data = cv[2]; + + UNUSED(cc); + UNUSED(cn); + + ttl = strtol(ttlstr, &endp, 10); + if (*endp) { + parm->result = DNS_R_BADTTL; + return 1; + } + + parm->result = dns_sdb_putrr(parm->lookup, type, ttl, data); + + if (parm->result != ISC_R_SUCCESS) + return 1; + + (parm->i)++; + + return 0; +} + + +#ifdef DNS_CLIENTINFO_VERSION +static isc_result_t +sqlitedb_lookup(const char *zone, const char *name, void *dbdata, + dns_sdblookup_t *lookup, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) +#else +static isc_result_t +sqlitedb_lookup(const char *zone, const char *name, void *dbdata, + dns_sdblookup_t *lookup) +#endif /* DNS_CLIENTINFO_VERSION */ +/* + * synchronous absolute name lookup + */ +{ + dbinfo_t *dbi = (dbinfo_t *) dbdata; + char *sql; + lookup_parm_t parm = { 0, lookup, ISC_R_SUCCESS }; + char *errmsg = NULL; + int result; + + UNUSED(zone); +#ifdef DNS_CLIENTINFO_VERSION + UNUSED(methods); + UNUSED(clientinfo); +#endif /* DNS_CLIENTINFO_VERSION */ + + sql = sqlite3_mprintf( + "SELECT TTL,RDTYPE,RDATA FROM \"%q\" WHERE " + "lower(NAME) = lower('%q')", + dbi->table, name); + + result = sqlite3_exec(dbi->db, sql, + &sqlitedb_lookup_cb, &parm, + &errmsg); + sqlite3_free(sql); + + if (result != SQLITE_OK) + return (ISC_R_FAILURE); + if (parm.i == 0) + return (ISC_R_NOTFOUND); + + return (ISC_R_SUCCESS); +} + + +typedef struct _allnodes_parm_t { + int i; + dns_sdballnodes_t *allnodes; + isc_result_t result; +} allnodes_parm_t; + + +static int +sqlitedb_allnodes_cb(void *p, int cc, char **cv, char **cn) +{ + allnodes_parm_t *parm = p; + dns_ttl_t ttl; + char *endp; + + /* FIXME - check these(num/names); I'm assuming a mapping for now */ + char *ttlstr = cv[0]; + char *name = cv[1]; + char *type = cv[2]; + char *data = cv[3]; + + UNUSED(cc); + UNUSED(cn); + + ttl = strtol(ttlstr, &endp, 10); + if (*endp) { + parm->result = DNS_R_BADTTL; + return 1; + } + + parm->result = dns_sdb_putnamedrr(parm->allnodes, name, type, ttl, data); + + if (parm->result != ISC_R_SUCCESS) + return 1; + + (parm->i)++; + + return 0; +} + + +static isc_result_t +sqlitedb_allnodes(const char *zone, + void *dbdata, + dns_sdballnodes_t *allnodes) +{ + dbinfo_t *dbi = (dbinfo_t *) dbdata; + char *sql; + allnodes_parm_t parm = { 0, allnodes, ISC_R_SUCCESS }; + char *errmsg = NULL; + int result; + + UNUSED(zone); + + sql = sqlite3_mprintf( + "SELECT TTL,NAME,RDTYPE,RDATA FROM \"%q\" ORDER BY NAME", + dbi->table); + + result = sqlite3_exec(dbi->db, sql, + &sqlitedb_allnodes_cb, &parm, + &errmsg); + sqlite3_free(sql); + + if (result != SQLITE_OK) + return (ISC_R_FAILURE); + if (parm.i == 0) + return (ISC_R_NOTFOUND); + + return (ISC_R_SUCCESS); +} + + +static void +sqlitedb_destroy(const char *zone, void *driverdata, void **dbdata) +{ + dbinfo_t *dbi = *dbdata; + + UNUSED(zone); + UNUSED(driverdata); + + if (dbi->db != NULL) + sqlite3_close(dbi->db); + if (dbi->table != NULL) + isc_mem_free(ns_g_mctx, dbi->table); + if (dbi->filename != NULL) + isc_mem_free(ns_g_mctx, dbi->filename); + + isc_mem_put(ns_g_mctx, dbi, sizeof(dbinfo_t)); +} + + +#define STRDUP_OR_FAIL(target, source) \ + do { \ + target = isc_mem_strdup(ns_g_mctx, source); \ + if (target == NULL) { \ + result = ISC_R_NOMEMORY; \ + goto cleanup; \ + } \ + } while (0); + +/* + * Create a connection to the database and save any necessary information + * in dbdata. + * + * argv[0] is the name of the database file + * argv[1] is the name of the table + */ +static isc_result_t +sqlitedb_create(const char *zone, + int argc, char **argv, + void *driverdata, void **dbdata) +{ + dbinfo_t *dbi; + isc_result_t result; + + UNUSED(zone); + UNUSED(driverdata); + + if (argc < 2) + return (ISC_R_FAILURE); + + dbi = isc_mem_get(ns_g_mctx, sizeof(dbinfo_t)); + if (dbi == NULL) + return (ISC_R_NOMEMORY); + dbi->db = NULL; + dbi->filename = NULL; + dbi->table = NULL; + + STRDUP_OR_FAIL(dbi->filename, argv[0]); + STRDUP_OR_FAIL(dbi->table, argv[1]); + + result = db_connect(dbi); + if (result != ISC_R_SUCCESS) + goto cleanup; + + *dbdata = dbi; + return (ISC_R_SUCCESS); + +cleanup: + sqlitedb_destroy(zone, driverdata, (void **)&dbi); + return (result); +} + + +/* + * Since the SQL database corresponds to a zone, the authority data should + * be returned by the lookup() function. Therefore the authority() function + * is NULL. + */ +static dns_sdbmethods_t sqlitedb_methods = { + sqlitedb_lookup, + NULL, /* authority */ + sqlitedb_allnodes, + sqlitedb_create, + sqlitedb_destroy, + NULL /* lookup2 */ +}; + + +/* + * Wrapper around dns_sdb_register(). + */ +isc_result_t +sqlitedb_init(void) +{ + unsigned int flags; + flags = 0; + return (dns_sdb_register("sqlite", &sqlitedb_methods, NULL, flags, + ns_g_mctx, &sqlitedb)); +} + + +/* + * Wrapper around dns_sdb_unregister(). + */ +void +sqlitedb_clear(void) +{ + if (sqlitedb != NULL) + dns_sdb_unregister(&sqlitedb); +} diff --git a/contrib/sdb/sqlite/sqlitedb.h b/contrib/sdb/sqlite/sqlitedb.h new file mode 100644 index 0000000..34f63dc --- /dev/null +++ b/contrib/sdb/sqlite/sqlitedb.h @@ -0,0 +1,15 @@ +/* + * Copyright (C) 2000-2002, 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 <isc/types.h> + +isc_result_t sqlitedb_init(void); + +void sqlitedb_clear(void); + diff --git a/contrib/sdb/sqlite/zone2sqlite.c b/contrib/sdb/sqlite/zone2sqlite.c new file mode 100644 index 0000000..86d577e --- /dev/null +++ b/contrib/sdb/sqlite/zone2sqlite.c @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2007, 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 <stdlib.h> +#include <string.h> + +#include <isc/buffer.h> +#include <isc/mem.h> +#include <isc/print.h> +#include <isc/result.h> + +#include <dns/db.h> +#include <dns/dbiterator.h> +#include <isc/entropy.h> +#include <dns/fixedname.h> +#include <isc/hash.h> +#include <dns/name.h> +#include <dns/rdata.h> +#include <dns/rdataset.h> +#include <dns/rdatasetiter.h> +#include <dns/rdatatype.h> +#include <dns/result.h> + +#include <sqlite3.h> + +#ifndef UNUSED +#define UNUSED(x) (x) = (x) +#endif + +/* + * Generate an SQLite table from a zone. + */ + +typedef struct _dbinfo { + sqlite3 *db; + char *filename; + char *table; +} dbinfo_t; + +dbinfo_t dbi = { NULL, NULL, NULL }; + + +static void +closeandexit(int status) +{ + if (dbi.db) { + sqlite3_close(dbi.db); + dbi.db = NULL; + } + exit(status); +} + +static void +check_result(isc_result_t result, const char *message) +{ + if (result != ISC_R_SUCCESS) { + fprintf(stderr, "%s: %s\n", message, + isc_result_totext(result)); + closeandexit(1); + } +} + +static isc_result_t +db_connect(dbinfo_t *dbi) +{ + if (sqlite3_open(dbi->filename, &dbi->db) == SQLITE_OK) { + return (ISC_R_SUCCESS); + } else { + /* a connection is returned even if the open fails */ + sqlite3_close(dbi->db); + dbi->db = NULL; + return (ISC_R_FAILURE); + } +} + +static int +add_rdata_cb(void *parm, int cc, char **cv, char **cn) +{ + UNUSED(parm); + UNUSED(cc); + UNUSED(cv); + UNUSED(cn); + + return 0; +} + + +static void +addrdata(dns_name_t *name, dns_ttl_t ttl, dns_rdata_t *rdata) +{ + unsigned char namearray[DNS_NAME_MAXTEXT + 1]; + unsigned char typearray[20]; + unsigned char dataarray[2048]; + isc_buffer_t b; + isc_result_t result; + char *sql; + char *errmsg = NULL; + int res; + + isc_buffer_init(&b, namearray, sizeof(namearray) - 1); + result = dns_name_totext(name, true, &b); + check_result(result, "dns_name_totext"); + namearray[isc_buffer_usedlength(&b)] = 0; + + isc_buffer_init(&b, typearray, sizeof(typearray) - 1); + result = dns_rdatatype_totext(rdata->type, &b); + check_result(result, "dns_rdatatype_totext"); + typearray[isc_buffer_usedlength(&b)] = 0; + + isc_buffer_init(&b, dataarray, sizeof(dataarray) - 1); + result = dns_rdata_totext(rdata, NULL, &b); + check_result(result, "dns_rdata_totext"); + dataarray[isc_buffer_usedlength(&b)] = 0; + + sql = sqlite3_mprintf( + "INSERT INTO %Q (NAME, TTL, RDTYPE, RDATA)" + " VALUES ('%q', %d, '%q', '%q') ", + dbi.table, + namearray, ttl, typearray, dataarray); + printf("%s\n", sql); + res = sqlite3_exec(dbi.db, sql, add_rdata_cb, NULL, &errmsg); + sqlite3_free(sql); + + if (res != SQLITE_OK) { + fprintf(stderr, "INSERT failed: %s\n", errmsg); + closeandexit(1); + } +} + +int +main(int argc, char *argv[]) +{ + char *sql; + int res; + char *errmsg = NULL; + char *porigin, *zonefile; + dns_fixedname_t forigin, fname; + dns_name_t *origin, *name; + dns_db_t *db = NULL; + dns_dbiterator_t *dbiter; + dns_dbnode_t *node; + dns_rdatasetiter_t *rdsiter; + dns_rdataset_t rdataset; + dns_rdata_t rdata = DNS_RDATA_INIT; + isc_mem_t *mctx = NULL; + isc_entropy_t *ectx = NULL; + isc_buffer_t b; + isc_result_t result; + + if (argc != 5) { + printf("usage: %s <zone> <zonefile> <dbfile> <dbtable>\n", argv[0]); + exit(1); + } + + porigin = argv[1]; + zonefile = argv[2]; + + dbi.filename = argv[3]; + dbi.table = argv[4]; + + dns_result_register(); + + result = isc_mem_create(0, 0, &mctx); + check_result(result, "isc_mem_create"); + result = isc_entropy_create(mctx, &ectx); + check_result(result, "isc_entropy_create"); + result = isc_hash_create(mctx, ectx, DNS_NAME_MAXWIRE); + check_result(result, "isc_hash_create"); + + isc_buffer_init(&b, porigin, strlen(porigin)); + isc_buffer_add(&b, strlen(porigin)); + origin = dns_fixedname_initname(&forigin); + result = dns_name_fromtext(origin, &b, dns_rootname, 0, NULL); + check_result(result, "dns_name_fromtext"); + + db = NULL; + result = dns_db_create(mctx, "rbt", origin, dns_dbtype_zone, + dns_rdataclass_in, 0, NULL, &db); + check_result(result, "dns_db_create"); + + result = dns_db_load(db, zonefile); + if (result == DNS_R_SEENINCLUDE) + result = ISC_R_SUCCESS; + check_result(result, "dns_db_load"); + + printf("Connecting to '%s'\n", dbi.filename); + + if ((result = db_connect(&dbi)) != ISC_R_SUCCESS) { + fprintf(stderr, "Connection to database '%s' failed\n", + dbi.filename); + closeandexit(1); + } + + sql = sqlite3_mprintf("DROP TABLE %Q ", dbi.table); + printf("%s\n", sql); + res = sqlite3_exec(dbi.db, sql, NULL, NULL, &errmsg); + sqlite3_free(sql); +#if 0 + if (res != SQLITE_OK) { + fprintf(stderr, "DROP TABLE %s failed: %s\n", + dbi.table, errmsg); + } +#endif + +#if 0 + sql = sqlite3_mprintf(sql, "BEGIN TRANSACTION"); + printf("%s\n", sql); + res = sqlite3_exec(dbi.db, sql, NULL, NULL, &errmsg); + sqlite3_free(sql); + if (res != SQLITE_OK) { + fprintf(stderr, "BEGIN TRANSACTION failed: %s\n", errmsg); + closeandexit(1); + } +#endif + + sql = sqlite3_mprintf( + "CREATE TABLE %Q " + "(NAME TEXT, TTL INTEGER, RDTYPE TEXT, RDATA TEXT) ", + dbi.table); + printf("%s\n", sql); + res = sqlite3_exec(dbi.db, sql, NULL, NULL, &errmsg); + sqlite3_free(sql); + if (res != SQLITE_OK) { + fprintf(stderr, "CREATE TABLE %s failed: %s\n", + dbi.table, errmsg); + closeandexit(1); + } + + dbiter = NULL; + result = dns_db_createiterator(db, 0, &dbiter); + check_result(result, "dns_db_createiterator()"); + + result = dns_dbiterator_first(dbiter); + check_result(result, "dns_dbiterator_first"); + + name = dns_fixedname_initname(&fname); + dns_rdataset_init(&rdataset); + dns_rdata_init(&rdata); + + while (result == ISC_R_SUCCESS) { + node = NULL; + result = dns_dbiterator_current(dbiter, &node, name); + if (result == ISC_R_NOMORE) + break; + check_result(result, "dns_dbiterator_current"); + + rdsiter = NULL; + result = dns_db_allrdatasets(db, node, NULL, 0, &rdsiter); + check_result(result, "dns_db_allrdatasets"); + + result = dns_rdatasetiter_first(rdsiter); + + while (result == ISC_R_SUCCESS) { + dns_rdatasetiter_current(rdsiter, &rdataset); + result = dns_rdataset_first(&rdataset); + check_result(result, "dns_rdataset_first"); + while (result == ISC_R_SUCCESS) { + dns_rdataset_current(&rdataset, &rdata); + addrdata(name, rdataset.ttl, &rdata); + dns_rdata_reset(&rdata); + result = dns_rdataset_next(&rdataset); + } + dns_rdataset_disassociate(&rdataset); + result = dns_rdatasetiter_next(rdsiter); + } + dns_rdatasetiter_destroy(&rdsiter); + dns_db_detachnode(db, &node); + result = dns_dbiterator_next(dbiter); + } + +#if 0 + sql = sqlite3_mprintf(sql, "COMMIT TRANSACTION "); + printf("%s\n", sql); + res = sqlite3_exec(dbi.db, sql, NULL, NULL, &errmsg); + sqlite3_free(sql); + if (res != SQLITE_OK) { + fprintf(stderr, "COMMIT TRANSACTION failed: %s\n", errmsg); + closeandexit(1); + } +#endif + + dns_dbiterator_destroy(&dbiter); + dns_db_detach(&db); + isc_hash_destroy(); + isc_entropy_detach(&ectx); + isc_mem_destroy(&mctx); + + closeandexit(0); + + exit(0); +} diff --git a/contrib/sdb/tcl/lookup.tcl b/contrib/sdb/tcl/lookup.tcl new file mode 100644 index 0000000..7672ed1 --- /dev/null +++ b/contrib/sdb/tcl/lookup.tcl @@ -0,0 +1,43 @@ +# 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. + +# +# Sample lookup procedure for tcldb +# +# This lookup procedure defines zones with identical SOA, NS, and MX +# records at the apex and a single A record that varies from zone to +# zone at the name "www". +# +# Something like this could be used by a web hosting company to serve +# a number of domains without needing to create a separate master file +# for each domain. Instead, all per-zone data (in this case, a single +# IP address) specified in the named.conf file like this: +# +# zone "a.com." { type master; database "tcl 10.0.0.42"; }; +# zone "b.com." { type master; database "tcl 10.0.0.99"; }; +# +# Since the tcldb driver doesn't support zone transfers, there should +# be at least two identically configured master servers. In the +# example below, they are assumed to be called ns1.isp.nil and +# ns2.isp.nil. +# + +proc lookup {zone name} { + global dbargs + switch -- $name { + @ { return [list \ + {SOA 86400 "ns1.isp.nil. hostmaster.isp.nil. \ + 1 3600 1800 1814400 3600"} \ + {NS 86400 "ns1.isp.nil."} \ + {NS 86400 "ns2.isp.nil."} \ + {MX 86400 "10 mail.isp.nil."} ] } + www { return [list [list A 3600 $dbargs($zone)] ] } + } + return NXDOMAIN +} diff --git a/contrib/sdb/tcl/tcldb.c b/contrib/sdb/tcl/tcldb.c new file mode 100644 index 0000000..61be7df --- /dev/null +++ b/contrib/sdb/tcl/tcldb.c @@ -0,0 +1,238 @@ +/* + * 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. + */ + + +/* + * A simple database driver that calls a Tcl procedure to define + * the contents of the DNS namespace. The procedure is loaded + * from the file lookup.tcl; look at the comments there for + * more information. + */ + +#include <config.h> + +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/stat.h> + +#include <isc/mem.h> +#include <isc/print.h> +#include <isc/result.h> +#include <isc/util.h> + +#include <dns/log.h> +#include <dns/sdb.h> + +#include <named/globals.h> + +#include <tcl.h> + +#include <tcldb.h> + +#define CHECK(op) \ + do { result = (op); \ + if (result != ISC_R_SUCCESS) return (result); \ + } while (0) + +typedef struct tcldb_driver { + isc_mem_t *mctx; + Tcl_Interp *interp; +} tcldb_driver_t; + +static tcldb_driver_t *the_driver = NULL; + +static dns_sdbimplementation_t *tcldb = NULL; + +static isc_result_t +tcldb_driver_create(isc_mem_t *mctx, tcldb_driver_t **driverp) { + int tclres; + isc_result_t result = ISC_R_SUCCESS; + tcldb_driver_t *driver = isc_mem_get(mctx, sizeof(tcldb_driver_t)); + if (driver == NULL) + return (ISC_R_NOMEMORY); + driver->mctx = mctx; + driver->interp = Tcl_CreateInterp(); + + tclres = Tcl_EvalFile(driver->interp, (char *) "lookup.tcl"); + if (tclres != TCL_OK) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_SDB, ISC_LOG_ERROR, + "initializing tcldb: " + "loading 'lookup.tcl' failed: %s", + driver->interp->result); + result = ISC_R_FAILURE; + goto cleanup; + } + *driverp = driver; + return (ISC_R_SUCCESS); + + cleanup: + isc_mem_put(mctx, driver, sizeof(tcldb_driver_t)); + return (result); + +} + +static void +tcldb_driver_destroy(tcldb_driver_t **driverp) { + tcldb_driver_t *driver = *driverp; + Tcl_DeleteInterp(driver->interp); + isc_mem_put(driver->mctx, driver, sizeof(tcldb_driver_t)); +} + +/* + * Perform a lookup, by invoking the Tcl procedure "lookup". + */ +#ifdef DNS_CLIENTINFO_VERSION +static isc_result_t +tcldb_lookup(const char *zone, const char *name, void *dbdata, + dns_sdblookup_t *lookup, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) +#else +static isc_result_t +tcldb_lookup(const char *zone, const char *name, void *dbdata, + dns_sdblookup_t *lookup) +#endif /* DNS_CLIENTINFO_VERSION */ +{ + isc_result_t result = ISC_R_SUCCESS; + int tclres; + int rrc; /* RR count */ + char **rrv; /* RR vector */ + int i; + char *cmdv[3]; + char *cmd; + +#ifdef DNS_CLIENTINFO_VERSION + UNUSED(methods); + UNUSED(clientinfo); +#endif /* DNS_CLIENTINFO_VERSION */ + + tcldb_driver_t *driver = (tcldb_driver_t *) dbdata; + + cmdv[0] = "lookup"; + cmdv[1] = zone; + cmdv[2] = name; + cmd = Tcl_Merge(3, cmdv); + tclres = Tcl_Eval(driver->interp, cmd); + Tcl_Free(cmd); + + if (tclres != TCL_OK) { + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_SDB, ISC_LOG_ERROR, + "zone '%s': tcl lookup function failed: %s", + zone, driver->interp->result); + return (ISC_R_FAILURE); + } + + if (strcmp(driver->interp->result, "NXDOMAIN") == 0) { + result = ISC_R_NOTFOUND; + goto fail; + } + + tclres = Tcl_SplitList(driver->interp, driver->interp->result, + &rrc, &rrv); + if (tclres != TCL_OK) + goto malformed; + + for (i = 0; i < rrc; i++) { + isc_result_t tmpres; + int fieldc; /* Field count */ + char **fieldv; /* Field vector */ + tclres = Tcl_SplitList(driver->interp, rrv[i], + &fieldc, &fieldv); + if (tclres != TCL_OK) { + tmpres = ISC_R_FAILURE; + goto failrr; + } + if (fieldc != 3) + goto malformed; + tmpres = dns_sdb_putrr(lookup, fieldv[0], atoi(fieldv[1]), + fieldv[2]); + Tcl_Free((char *) fieldv); + failrr: + if (tmpres != ISC_R_SUCCESS) + result = tmpres; + } + Tcl_Free((char *) rrv); + if (result == ISC_R_SUCCESS) + return (result); + + malformed: + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_SDB, ISC_LOG_ERROR, + "zone '%s': " + "malformed return value from tcl lookup function: %s", + zone, driver->interp->result); + result = ISC_R_FAILURE; + fail: + return (result); +} + +/* + * Set up per-zone state. In our case, the database arguments of the + * zone are collected into a Tcl list and assigned to an element of + * the global array "dbargs". + */ +static isc_result_t +tcldb_create(const char *zone, int argc, char **argv, + void *driverdata, void **dbdata) +{ + tcldb_driver_t *driver = (tcldb_driver_t *) driverdata; + + char *list = Tcl_Merge(argc, argv); + + Tcl_SetVar2(driver->interp, (char *) "dbargs", (char *) zone, list, 0); + + Tcl_Free(list); + + *dbdata = driverdata; + + return (ISC_R_SUCCESS); +} + +/* + * This driver does not support zone transfer, so allnodes() is NULL. + */ +static dns_sdbmethods_t tcldb_methods = { + tcldb_lookup, + NULL, /* authority */ + NULL, /* allnodes */ + tcldb_create, + NULL, /* destroy */ + NULL /* lookup2 */ +}; + +/* + * Initialize the tcldb driver. + */ +isc_result_t +tcldb_init(void) { + isc_result_t result; + int flags = DNS_SDBFLAG_RELATIVEOWNER | DNS_SDBFLAG_RELATIVERDATA; + + result = tcldb_driver_create(ns_g_mctx, &the_driver); + if (result != ISC_R_SUCCESS) + return (result); + + return (dns_sdb_register("tcl", &tcldb_methods, the_driver, flags, + ns_g_mctx, &tcldb)); +} + +/* + * Wrapper around dns_sdb_unregister(). + */ +void +tcldb_clear(void) { + if (tcldb != NULL) + dns_sdb_unregister(&tcldb); + if (the_driver != NULL) + tcldb_driver_destroy(&the_driver); +} diff --git a/contrib/sdb/tcl/tcldb.h b/contrib/sdb/tcl/tcldb.h new file mode 100644 index 0000000..1d4d16c --- /dev/null +++ b/contrib/sdb/tcl/tcldb.h @@ -0,0 +1,18 @@ +/* + * 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. + */ + + +#include <isc/types.h> + +isc_result_t tcldb_init(void); + +void tcldb_clear(void); + diff --git a/contrib/sdb/time/timedb.c b/contrib/sdb/time/timedb.c new file mode 100644 index 0000000..11633ca --- /dev/null +++ b/contrib/sdb/time/timedb.c @@ -0,0 +1,148 @@ +/* + * 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. + */ + + +/* + * A simple database driver that enables the server to return the + * current time in a DNS record. + */ + +#include <config.h> + +#include <string.h> +#include <stdio.h> +#include <time.h> + +#include <isc/print.h> +#include <isc/result.h> +#include <isc/util.h> + +#include <dns/sdb.h> + +#include <named/globals.h> + +#include "timedb.h" + +static dns_sdbimplementation_t *timedb = NULL; + +/* + * This database operates on relative names. + * + * "time" and "@" return the time in a TXT record. + * "clock" is a CNAME to "time" + * "current" is a DNAME to "@" (try time.current.time) + */ +#ifdef DNS_CLIENTINFO_VERSION +static isc_result_t +timedb_lookup(const char *zone, const char *name, void *dbdata, + dns_sdblookup_t *lookup, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) +#else +static isc_result_t +timedb_lookup(const char *zone, const char *name, void *dbdata, + dns_sdblookup_t *lookup) +#endif /* DNS_CLIENTINFO_VERSION */ +{ + isc_result_t result; + + UNUSED(zone); + UNUSED(dbdata); +#ifdef DNS_CLIENTINFO_VERSION + UNUSED(methods); + UNUSED(clientinfo); +#endif /* DNS_CLIENTINFO_VERSION */ + + if (strcmp(name, "@") == 0 || strcmp(name, "time") == 0) { + time_t now = time(NULL); + char buf[100]; + int n; + + /* + * Call ctime to create the string, put it in quotes, and + * remove the trailing newline. + */ + n = snprintf(buf, sizeof(buf), "\"%s", ctime(&now)); + if (n < 0) + return (ISC_R_FAILURE); + buf[n - 1] = '\"'; + result = dns_sdb_putrr(lookup, "txt", 1, buf); + if (result != ISC_R_SUCCESS) + return (ISC_R_FAILURE); + } else if (strcmp(name, "clock") == 0) { + result = dns_sdb_putrr(lookup, "cname", 1, "time"); + if (result != ISC_R_SUCCESS) + return (ISC_R_FAILURE); + } else if (strcmp(name, "current") == 0) { + result = dns_sdb_putrr(lookup, "dname", 1, "@"); + if (result != ISC_R_SUCCESS) + return (ISC_R_FAILURE); + } else + return (ISC_R_NOTFOUND); + + return (ISC_R_SUCCESS); +} + +/* + * lookup() does not return SOA or NS records, so authority() must be defined. + */ +static isc_result_t +timedb_authority(const char *zone, void *dbdata, dns_sdblookup_t *lookup) { + isc_result_t result; + + UNUSED(zone); + UNUSED(dbdata); + + result = dns_sdb_putsoa(lookup, "localhost.", "root.localhost.", 0); + if (result != ISC_R_SUCCESS) + return (ISC_R_FAILURE); + + result = dns_sdb_putrr(lookup, "ns", 86400, "ns1.localdomain."); + if (result != ISC_R_SUCCESS) + return (ISC_R_FAILURE); + result = dns_sdb_putrr(lookup, "ns", 86400, "ns2.localdomain."); + if (result != ISC_R_SUCCESS) + return (ISC_R_FAILURE); + + return (ISC_R_SUCCESS); +} + +/* + * This zone does not support zone transfer, so allnodes() is NULL. There + * is no database specific data, so create() and destroy() are NULL. + */ +static dns_sdbmethods_t timedb_methods = { + timedb_lookup, + timedb_authority, + NULL, /* allnodes */ + NULL, /* create */ + NULL, /* destroy */ + NULL /* lookup2 */ +}; + +/* + * Wrapper around dns_sdb_register(). + */ +isc_result_t +timedb_init(void) { + unsigned int flags; + flags = DNS_SDBFLAG_RELATIVEOWNER | DNS_SDBFLAG_RELATIVERDATA; + return (dns_sdb_register("time", &timedb_methods, NULL, flags, + ns_g_mctx, &timedb)); +} + +/* + * Wrapper around dns_sdb_unregister(). + */ +void +timedb_clear(void) { + if (timedb != NULL) + dns_sdb_unregister(&timedb); +} diff --git a/contrib/sdb/time/timedb.h b/contrib/sdb/time/timedb.h new file mode 100644 index 0000000..3089988 --- /dev/null +++ b/contrib/sdb/time/timedb.h @@ -0,0 +1,18 @@ +/* + * 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. + */ + + +#include <isc/types.h> + +isc_result_t timedb_init(void); + +void timedb_clear(void); + |