From: Peter van Dijk Date: Mon, 29 Jun 2015 14:34:09 +0000 (+0200) Subject: implement gODBC backend; loosely based on old godbc code by Michel Stol X-Git-Tag: dnsdist-1.0.0-alpha1~207^2~1 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=4f983d1bebbd6645dbf6d3aa11f16d13e99d390a;p=pdns implement gODBC backend; loosely based on old godbc code by Michel Stol --- diff --git a/.travis.yml b/.travis.yml index 15a6156b0..0f5e50605 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,6 +33,7 @@ before_script: libopendbx1-dev libopendbx1-sqlite3 libp11-kit-dev + libsqliteodbc libtolua-dev libtool libyaml-cpp-dev @@ -52,6 +53,7 @@ before_script: softhsm time unbound-host + unixodbc-dev xmlto - virtualenv $HOME/.venv - source $HOME/.venv/bin/activate @@ -82,12 +84,13 @@ before_script: - sudo chmod 0644 /etc/softhsm/softhsm.conf - sudo chmod 0777 /var/lib/softhsm - p11-kit -l # ensure it's ok + - echo -e "[pdns-sqlite3-1]\nDriver = SQLite3\nDatabase = ${PWD}/regression-tests/pdns.sqlite3\n\n[pdns-sqlite3-2]\nDriver = SQLite3\nDatabase = ${PWD}/regression-tests/pdns.sqlite32\n" > ${HOME}/.odbc.ini script: - ./bootstrap #Build without --enable-botan1.10 option, Botan/SoftHSM conflict #2496 - source $HOME/.venv/bin/activate - ./configure - --with-dynmodules='bind gmysql geoip gpgsql gsqlite3 mydns tinydns pipe remote random opendbx ldap lua' + --with-dynmodules='bind gmysql geoip godbc gpgsql gsqlite3 mydns tinydns pipe remote random opendbx ldap lua' --with-modules='' --with-sqlite3 --enable-unit-tests @@ -175,6 +178,7 @@ script: - ./timestamp ./start-test-stop 5300 gsqlite3-nsec3-narrow - ./timestamp ./start-test-stop 5300 mydns - ./timestamp ./start-test-stop 5300 opendbx-sqlite3 + - GODBC_SQLITE3_DSN=pdns-sqlite3-1 ./timestamp ./start-test-stop 5300 godbc_sqlite3-nsec3 - travis_retry ./timestamp timeout 120s ./start-test-stop 5300 remotebackend-pipe - travis_retry ./timestamp timeout 120s ./start-test-stop 5300 remotebackend-pipe-dnssec - travis_retry ./timestamp timeout 120s ./start-test-stop 5300 remotebackend-unix diff --git a/build-scripts/build-auth-rpm b/build-scripts/build-auth-rpm index fb79abb7b..80cfcd877 100755 --- a/build-scripts/build-auth-rpm +++ b/build-scripts/build-auth-rpm @@ -337,6 +337,16 @@ BuildRequires: postgresql-devel %description backend-postgresql This package contains the gpgsql backend for %{name} +%package backend-odbc +Summary: UnixODBC backend for %{name} +Group: System Environment/Daemons +Requires: %{name}%{?_isa} = %{version}-%{release} +BuildRequires: unixodbc-devel +%global backends %{backends} godbc + +%description backend-odbc +This package contains the godbc backend for %{name} + %package backend-pipe Summary: Pipe backend for %{name} Group: System Environment/Daemons @@ -522,6 +532,10 @@ exit 0 %doc modules/gpgsqlbackend/nodnssec-3.x_to_3.4.0_schema.pgsql.sql %{_libdir}/%{name}/libgpgsqlbackend.so +%files backend-odbc +%doc modules/godbcbackend/schema.mssql.sql +%{_libdir}/%{name}/libgodbcbackend.so + %files backend-pipe %{_libdir}/%{name}/libpipebackend.so diff --git a/build-scripts/debian-authoritative/config/pdns.local.godbc.conf b/build-scripts/debian-authoritative/config/pdns.local.godbc.conf new file mode 100644 index 000000000..a80b71b9d --- /dev/null +++ b/build-scripts/debian-authoritative/config/pdns.local.godbc.conf @@ -0,0 +1,19 @@ +# godbc Configuration +# +# Uncomment to launch the godbc backend +#launch+=godbc + +################################# +# godbc-datasource Datasource (DSN) to use +# +# godbc-datasource=PowerDNS + +################################# +# godbc-username User to connect as +# +# godbc-username=powerdns + +################################# +# godbc-password Password to connect with +# +# godbc-password= diff --git a/build-scripts/debian-authoritative/control b/build-scripts/debian-authoritative/control index 6af8203df..25880ff11 100644 --- a/build-scripts/debian-authoritative/control +++ b/build-scripts/debian-authoritative/control @@ -4,7 +4,7 @@ Priority: extra Standards-Version: 3.9.6 Maintainer: PowerDNS Autobuilder Origin: PowerDNS -Build-Depends: debhelper (>= 9~), dh-autoreconf, dh-systemd, po-debconf, libtool, flex, bison, libmysqlclient-dev, libpq-dev, libssl-dev, libpolarssl-dev, libgdbm-dev, libldap2-dev, libsqlite3-dev, dpkg-dev (>= 1.17.0~), libboost-dev, libboost-serialization-dev, libboost-program-options-dev, libboost-test-dev, autotools-dev, automake, autoconf, liblua5.2-dev, pkg-config, libcrypto++-dev, ragel, libgmp-dev, libbotan1.10-dev, libcurl4-openssl-dev, libzmq-dev, libyaml-cpp-dev (>= 0.5), libgeoip-dev, libopendbx1-dev, libcdb-dev +Build-Depends: debhelper (>= 9~), dh-autoreconf, dh-systemd, po-debconf, libtool, flex, bison, libmysqlclient-dev, libpq-dev, libssl-dev, libpolarssl-dev, libgdbm-dev, libldap2-dev, libsqlite3-dev, dpkg-dev (>= 1.17.0~), libboost-dev, libboost-serialization-dev, libboost-program-options-dev, libboost-test-dev, autotools-dev, automake, autoconf, liblua5.2-dev, pkg-config, libcrypto++-dev, ragel, libgmp-dev, libbotan1.10-dev, libcurl4-openssl-dev, libzmq-dev, libyaml-cpp-dev (>= 0.5), libgeoip-dev, libopendbx1-dev, libcdb-dev, unixodbc-dev (>= 2.3.1) Homepage: http://www.powerdns.com/ Package: pdns-server @@ -118,6 +118,19 @@ Description: generic MySQL backend for PowerDNS This package contains a generic MySQL backend for the PowerDNS nameserver. It has configurable SQL statements. +Package: pdns-backend-odbc +Architecture: any +Depends: pdns-server (>= ${source:Version}), ucf (>= 0.28), ${shlibs:Depends}, ${misc:Depends} +Provides: pdns-backend +Description: generic UnixODBC backend for PowerDNS + PowerDNS is a versatile nameserver which supports a large number + of different backends ranging from simple zonefiles to relational + databases and load balancing/failover algorithms. + PowerDNS tries to emphasize speed and security. + . + This package contains a generic UnixODBC backend for the PowerDNS + nameserver. It has configurable SQL statements. + Package: pdns-backend-pgsql Architecture: any Depends: pdns-server (>= ${source:Version}), ucf (>= 0.28), ${shlibs:Depends}, ${misc:Depends} diff --git a/build-scripts/debian-authoritative/pdns-backend-odbc.docs b/build-scripts/debian-authoritative/pdns-backend-odbc.docs new file mode 100644 index 000000000..76cc1e02c --- /dev/null +++ b/build-scripts/debian-authoritative/pdns-backend-odbc.docs @@ -0,0 +1 @@ +modules/godbcbackend/schema.mssql.sql diff --git a/build-scripts/debian-authoritative/pdns-backend-odbc.install b/build-scripts/debian-authoritative/pdns-backend-odbc.install new file mode 100644 index 000000000..8a5062dd5 --- /dev/null +++ b/build-scripts/debian-authoritative/pdns-backend-odbc.install @@ -0,0 +1,2 @@ +usr/lib/*/pdns/libgodbcbackend.so* +debian/config/pdns.local.godbc.conf usr/share/pdns-backend-godbc diff --git a/build-scripts/debian-authoritative/pdns-backend-odbc.postinst b/build-scripts/debian-authoritative/pdns-backend-odbc.postinst new file mode 100644 index 000000000..ace1f6343 --- /dev/null +++ b/build-scripts/debian-authoritative/pdns-backend-odbc.postinst @@ -0,0 +1,54 @@ +#!/bin/sh +# +# postinst script for pdns-backend-mysql + +set -e + +if [ -n "$PDNSDEBUG" ]; then + echo "now debugging $0 $@" + set -x +fi + +PKGNAME="pdns-backend-odbc" + +# rename ucf-conffile. This was mostly stolen from cacti.postinst after +# a short discussion on debian-mentors, see +# http://lists.debian.org/debian-mentors/2013/07/msg00027.html +# and the following thread. Thanks to Paul Gevers +renameconffile() { + oldname="$1" + newname="$2" + sourcefile="$3" + if [ -f $oldname ] ; then + if [ ! -e $newname ] ; then + mv $oldname $newname +# else: Don't do anything, leave old file in place + fi + ucf --purge $oldname + ucfr --purge $PKGNAME $oldname + elif [ ! -e $newname ] ; then +# The file was removed, we should respect that. Unfortunately, we don't +# have a proper way to tell ucf that for the new location, so we need +# to hack it a bit. +# We only need to do this if the target does not already exist. If the +# target already exists, we can later call ucf straight as there +# is already a version of the file available, althought never +# provided by this package, but we can just propose the new file anyway. + ucf --debconf-ok $sourcefile $newname + ucfr $PKGNAME $newname + rm -f $newname + fi +} + +# Activate trigger +dpkg-trigger pdns-server + +ucfr $PKGNAME /etc/powerdns/pdns.d/pdns.local.godbc.conf + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 + diff --git a/build-scripts/debian-authoritative/pdns-backend-odbc.postrm b/build-scripts/debian-authoritative/pdns-backend-odbc.postrm new file mode 100644 index 000000000..d3e4a157c --- /dev/null +++ b/build-scripts/debian-authoritative/pdns-backend-odbc.postrm @@ -0,0 +1,47 @@ +#!/bin/sh +# +# Post removal + +set -e + +if [ -n "$PDNSDEBUG" ]; then + echo "now debugging $0 $@" + set -x +fi + +PKGNAME="pdns-backend-odbc" + +# Remove configuration file +if [ "$1" = "purge" ]; then + # Remove files registered with ucf. + # this has been pulled from aide-common.postrm + + UCF="ucf" + UCFR="ucfr" + + if command -v ucfq >/dev/null; then + for file in $(ucfq --with-colons "$PKGNAME" | cut --delimiter=: --fields=1); do + for ext in '~' '%' .bak .dpkg-tmp .dpkg-new .dpkg-old .dpkg-dist; do + rm -f ${file}$ext + done + rm -f ${file} + + if command -v $UCF >/dev/null; then + $UCF --purge ${file} + fi + if command -v $UCFR >/dev/null; then + $UCFR --purge $PKGNAME ${file} + fi + done + else + echo >&2 "ucf no longer installed, not cleaning up" + fi +fi + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 + diff --git a/build-scripts/debian-authoritative/pdns-backend-odbc.prerm b/build-scripts/debian-authoritative/pdns-backend-odbc.prerm new file mode 100644 index 000000000..d02c6bd38 --- /dev/null +++ b/build-scripts/debian-authoritative/pdns-backend-odbc.prerm @@ -0,0 +1,18 @@ +#!/bin/sh +# +# Pre removal + +set -e + +# Stop pdns. +if [ -x "/etc/init.d/pdns" ]; then + invoke-rc.d pdns stop || exit $? +fi + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 + diff --git a/build-scripts/debian-authoritative/rules b/build-scripts/debian-authoritative/rules index 6080d3ad9..94b8f9c14 100755 --- a/build-scripts/debian-authoritative/rules +++ b/build-scripts/debian-authoritative/rules @@ -5,7 +5,7 @@ version := $(shell dpkg-parsechangelog -SVersion).$(shell dpkg-vendor --query Ve CXXFLAGS += -DPACKAGEVERSION='"$(version)"' # Backends -backends := bind ldap pipe gmysql gpgsql gsqlite3 geoip lua mydns remote random opendbx tinydns +backends := bind ldap pipe gmysql godbc gpgsql gsqlite3 geoip lua mydns remote random opendbx tinydns DEB_HOST_MULTIARCH ?= $(shell dpkg-architecture -qDEB_HOST_MULTIARCH) diff --git a/build-scripts/test-auth b/build-scripts/test-auth index 56a61ef33..8c1f0e826 100755 --- a/build-scripts/test-auth +++ b/build-scripts/test-auth @@ -1,7 +1,20 @@ #!/bin/sh set -x -[ -n "$1" ] && exit 0 # avoid running the whole suite on specifically targeted builders +context='' +# poor mans option parsing +if [ -n "$1" ]; then + if [ "$1" != "odbc" ]; then + echo "invalid argument" + exit 1 + fi + context=odbc + if [ -n "$2" ]; then + echo "too many arguments" + exit 1 + fi +fi + export PDNS=/usr/sbin/pdns_server export PDNS2=$PDNS export SDIG=/usr/bin/sdig @@ -15,9 +28,11 @@ export GEM_HOME=${PWD}/gems mkdir -p $GEM_HOME export PATH="${GEM_HOME}/bin:$PATH" -cd modules/remotebackend -ruby -S bundle install -cd ../../ +if [ -z "$context"]; then + cd modules/remotebackend + ruby -S bundle install + cd ../../ +fi MODULES="" @@ -39,62 +54,97 @@ cd .. EXITCODE=0 -export geoipregion=oc geoipregionip=1.2.3.4 -./timestamp ./start-test-stop 5300 bind-both || EXITCODE=1 -./timestamp ./start-test-stop 5300 bind-dnssec-both || EXITCODE=1 - -# No PKCS#11 in packages -#SETUP_SOFTHSM=y ./timestamp ./start-test-stop 5300 bind-dnssec-pkcs11 || EXITCODE=1 -./timestamp ./start-test-stop 5300 bind-dnssec-nsec3-both || EXITCODE=1 -./timestamp ./start-test-stop 5300 bind-dnssec-nsec3-optout-both || EXITCODE=1 -./timestamp ./start-test-stop 5300 bind-dnssec-nsec3-narrow || EXITCODE=1 -./timestamp ./start-test-stop 5300 bind-hybrid-nsec3 || EXITCODE=1 - -# Adding extra IPs to docker containers in not supported :( -#./timestamp ./start-test-stop 5300 geoipbackend || EXITCODE=1 -#./timestamp ./start-test-stop 5300 geoipbackend-nsec3-narrow || EXITCODE=1 - -./timestamp ./start-test-stop 5300 gmysql-nodnssec-both || EXITCODE=1 -./timestamp ./start-test-stop 5300 gmysql-both || EXITCODE=1 -./timestamp ./start-test-stop 5300 gmysql-nsec3-both || EXITCODE=1 -./timestamp ./start-test-stop 5300 gmysql-nsec3-optout-both || EXITCODE=1 -./timestamp ./start-test-stop 5300 gmysql-nsec3-narrow || EXITCODE=1 - -./timestamp ./start-test-stop 5300 gpgsql-nodnssec-both || EXITCODE=1 -./timestamp ./start-test-stop 5300 gpgsql-both || EXITCODE=1 -./timestamp ./start-test-stop 5300 gpgsql-nsec3-both || EXITCODE=1 -./timestamp ./start-test-stop 5300 gpgsql-nsec3-optout-both || EXITCODE=1 -./timestamp ./start-test-stop 5300 gpgsql-nsec3-narrow || EXITCODE=1 - -./timestamp ./start-test-stop 5300 gsqlite3-nodnssec-both || EXITCODE=1 -./timestamp ./start-test-stop 5300 gsqlite3-both || EXITCODE=1 -./timestamp ./start-test-stop 5300 gsqlite3-nsec3-both || EXITCODE=1 -./timestamp ./start-test-stop 5300 gsqlite3-nsec3-optout-both || EXITCODE=1 -./timestamp ./start-test-stop 5300 gsqlite3-nsec3-narrow || EXITCODE=1 - -./timestamp ./start-test-stop 5300 mydns || EXITCODE=1 - -./timestamp ./start-test-stop 5300 opendbx-sqlite3 || EXITCODE=1 - -./timestamp timeout 120s ./start-test-stop 5300 remotebackend-pipe || EXITCODE=1 -./timestamp timeout 120s ./start-test-stop 5300 remotebackend-pipe-dnssec || EXITCODE=1 -./timestamp timeout 120s ./start-test-stop 5300 remotebackend-unix || EXITCODE=1 -./timestamp timeout 120s ./start-test-stop 5300 remotebackend-unix-dnssec || EXITCODE=1 -./timestamp timeout 120s ./start-test-stop 5300 remotebackend-http || EXITCODE=1 -./timestamp timeout 120s ./start-test-stop 5300 remotebackend-http-dnssec || EXITCODE=1 - - -# No 0MQ in the PowerDNS packages -#./timestamp timeout 120s ./start-test-stop 5300 remotebackend-zeromq || EXITCODE=1 -#./timestamp timeout 120s ./start-test-stop 5300 remotebackend-zeromq-dnssec || EXITCODE=1 - -./timestamp ./start-test-stop 5300 tinydns || EXITCODE=1 - -cd ../regression-tests.nobackend/ - -# The package builds define other dirs, so the distconf test fails, so skip it -touch pdnsconfdist/skip - -./runtests || EXITCODE=1 +if [ -z "$context" ]; then + export geoipregion=oc geoipregionip=1.2.3.4 + ./timestamp ./start-test-stop 5300 bind-both || EXITCODE=1 + ./timestamp ./start-test-stop 5300 bind-dnssec-both || EXITCODE=1 + + # No PKCS#11 in packages + #SETUP_SOFTHSM=y ./timestamp ./start-test-stop 5300 bind-dnssec-pkcs11 || EXITCODE=1 + ./timestamp ./start-test-stop 5300 bind-dnssec-nsec3-both || EXITCODE=1 + ./timestamp ./start-test-stop 5300 bind-dnssec-nsec3-optout-both || EXITCODE=1 + ./timestamp ./start-test-stop 5300 bind-dnssec-nsec3-narrow || EXITCODE=1 + ./timestamp ./start-test-stop 5300 bind-hybrid-nsec3 || EXITCODE=1 + + # Adding extra IPs to docker containers in not supported :( + #./timestamp ./start-test-stop 5300 geoipbackend || EXITCODE=1 + #./timestamp ./start-test-stop 5300 geoipbackend-nsec3-narrow || EXITCODE=1 + + ./timestamp ./start-test-stop 5300 gmysql-nodnssec-both || EXITCODE=1 + ./timestamp ./start-test-stop 5300 gmysql-both || EXITCODE=1 + ./timestamp ./start-test-stop 5300 gmysql-nsec3-both || EXITCODE=1 + ./timestamp ./start-test-stop 5300 gmysql-nsec3-optout-both || EXITCODE=1 + ./timestamp ./start-test-stop 5300 gmysql-nsec3-narrow || EXITCODE=1 + + ./timestamp ./start-test-stop 5300 gpgsql-nodnssec-both || EXITCODE=1 + ./timestamp ./start-test-stop 5300 gpgsql-both || EXITCODE=1 + ./timestamp ./start-test-stop 5300 gpgsql-nsec3-both || EXITCODE=1 + ./timestamp ./start-test-stop 5300 gpgsql-nsec3-optout-both || EXITCODE=1 + ./timestamp ./start-test-stop 5300 gpgsql-nsec3-narrow || EXITCODE=1 + + ./timestamp ./start-test-stop 5300 gsqlite3-nodnssec-both || EXITCODE=1 + ./timestamp ./start-test-stop 5300 gsqlite3-both || EXITCODE=1 + ./timestamp ./start-test-stop 5300 gsqlite3-nsec3-both || EXITCODE=1 + ./timestamp ./start-test-stop 5300 gsqlite3-nsec3-optout-both || EXITCODE=1 + ./timestamp ./start-test-stop 5300 gsqlite3-nsec3-narrow || EXITCODE=1 + + ./timestamp ./start-test-stop 5300 mydns || EXITCODE=1 + + ./timestamp ./start-test-stop 5300 opendbx-sqlite3 || EXITCODE=1 + + ./timestamp timeout 120s ./start-test-stop 5300 remotebackend-pipe || EXITCODE=1 + ./timestamp timeout 120s ./start-test-stop 5300 remotebackend-pipe-dnssec || EXITCODE=1 + ./timestamp timeout 120s ./start-test-stop 5300 remotebackend-unix || EXITCODE=1 + ./timestamp timeout 120s ./start-test-stop 5300 remotebackend-unix-dnssec || EXITCODE=1 + ./timestamp timeout 120s ./start-test-stop 5300 remotebackend-http || EXITCODE=1 + ./timestamp timeout 120s ./start-test-stop 5300 remotebackend-http-dnssec || EXITCODE=1 + + + # No 0MQ in the PowerDNS packages + #./timestamp timeout 120s ./start-test-stop 5300 remotebackend-zeromq || EXITCODE=1 + #./timestamp timeout 120s ./start-test-stop 5300 remotebackend-zeromq-dnssec || EXITCODE=1 + + ./timestamp ./start-test-stop 5300 tinydns || EXITCODE=1 + + cd ../regression-tests.nobackend/ + + # The package builds define other dirs, so the distconf test fails, so skip it + touch pdnsconfdist/skip + + ./runtests || EXITCODE=1 +elif [ "$context" = "odbc" ]; then + cat > ~/.odbc.ini << __EOF__ +[pdns-sqlite3-1] +Driver = SQLite3 +Database = $(pwd)/pdns.sqlite3 + +[pdns-sqlite3-2] +Driver = SQLite3 +Database = $(pwd)/pdns.sqlite32 + +[pdns-mssql] +Driver=FreeTDS +Trace=No +Server=pdns-odbc-regress-sql-1.database.windows.net +Port=1433 +Database=pdns +TDS_Version=7.1 +ClientCharset=UTF-8 +__EOF__ + + set +x + . ~/.mssql-credentials + set -x + export GODBC_SQLITE3_DSN=pdns-sqlite3-1 + ./timestamp timeout 120s ./start-test-stop 5300 godbc_sqlite3-nodnssec || EXITCODE=1 + export GODBC_MSSQL_DSN=pdns-mssql + export GODBC_MSSQL_USERNAME + export GODBC_MSSQL_PASSWORD + ./timestamp timeout 3600s ./start-test-stop 5300 godbc_mssql-nodnssec || EXITCODE=1 + ./timestamp timeout 3600s ./start-test-stop 5300 godbc_mssql || EXITCODE=1 + ./timestamp timeout 3600s ./start-test-stop 5300 godbc_mssql-nsec3 || EXITCODE=1 + ./timestamp timeout 3600s ./start-test-stop 5300 godbc_mssql-nsec3-optout || EXITCODE=1 + ./timestamp timeout 3600s ./start-test-stop 5300 godbc_mssql-nsec3-narrow || EXITCODE=1 +fi exit $EXITCODE diff --git a/configure.ac b/configure.ac index 06e9c8dd1..8634b8a41 100644 --- a/configure.ac +++ b/configure.ac @@ -242,6 +242,9 @@ for a in $modules $dynmodules; do PDNS_WITH_ORACLE needoracle=yes ;; + godbc) + PDNS_WITH_UNIXODBC + ;; mydns|gmysql|pdns) PDNS_WITH_MYSQL ;; @@ -351,6 +354,7 @@ AC_CONFIG_FILES([ modules/bindbackend/Makefile modules/geoipbackend/Makefile modules/gmysqlbackend/Makefile + modules/godbcbackend/Makefile modules/goraclebackend/Makefile modules/gpgsqlbackend/Makefile modules/gsqlite3backend/Makefile diff --git a/docs/markdown/authoritative/backend-godbc.md b/docs/markdown/authoritative/backend-godbc.md new file mode 100644 index 000000000..79a47fd96 --- /dev/null +++ b/docs/markdown/authoritative/backend-godbc.md @@ -0,0 +1,120 @@ +# Generic ODBC Backend +| | | +|:--|:--| +|Native|Yes| +|Master|Yes| +|Slave|Yes| +|Superslave|Yes| +|Autoserial|Yes| +|Case|All lower| +|DNSSEC|Yes| +|Disabled data|Yes| +|Comments|Yes| +|Module name|godbc| +|Launch name|godbc| + +The Generic ODBC Backend (godbc) is a child of the Generic SQL (gsql) backend, +similar to the gmysql and gpgsql backends. It uses [UnixODBC](http://www.unixodbc.org/) +and installed drivers to connect to the databases supported by said drivers. + +**Warning**: When there is a more specific generic sql backend (like goracle or +gmysql), it is highly recommended to use that backend instead! + +# Enabling the backend +When building PowerDNS yourself, append `godbc` to `--with-modules` or +`--with-dynmodules`. It is expected that most pre-built packages contain this +backend or be separately installable. + +# Configuration Parameters +This section only details the configuration of PowerDNS for use with ODBC. For +ODBC related configuration, please see UnixODBC website/documentation and the +documentation for the driver you intend to use. + +## `godbc-datasource` + +* String +* Default: PowerDNS + +The datasource (DSN) to use. This must be configured in the `odbc.ini` file, +usually found in `/etc/`, but this depends your local setup. + +## `godbc-username` + +* String +* Default: powerdns + +The user to connect to the datasource. + +## `godbc-password` + +* String +* Default is empty + +The password to connect with the datasource. + +# Connecting to Microsoft SQL Server +**note**: In order to connect to Microsoft SQL Server, you will need at least +version 3.2.0 of UnixODBC. FreeDTS has been tested with versions 0.91 and 0.95. + +Install the [FreeTDS](http://www.freetds.org/) driver for UnixODBC, either by +compiling or getting it from our distribution's repository and configure your +`/etc/odbcinst.ini` with the driver, e.g.: + +``` +[FreeTDS] +Description=v0.95.8 with protocol v7.1 +Driver=/usr/local/lib/libtdsodbc.so +UsageCount=1 +``` + +And add the datasource to your `/etc/odbc.ini`, e.g: +``` +[pdns1] +Driver=FreeTDS +Trace=No +Server=server.example.net +Port=1433 +Database=pdns-1 +TDS_Version=7.1 +``` + +(For our tests, we add `ClientCharset=UTF-8` as well. YMMV.) + +You can now test the connection with `isql pdns1 USERNAME PASSWORD`. + +## Loading the schema into the database +For convenience, a schema for MS SQL Server has been created: +(Note: This schema can also be found in the PowerDNS source as + `modules/godbcbackend/schema.mssql.sql`). + +``` +!!include=../modules/godbcbackend/schema.mssql.sql +``` + +Load this into the database as follows: +`cat schema.mssql.sql | tr '\n' ' ' | isql pdns1 USERNAME PASSWORD -b`. + +## Loading records into the database +Loading records is the same as with any SQL backend, just add them +using SQL-queries. Should you want to use [`zone2sql`](migration.md#zone2sql), +use the `--sqlite` option for correctly formatted SQL. + +## Configuring PowerDNS +Add the options required to your `pdns.conf`: + +``` +launch=godbc +godbc-datasource=pdns1 +godbc-username=USERNAME +godbc-password=PASSWORD +``` + +Now restart PowerDNS and you're done. Just don't forget to add zones and +records to the database. + +## Possible issues +It might be that you need to compile FreeTDS with the `--tds-version=7.1` to +connect to SQL Server. + +When connecting to a database hosted with Microsoft Azure, FreeTDS must be +compiled with OpenSSL, use the `--with-openssl` configure flag. diff --git a/docs/markdown/authoritative/index.md b/docs/markdown/authoritative/index.md index c788c1d5c..f9a306b3e 100644 --- a/docs/markdown/authoritative/index.md +++ b/docs/markdown/authoritative/index.md @@ -20,6 +20,7 @@ The following table describes the capabilities of the backends. | [LDAP](backend-ldap.md) | Unmaintained | Yes | No | No | No | No | No | Unknown (No) | Unknown (No) | Unknown | | [MySQL](backend-generic-mypgsql.md) | Supported | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | `gmysql` | | [MyDNS](backend-mydns.md) | Supported | Yes | No | No | No | No | No | No | No | `mydns` | +| [ODBC](backend-godbc.md) | Supported | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes| `godbc` | | [OpenDBX](backend-opendbx.md) | Supported | Yes | Yes | Yes | Yes | Unknown (No) | No | Unknown (No) | Unknown (No) | `opendbx` | | [Oracle](backend-oracle.md) | Supported | Yes | Yes | Yes | Yes | Yes | Yes | Unknown (No) | No | `oracle` | | [Pipe](backend-pipe.md) | Supported | Yes | No | No | No | No | Partial (no delegation, no key storage) | No | No | `pipe` | diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 2475ef93a..d1f12800a 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -40,6 +40,7 @@ pages: - Authoritative Backends: - BIND: authoritative/backend-bind.md - Generic MySQL and PostgreSQL: authoritative/backend-generic-mypgsql.md + - Generic ODBC: authoritative/backend-godbc.md - GeoIP: authoritative/backend-geoip.md - Generic SQLite: authoritative/backend-gsqlite.md - MyDNS: authoritative/backend-mydns.md diff --git a/m4/pdns_with_unixodbc.m4 b/m4/pdns_with_unixodbc.m4 new file mode 100644 index 000000000..95c26c948 --- /dev/null +++ b/m4/pdns_with_unixodbc.m4 @@ -0,0 +1,106 @@ +AC_DEFUN([PDNS_WITH_UNIXODBC],[ + AC_ARG_WITH([unixodbc], + [AS_HELP_STRING([--with-unixodbc=], [root directory path of unixODBC installation])], + [ + UNIXODBC_LIBS_check="$withval/lib/unixodbc $with_unixodbc/lib" + UNIXODBC_CFLAGS_check="$withval/include/unixodbc" + UNIXODBC_config_check="$withval/bin/odbc_config" + ], + [ + UNIXODBC_LIBS_check="/usr/local/unixodbc/lib/unixodbc /usr/local/lib/unixodbc /opt/unixodbc/lib/unixodbc \ + /usr/lib/unixodbc /usr/lib64/unixodbc /usr/local/unixodbc/lib /usr/local/lib /opt/unixodbc/lib /usr/lib \ + /usr/sfw/lib/ /usr/lib/odbc /usr/lib/x86_64-linux-gnu $full_libdir" + UNIXODBC_CFLAGS_check="/usr/local/unixodbc/include/unixodbc /usr/local/include/unixodbc \ + /opt/unixodbc/include/unixodbc /opt/unixodbc/include /usr/include/unixodbc /usr/sfw/include/unixodbc \ + /usr/include /usr/local/include" + ] + ) + + AC_ARG_WITH([odbc-config], + [AS_HELP_STRING([--with-odbc-config=], [file path to odbc_config])], + [UNIXODBC_config_check=$withval] + ) + + AC_ARG_WITH([unixodbc-lib], + [AS_HELP_STRING([--with-unixodbc-lib=], [directory path of unixODBC library installation])], + [ + UNIXODBC_LIBS_check="$withval/lib/unixodbc $withval/unixodbc $withval" + UNIXODBC_config_check="skip" + ] + ) + + AC_ARG_WITH([unixodbc-includes], + [AS_HELP_STRING([--with-unixodbc-includes=], [directory path of unixODBC header installation])], + [ + UNIXODBC_CFLAGS_check="$withval/include/unixodbc $withval/unixodbc $withval" + UNIXODBC_config_check="skip" + ] + ) + + UNIXODBC_config="" + if test "x$UNIXODBC_config_check" != "xskip"; then + if test "x$UNIXODBC_config_check" = "x"; then + AC_PATH_PROG([UNIXODBC_config], [odbc_config]) + else + AC_MSG_CHECKING([for $UNIXODBC_config_check]) + if test -x $UNIXODBC_config_check; then + UNIXODBC_config="$UNIXODBC_config_check" + AC_MSG_RESULT([yes]) + else + UNIXODBC_config="" + AC_MSG_ERROR([not found]) + fi + fi + fi + + if test "x$UNIXODBC_config" != "x"; then + # use this to configure everything + UNIXODBC_LIBS=`$UNIXODBC_config --libs` + UNIXODBC_CFLAGS=-I`$UNIXODBC_config --include-prefix` + else + AC_MSG_CHECKING([for unixODBC library directory]) + UNIXODBC_libdir= + for m in $UNIXODBC_LIBS_check; do + if test -d "$m" && \ + (test -f "$m/libodbc.so" || test -f "$m/libodbc.a") + then + UNIXODBC_libdir=$m + break + fi + done + if test -z "$UNIXODBC_libdir"; then + AC_MSG_ERROR([Did not find the unixodbc library dir in '$UNIXODBC_LIBS_check']) + fi + case "$UNIXODBC_libdir" in + /*) UNIXODBC_LIBS="-L$UNIXODBC_libdir -lodbc" + ;; + *) AC_MSG_ERROR([The unixODBC library directory ($UNIXODBC_libdir) must be an absolute path.]) + ;; + esac + AC_MSG_RESULT([$UNIXODBC_libdir]) + + AC_MSG_CHECKING([for unixODBC include directory]) + UNIXODBC_CFLAGS= + for m in $UNIXODBC_CFLAGS_check; do + if test -d "$m" && test -f "$m/sql.h" + then + UNIXODBC_CFLAGS="$m" + break + fi + done + if test -z "$UNIXODBC_CFLAGS"; then + AC_MSG_ERROR([Did not find the unixodbc include dir in '$UNIXODBC_CFLAGS_check']) + fi + + case "$UNIXODBC_CFLAGS" in + /*) AC_MSG_RESULT($UNIXODBC_CFLAGS) + ;; + *) AC_MSG_ERROR([The unixODBC include directory ($UNIXODBC_CFLAGS) must be an absolute path.]) + ;; + esac + UNIXODBC_CFLAGS="-I$UNIXODBC_CFLAGS" + fi + + AC_SUBST(UNIXODBC_CFLAGS) + AC_SUBST(UNIXODBC_LIBS) +]) diff --git a/modules/Makefile.am b/modules/Makefile.am index 7dcb11543..fa231a03c 100644 --- a/modules/Makefile.am +++ b/modules/Makefile.am @@ -4,6 +4,7 @@ DIST_SUBDIRS = \ bindbackend \ geoipbackend \ gmysqlbackend \ + godbcbackend \ goraclebackend \ gpgsqlbackend \ gsqlite3backend \ diff --git a/modules/godbcbackend/Makefile.am b/modules/godbcbackend/Makefile.am new file mode 100644 index 000000000..199b84873 --- /dev/null +++ b/modules/godbcbackend/Makefile.am @@ -0,0 +1,12 @@ +AM_CPPFLAGS += $(UNIXODBC_CFLAGS) +pkglib_LTLIBRARIES = libgodbcbackend.la + +EXTRA_DIST=OBJECTFILES OBJECTLIBS + +dist_doc_DATA=schema.mssql.sql + +libgodbcbackend_la_SOURCES=godbcbackend.cc godbcbackend.hh \ + sodbc.hh sodbc.cc + +libgodbcbackend_la_LDFLAGS=-module -avoid-version +libgodbcbackend_la_LIBADD=$(UNIXODBC_LIBS) diff --git a/modules/godbcbackend/OBJECTFILES b/modules/godbcbackend/OBJECTFILES new file mode 100644 index 000000000..0f6af2a49 --- /dev/null +++ b/modules/godbcbackend/OBJECTFILES @@ -0,0 +1 @@ +godbcbackend.lo sodbc.lo \ No newline at end of file diff --git a/modules/godbcbackend/OBJECTLIBS b/modules/godbcbackend/OBJECTLIBS new file mode 100644 index 000000000..3660bcf3f --- /dev/null +++ b/modules/godbcbackend/OBJECTLIBS @@ -0,0 +1 @@ +-lodbc \ No newline at end of file diff --git a/modules/godbcbackend/godbcbackend.cc b/modules/godbcbackend/godbcbackend.cc new file mode 100644 index 000000000..c5d1811ad --- /dev/null +++ b/modules/godbcbackend/godbcbackend.cc @@ -0,0 +1,159 @@ +// The Generic ODBC Backend +// By Michel Stol + +#include "pdns/utility.hh" +#include +#include +#include + +#include "pdns/dns.hh" +#include "pdns/dnsbackend.hh" +#include "pdns/dnspacket.hh" +#include "pdns/ueberbackend.hh" +#include "pdns/pdnsexception.hh" +#include "pdns/logger.hh" +#include "pdns/arguments.hh" +#include "sodbc.hh" +#include "godbcbackend.hh" + + +// Connects to the database. +gODBCBackend::gODBCBackend (const std::string & mode, const std::string & suffix) : GSQLBackend( mode, suffix ) +{ + try + { + setDB( new SODBC( getArg( "datasource" ), getArg( "username" ), getArg( "password" ))); + } + catch( SSqlException & e ) + { + L< convert(varbinary(255),?) and domain_id=? and disabled=0 and ordername is not null"); + declare(suffix, "get-order-last-query", "DNSSEC Ordering Query, last", "select top 1 convert(varchar(255), ordername), name from records where ordername != convert(varbinary(255),'') and domain_id=? and disabled=0 and ordername is not null order by 1 desc"); + + declare(suffix, "update-ordername-and-auth-query", "DNSSEC update ordername and auth for a qname query", "update records set ordername=convert(varbinary(255),?),auth=? where domain_id=? and name=? and disabled=0"); + declare(suffix, "update-ordername-and-auth-type-query", "DNSSEC update ordername and auth for a rrset query", "update records set ordername=convert(varbinary(255),?),auth=? where domain_id=? and name=? and type=? and disabled=0"); + declare(suffix, "nullify-ordername-and-update-auth-query", "DNSSEC nullify ordername and update auth for a qname query", "update records set ordername=NULL,auth=? where domain_id=? and name=? and disabled=0"); + declare(suffix, "nullify-ordername-and-update-auth-type-query", "DNSSEC nullify ordername and update auth for a rrset query", "update records set ordername=NULL,auth=? where domain_id=? and name=? and type=? and disabled=0"); + + declare(suffix,"update-master-query","", "update domains set master=? where name=?"); + declare(suffix,"update-kind-query","", "update domains set type=? where name=?"); + declare(suffix,"update-account-query","", "update domains set account=? where name=?"); + declare(suffix,"update-serial-query","", "update domains set notified_serial=? where id=?"); + declare(suffix,"update-lastcheck-query","", "update domains set last_check=? where id=?"); + declare(suffix,"zone-lastchange-query", "", "select max(change_date) from records where domain_id=?"); + declare(suffix,"info-all-master-query","", "select id,name,master,last_check,notified_serial,type from domains where type='MASTER'"); + declare(suffix,"delete-domain-query","", "delete from domains where name=?"); + declare(suffix,"delete-zone-query","", "delete from records where domain_id=?"); + declare(suffix,"delete-rrset-query","","delete from records where domain_id=? and name=? and type=?"); + declare(suffix,"delete-names-query","","delete from records where domain_id=? and name=?"); + + declare(suffix,"add-domain-key-query","", "insert into cryptokeys (domain_id, flags, active, content) select id, ?, ?, ? from domains where name=?"); + declare(suffix,"list-domain-keys-query","", "select cryptokeys.id, flags, active, content from domains, cryptokeys where cryptokeys.domain_id=domains.id and name=?"); + declare(suffix,"get-all-domain-metadata-query","", "select kind,content from domains, domainmetadata where domainmetadata.domain_id=domains.id and name=?"); + declare(suffix,"get-domain-metadata-query","", "select content from domains, domainmetadata where domainmetadata.domain_id=domains.id and name=? and domainmetadata.kind=?"); + declare(suffix,"clear-domain-metadata-query","", "delete from domainmetadata where domain_id=(select id from domains where name=?) and domainmetadata.kind=?"); + declare(suffix,"clear-domain-all-metadata-query","", "delete from domainmetadata where domain_id=(select id from domains where name=?)"); + declare(suffix,"set-domain-metadata-query","", "insert into domainmetadata (domain_id, kind, content) select id, ?, ? from domains where name=?"); + declare(suffix,"activate-domain-key-query","", "update cryptokeys set active=1 where domain_id=(select id from domains where name=?) and cryptokeys.id=?"); + declare(suffix,"deactivate-domain-key-query","", "update cryptokeys set active=0 where domain_id=(select id from domains where name=?) and cryptokeys.id=?"); + declare(suffix,"remove-domain-key-query","", "delete from cryptokeys where domain_id=(select id from domains where name=?) and cryptokeys.id=?"); + declare(suffix,"clear-domain-all-keys-query","", "delete from cryptokeys where domain_id=(select id from domains where name=?)"); + declare(suffix,"get-tsig-key-query","", "select algorithm, secret from tsigkeys where name=?"); + /* FIXME: set-tsig-key-query only works on an empty database right now. For MySQL we use the "update into" statement.. + According to the internet, we need to construct a pretty hefty "merge" query: https://msdn.microsoft.com/en-us/library/bb510625.aspx + */ + declare(suffix,"set-tsig-key-query","", "insert into tsigkeys (name,algorithm,secret) values(?,?,?)"); + declare(suffix,"delete-tsig-key-query","", "delete from tsigkeys where name=?"); + declare(suffix,"get-tsig-keys-query","", "select name,algorithm, secret from tsigkeys"); + + declare(suffix, "get-all-domains-query", "Retrieve all domains", "select domains.id, domains.name, records.content, domains.type, domains.master, domains.notified_serial, domains.last_check, domains.account from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name WHERE records.disabled=0 OR records.disabled=?"); + + declare(suffix, "list-comments-query", "", "SELECT domain_id,name,type,modified_at,account,comment FROM comments WHERE domain_id=?"); + declare(suffix, "insert-comment-query", "", "INSERT INTO comments (domain_id, name, type, modified_at, account, comment) VALUES (?, ?, ?, ?, ?, ?)"); + declare(suffix, "delete-comment-rrset-query", "", "DELETE FROM comments WHERE domain_id=? AND name=? AND type=?"); + declare(suffix, "delete-comments-query", "", "DELETE FROM comments WHERE domain_id=?"); + declare(suffix, "search-records-query", "", record_query+" name LIKE ? OR content LIKE ? LIMIT ?"); + declare(suffix, "search-comments-query", "", "SELECT domain_id,name,type,modified_at,account,comment FROM comments WHERE name LIKE ? OR comment LIKE ? LIMIT ?"); + } + + //! Constructs a new gODBCBackend object. + DNSBackend *make(const string & suffix = "" ) + { + return new gODBCBackend( d_mode, suffix ); + } + +private: + const string d_mode; +}; + + +//! Magic class that is activated when the dynamic library is loaded +class gODBCLoader +{ +public: + //! This reports us to the main UeberBackend class + gODBCLoader() + { + BackendMakers().report( new gODBCFactory("godbc")); + L< + +#include +#include "pdns/backends/gsql/gsqlbackend.hh" + +class gODBCBackend : public GSQLBackend +{ +public: + //! Constructor that connects to the database, throws an exception if something went wrong. + gODBCBackend( const std::string & mode, const std::string & suffix ); + +}; + diff --git a/modules/godbcbackend/schema.mssql.sql b/modules/godbcbackend/schema.mssql.sql new file mode 100644 index 000000000..7a4c235da --- /dev/null +++ b/modules/godbcbackend/schema.mssql.sql @@ -0,0 +1,88 @@ +CREATE TABLE domains ( + id INT IDENTITY, + name VARCHAR(255) NOT NULL, + master VARCHAR(128) DEFAULT NULL, + last_check INT DEFAULT NULL, + type VARCHAR(6) NOT NULL, + notified_serial INT DEFAULT NULL, + account VARCHAR(40) DEFAULT NULL, + PRIMARY KEY (id) +); + +CREATE UNIQUE INDEX name_index ON domains(name); + +CREATE TABLE records ( + id INT IDENTITY, + domain_id INT DEFAULT NULL, + name VARCHAR(255) DEFAULT NULL, + type VARCHAR(10) DEFAULT NULL, + content VARCHAR(MAX) DEFAULT NULL, + ttl INT DEFAULT NULL, + prio INT DEFAULT NULL, + change_date INT DEFAULT NULL, + disabled BIT DEFAULT 0, + ordername VARBINARY(255) DEFAULT NULL, + auth BIT DEFAULT 1, + PRIMARY KEY (id) +); + +CREATE INDEX nametype_index ON records(name,type); +CREATE INDEX domain_id ON records(domain_id); +CREATE INDEX recordorder ON records (domain_id, ordername); + + +CREATE TABLE supermasters ( + ip VARCHAR(64) NOT NULL, + nameserver VARCHAR(255) NOT NULL, + account VARCHAR(40) NOT NULL, + PRIMARY KEY (ip, nameserver) +); + + +CREATE TABLE comments ( + id INT IDENTITY, + domain_id INT NOT NULL, + name VARCHAR(255) NOT NULL, + type VARCHAR(10) NOT NULL, + modified_at INT NOT NULL, + account VARCHAR(40) NOT NULL, + comment VARCHAR(MAX) NOT NULL, + PRIMARY KEY (id) +); + +CREATE INDEX comments_domain_id_idx ON comments (domain_id); +CREATE INDEX comments_name_type_idx ON comments (name, type); +CREATE INDEX comments_order_idx ON comments (domain_id, modified_at); + + +CREATE TABLE domainmetadata ( + id INT IDENTITY, + domain_id INT NOT NULL, + kind VARCHAR(32), + content VARCHAR(MAX), + PRIMARY KEY (id) +); + +CREATE INDEX domainmetadata_idx ON domainmetadata (domain_id, kind); + +CREATE TABLE cryptokeys ( + id INT IDENTITY, + domain_id INT NOT NULL, + flags INT NOT NULL, + active BIT, + content VARCHAR(MAX), + PRIMARY KEY(id) +); + +CREATE INDEX domainidindex ON cryptokeys(domain_id); + + +CREATE TABLE tsigkeys ( + id INT IDENTITY, + name VARCHAR(255), + algorithm VARCHAR(50), + secret VARCHAR(255), + PRIMARY KEY (id) +); + +CREATE UNIQUE INDEX namealgoindex ON tsigkeys(name, algorithm); diff --git a/modules/godbcbackend/sodbc.cc b/modules/godbcbackend/sodbc.cc new file mode 100644 index 000000000..fe032d269 --- /dev/null +++ b/modules/godbcbackend/sodbc.cc @@ -0,0 +1,440 @@ +#include "pdns/utility.hh" +#include +#include "sodbc.hh" +#include + +static void testResult( SQLRETURN result, SQLSMALLINT type, SQLHANDLE handle, const std::string & message ) +{ + // cerr<<"result = "< d_req_bind; + + SSqlStatement* bind(const string& name, bool value) { return bind(name, (long)value); } + SSqlStatement* bind(const string& name, long value) { + + // cerr<<"asked to bind long "< (d_parnum+1)) throw SSqlException("Trying to bind too many parameters."); + + ODBCParam p; + + p.ParameterValuePtr = (char*) new char[value.size()+1]; + value.copy((char*)p.ParameterValuePtr, value.size()); + ((char*)p.ParameterValuePtr)[value.size()]=0; + p.LenPtr=new SQLLEN; + *(p.LenPtr)=value.size(); + + d_req_bind.push_back(p); + + SQLRETURN result = SQLBindParameter( + d_statement, // StatementHandle, + d_paridx+1, // ParameterNumber, + SQL_PARAM_INPUT, // InputOutputType, + SQL_C_CHAR, // ValueType, + SQL_VARCHAR, // ParameterType, + value.size(), // ColumnSize, + 0, // DecimalDigits, + p.ParameterValuePtr, // ParameterValuePtr, + value.size()+1, // BufferLength, + p.LenPtr // StrLen_or_IndPtr + ); + testResult( result, SQL_HANDLE_STMT, d_statement, "Binding parameter."); + d_paridx++; + + return this; + } + + SSqlStatement* bindNull(const string& name) { + if(d_req_bind.size() > (d_parnum+1)) throw SSqlException("Trying to bind too many parameters."); + + ODBCParam p; + + p.ParameterValuePtr = NULL; + p.LenPtr=new SQLLEN; + *(p.LenPtr)=SQL_NULL_DATA; + + d_req_bind.push_back(p); + + SQLRETURN result = SQLBindParameter( + d_statement, // StatementHandle, + d_paridx+1, // ParameterNumber, + SQL_PARAM_INPUT, // InputOutputType, + SQL_C_CHAR, // ValueType, + SQL_VARCHAR, // ParameterType, + 0, // ColumnSize, + 0, // DecimalDigits, + p.ParameterValuePtr, // ParameterValuePtr, + 0, // BufferLength, + p.LenPtr // StrLen_or_IndPtr + ); + testResult( result, SQL_HANDLE_STMT, d_statement, "Binding parameter."); + d_paridx++; + + return this; + } + + SSqlStatement* execute() + { + SQLRETURN result; + // cerr<<"execute("<(coldata)); // FIXME: not NUL-safe, use len + } + } + + // Done! + d_residx++; + // cerr<<"SQLFetch"<( SQL_OV_ODBC3 ), 0 ); + testResult( result, SQL_HANDLE_ENV, m_environment, "Could not set the ODBC version." ); + + // Allocate connection handle. + result = SQLAllocHandle( SQL_HANDLE_DBC, m_environment, &m_connection ); + testResult( result, SQL_HANDLE_ENV, m_environment, "Could not allocate a connection handle." ); + + // Connect to the database. + char *l_dsn = strdup( dsn.c_str()); + char *l_username = strdup( username.c_str()); + char *l_password = strdup( password.c_str()); + + result = SQLConnect( m_connection, + reinterpret_cast< SQLTCHAR * >( l_dsn ), dsn.length(), + reinterpret_cast< SQLTCHAR * >( l_username ), username.length(), + reinterpret_cast< SQLTCHAR * >( l_password ), password.length()); + + free( l_dsn ); + free( l_username ); + free( l_password ); + + testResult( result, SQL_HANDLE_DBC, m_connection, "Could not connect to ODBC datasource." ); + + + m_busy = false; + m_log = false; +} + + +// Destructor. +SODBC::~SODBC( void ) +{ + // Disconnect from database and free all used resources. + // SQLFreeHandle( SQL_HANDLE_STMT, m_statement ); + + SQLDisconnect( m_connection ); + + SQLFreeHandle( SQL_HANDLE_DBC, m_connection ); + SQLFreeHandle( SQL_HANDLE_ENV, m_environment ); + + // Free all allocated column memory. + // for ( int i = 0; i < m_columnInfo.size(); i++ ) + // { + // if ( m_columnInfo[ i ].m_pData ) + // delete m_columnInfo[ i ].m_pData; + // } +} + +// Executes a command. +void SODBC::execute( const std::string & command ) +{ + SQLRETURN result; + SODBCStatement stmt(command, false, 0, m_connection); + + stmt.execute()->reset(); +} + +// Sets the log state. +void SODBC::setLog( bool state ) +{ + m_log = state; +} + +// Returns an exception. +SSqlException SODBC::sPerrorException( const std::string & reason ) +{ + return SSqlException( reason ); +} + +SSqlStatement* SODBC::prepare(const string& query, int nparams) +{ + return new SODBCStatement(query, true, nparams, m_connection); +} + + +void SODBC::startTransaction() { + // cerr<<"starting transaction"< + +#ifndef SODBC_HH +#define SODBC_HH + +#include +#include + +#include +#include + +#include "pdns/backends/gsql/ssql.hh" + +//! ODBC SSql implementation for use with the Generic ODBC Backend. +class SODBC : public SSql +{ +private: + + + bool m_log; //!< Should we log? + bool m_busy; //!< Are we busy executing a query? + + SQLHDBC m_connection; //!< Database connection handle. + SQLHENV m_environment; //!< Database environment handle + + + +public: + //! Default constructor. + /*! + This constructor connects to an ODBC datasource and makes sure it's ready to use. + + \param dsn The ODBC DSN to use. + \param username Username to use. + \param password Password to use. + */ + SODBC( + const std::string & dsn, + const std::string & username, + const std::string & password + ); + + //! Destructor. + virtual ~SODBC( void ); + + //! Sets the logging state. + void setLog( bool state ); + + SSqlStatement* prepare(const string& query, int nparams); + void execute(const string& query); + void startTransaction(); + void rollback(); + void commit(); + + //! Returns an exception. + SSqlException sPerrorException( const std::string & reason ); + +}; + + +#endif // SODBC_HH diff --git a/regression-tests/backends/common b/regression-tests/backends/common index 7000298a4..be3b784c9 100644 --- a/regression-tests/backends/common +++ b/regression-tests/backends/common @@ -11,6 +11,18 @@ start_master () source ./backends/gmysql-master ;; + godbc_mssql*) + [ -z $GODBC_MSSQL_DSN ] && echo '$GODBC_MSSQL_DSN must be set' >&2 && exit 1 + [ -z $GODBC_MSSQL_USERNAME ] && echo '$GODBC_MSSQL_USERNAME must be set' >&2 && exit 1 + [ -z $GODBC_MSSQL_PASSWORD ] && echo '$GODBC_MSSQL_PASSWORD must be set' >&2 && exit 1 + source ./backends/godbc_mssql-master + ;; + + godbc_sqlite3*) + [ -z $GODBC_SQLITE3_DSN ] && echo '$GODBC_SQLITE3_DSN must be set' >&2 && exit 1 + source ./backends/godbc_sqlite3-master + ;; + goracle*) source ./backends/goracle-master ;; @@ -78,6 +90,13 @@ start_slave () source ./backends/gmysql-slave ;; + godbc_mssql*) + [ -z $GODBC_MSSQL2_DSN ] && echo '$GODBC_MSSQL2_DSN must be set' >&2 && exit 1 + [ -z $GODBC_MSSQL2_USERNAME ] && echo '$GODBC_MSSQL2_USERNAME must be set' >&2 && exit 1 + [ -z $GODBC_MSSQL2_PASSWORD ] && echo '$GODBC_MSSQL2_PASSWORD must be set' >&2 && exit 1 + source ./backends/godbc_mssql-slave + ;; + goracle*) source ./backends/goracle-slave ;; diff --git a/regression-tests/backends/godbc_mssql-master b/regression-tests/backends/godbc_mssql-master new file mode 100644 index 000000000..6ece162d9 --- /dev/null +++ b/regression-tests/backends/godbc_mssql-master @@ -0,0 +1,33 @@ +source ./backends/gsql-common +ISQL="isql $GODBC_MSSQL_DSN $GODBC_MSSQL_USERNAME $GODBC_MSSQL_PASSWORD" +BSQLODBC="bsqlodbc -S $GODBC_MSSQL_DSN -U $GODBC_MSSQL_USERNAME -P $GODBC_MSSQL_PASSWORD" +case $context in + godbc_mssql-nodnssec | godbc_mssql | godbc_mssql-nsec3 | godbc_mssql-nsec3-optout | godbc_mssql-nsec3-narrow) + # Drop _ALL_ the tables! + for table in `echo "SELECT name FROM sysobjects WHERE OBJECTPROPERTY(id, N'IsUserTable') = 1" | $ISQL -b -d.`; do + echo "drop table $table" | $ISQL -b + done + cat ../modules/godbcbackend/schema.mssql.sql | tr '\n' ' ' | $ISQL -b + + # Some context, the inserts are extremely slow for some reason + # This code removes the transactions from the sql because example.com is + # too large (and hangs/crashes). + # The 'sed' then adds 'go' after every 1000th line to batch-insert + # Then we send that to bsqlodbc with a final 'go' and an EOF so bdsqlodbc + # actually terminates + tosql gsqlite | grep -v -E '(COMMIT|TRANSACTION)' | awk '1;!(NR%98){print "go"}' | cat - <(echo go) /dev/null | $BSQLODBC + cat > pdns-godbc_mssql.conf << __EOF__ +module-dir=./modules +launch=godbc +godbc-datasource=$GODBC_MSSQL_DSN +godbc-username=$GODBC_MSSQL_USERNAME +godbc-password=$GODBC_MSSQL_PASSWORD +godbc-dnssec=yes +__EOF__ + + gsql_master godbc_mssql nodyndns + ;; + + *) + nocontext=yes +esac diff --git a/regression-tests/backends/godbc_mssql-slave b/regression-tests/backends/godbc_mssql-slave new file mode 100644 index 000000000..d32d0f581 --- /dev/null +++ b/regression-tests/backends/godbc_mssql-slave @@ -0,0 +1,56 @@ + context=${context}-presigned-godbc + ISQL="isql $GODBC_MSSQL2_DSN $GODBC_MSSQL2_USERNAME $GODBC_MSSQL2_PASSWORD" + # Drop _ALL_ the tables! + for table in `echo "SELECT name FROM sysobjects WHERE OBJECTPROPERTY(id, N'IsUserTable') = 1" | $ISQL -b -d.`; do + echo "drop table $table" | $ISQL -b + done + $ISQL < ../modules/godbcbackend/schema.mssql.sql + cat > pdns-godbc2.conf << __EOF__ +module-dir=./modules +launch=godbc +godbc-datasource=$GODBC_MSSQL2_DSN +godbc-username=$GODBC_MSSQL2_USERNAME +godbc-password=$GODBC_MSSQL2_PASSWORD +__EOF__ + + for zone in $(grep 'zone ' named.conf | cut -f2 -d\" | tac) + do + echo "INSERT INTO domains (name, type, master) VALUES('$zone','SLAVE','127.0.0.1:$port');" | $ISQL -b + done + + ../pdns/pdnssec --config-dir=. --config-name=godbc2 import-tsig-key test $ALGORITHM $KEY + ../pdns/pdnssec --config-dir=. --config-name=godbc2 activate-tsig-key tsig.com test slave + if [[ $skipreasons != *nolua* ]] + then + ../pdns/pdnssec --config-dir=. --config-name=godbc2 set-meta stest.com AXFR-SOURCE 127.0.0.2 + fi + + port=$((port+100)) + + $RUNWRAPPER $PDNS2 --daemon=no --local-port=$port --config-dir=. \ + --config-name=godbc2 --socket-dir=./ --no-shuffle \ + --send-root-referral --slave --retrieval-threads=4 \ + --slave-cycle-interval=300 --experimental-dname-processing & + + echo 'waiting for zones to be slaved' + set +e + loopcount=0 + while [ $loopcount -lt 30 ] + do + sleep 5 + todo=$(echo "SELECT COUNT(id) FROM domains WHERE last_check IS NULL" | $ISQL) + if [ $? -eq 0 ] + then + if [ $todo = 0 ] + then + break + fi + fi + let loopcount=loopcount+1 + done + if [ $todo -ne 0 ] + then + echo "AXFR FAILED" >> failed_tests + exit + fi + set -e diff --git a/regression-tests/backends/godbc_sqlite3-master b/regression-tests/backends/godbc_sqlite3-master new file mode 100644 index 000000000..d977e4d74 --- /dev/null +++ b/regression-tests/backends/godbc_sqlite3-master @@ -0,0 +1,79 @@ +source ./backends/gsql-common +case $context in + godbc_sqlite3-nodnssec | godbc_sqlite3 | godbc_sqlite3-nsec3 | godbc_sqlite3-nsec3-optout | godbc_sqlite3-nsec3-narrow) + rm -f pdns.sqlite3 + sqlite3 pdns.sqlite3 < ../modules/gsqlite3backend/schema.sqlite3.sql + tosql gsqlite | sqlite3 pdns.sqlite3 + echo 'ANALYZE; PRAGMA journal_mode=WAL;' | sqlite3 pdns.sqlite3 + + cat > pdns-godbc_sqlite3.conf << __EOF__ +module-dir=./modules +launch=godbc +godbc-datasource=$GODBC_SQLITE3_DSN + +godbc-activate-domain-key-query=update cryptokeys set active=1 where domain_id=(select id from domains where name=?) and cryptokeys.id=? +godbc-add-domain-key-query=insert into cryptokeys (domain_id, flags, active, content) select id, ?,?, ? from domains where name=? +godbc-any-id-query=SELECT content,ttl,prio,type,domain_id,disabled,name,auth FROM records WHERE disabled=0 and name=? and domain_id=? +godbc-any-query=SELECT content,ttl,prio,type,domain_id,disabled,name,auth FROM records WHERE disabled=0 and name=? +godbc-basic-query=SELECT content,ttl,prio,type,domain_id,disabled,name,auth FROM records WHERE disabled=0 and type=? and name=? +godbc-clear-domain-all-keys-query=delete from cryptokeys where domain_id=(select id from domains where name=?) +godbc-clear-domain-all-metadata-query=delete from domainmetadata where domain_id=(select id from domains where name=?) +godbc-clear-domain-metadata-query=delete from domainmetadata where domain_id=(select id from domains where name=?) and domainmetadata.kind=? +godbc-deactivate-domain-key-query=update cryptokeys set active=0 where domain_id=(select id from domains where name=?) and cryptokeys.id=? +godbc-delete-comment-rrset-query=DELETE FROM comments WHERE domain_id=? AND name=? AND type=? +godbc-delete-comments-query=DELETE FROM comments WHERE domain_id=? +godbc-delete-domain-query=delete from domains where name=? +godbc-delete-empty-non-terminal-query=delete from records where domain_id=? and name=? and type is null +godbc-delete-names-query=delete from records where domain_id=? and name=? +godbc-delete-rrset-query=delete from records where domain_id=? and name=? and type=? +godbc-delete-tsig-key-query=delete from tsigkeys where name=? +godbc-delete-zone-query=delete from records where domain_id=? +godbc-get-all-domain-metadata-query=select kind,content from domains, domainmetadata where domainmetadata.domain_id=domains.id and name=? +godbc-get-all-domains-query=select domains.id, domains.name, records.content, domains.type, domains.master, domains.notified_serial, domains.last_check, domains.account from domains LEFT JOIN records ON records.domain_id=domains.id AND records.type='SOA' AND records.name=domains.name WHERE records.disabled=0 OR ? +godbc-get-domain-metadata-query=select content from domains, domainmetadata where domainmetadata.domain_id=domains.id and name=? and domainmetadata.kind=? +godbc-get-order-after-query=select min(ordername) from records where disabled=0 and ordername > ? and domain_id=? and ordername is not null +godbc-get-order-before-query=select ordername, name from records where disabled=0 and ordername <= ? and domain_id=? and ordername is not null order by 1 desc limit 1 +godbc-get-order-first-query=select ordername from records where disabled=0 and domain_id=? and ordername is not null order by 1 asc limit 1 +godbc-get-order-last-query=select ordername from records where disabled=0 and ordername != '' and domain_id=? and ordername is not null order by 1 desc limit 1 +godbc-get-tsig-key-query=select algorithm, secret from tsigkeys where name=? +godbc-get-tsig-keys-query=select name,algorithm, secret from tsigkeys +godbc-id-query=SELECT content,ttl,prio,type,domain_id,disabled,name,auth FROM records WHERE disabled=0 and type=? and name=? and domain_id=? +godbc-info-all-master-query=select id,name,master,last_check,notified_serial,type from domains where type='MASTER' +godbc-info-all-slaves-query=select id,name,master,last_check,type from domains where type='SLAVE' +godbc-info-zone-query=select id,name,master,last_check,notified_serial,type,account from domains where name=? +godbc-insert-comment-query=INSERT INTO comments (domain_id, name, type, modified_at, account, comment) VALUES (?, ?, ?, ?, ?, ?) +godbc-insert-empty-non-terminal-query=insert into records (domain_id,name,type,disabled,auth) values (?,?,null,0,'1') +godbc-insert-ent-order-query=insert into records (type,domain_id,disabled,name,ordername,auth) values (null,?,0,?,?,?) +godbc-insert-ent-query=insert into records (type,domain_id,disabled,name,auth) values (null,?,0,?,?) +godbc-insert-record-order-query=insert into records (content,ttl,prio,type,domain_id,disabled,name,ordername,auth) values (?,?,?,?,?,?,?,?,?) +godbc-insert-record-query=insert into records (content,ttl,prio,type,domain_id,disabled,name,auth) values (?,?,?,?,?,?,?,?) +godbc-insert-slave-query=insert into domains (type,name,master,account) values('SLAVE',?,?,?) +godbc-insert-zone-query=insert into domains (type,name) values('NATIVE',?) +godbc-list-comments-query=SELECT domain_id,name,type,modified_at,account,comment FROM comments WHERE domain_id=? +godbc-list-domain-keys-query=select cryptokeys.id, flags, active, content from domains, cryptokeys where cryptokeys.domain_id=domains.id and name=? +godbc-list-query=SELECT content,ttl,prio,type,domain_id,disabled,name,auth FROM records WHERE (disabled=0 OR ?) and domain_id=? order by name, type +godbc-list-subzone-query=SELECT content,ttl,prio,type,domain_id,disabled,name,auth FROM records WHERE disabled=0 and (name=? OR name like ?) and domain_id=? +godbc-master-zone-query=select master from domains where name=? and type='SLAVE' +godbc-nullify-ordername-and-update-auth-query=update records set ordername=NULL,auth=? where domain_id=? and name=? and disabled=0 +godbc-remove-domain-key-query=delete from cryptokeys where domain_id=(select id from domains where name=?) and cryptokeys.id=? +godbc-remove-empty-non-terminals-from-zone-query=delete from records where domain_id=? and type is null +godbc-set-domain-metadata-query=insert into domainmetadata (domain_id, kind, content) select id, ?, ? from domains where name=? +godbc-set-tsig-key-query=replace into tsigkeys (name,algorithm,secret) values(?,?,?) +godbc-supermaster-name-to-ips=select ip,account from supermasters where nameserver=? and account=? +godbc-supermaster-query=select account from supermasters where ip=? and nameserver=? +godbc-update-account-query=update domains set account=? where name=? +godbc-update-kind-query=update domains set type=? where name=? +godbc-update-lastcheck-query=update domains set last_check=? where id=? +godbc-update-master-query=update domains set master=? where name=? +godbc-update-ordername-and-auth-query=update records set ordername=?,auth=? where domain_id=? and name=? and disabled=0 +godbc-update-ordername-and-auth-type-query=update records set ordername=?,auth=? where domain_id=? and name=? and type=? and disabled=0 +godbc-update-serial-query=update domains set notified_serial=? where id=? +godbc-zone-lastchange-query=select max(change_date) from records where domain_id=? +__EOF__ + + gsql_master godbc_sqlite3 nodyndns + ;; + + *) + nocontext=yes +esac diff --git a/regression-tests/backends/gsql-common b/regression-tests/backends/gsql-common index 3e27d8d69..9dc9cea01 100644 --- a/regression-tests/backends/gsql-common +++ b/regression-tests/backends/gsql-common @@ -3,9 +3,14 @@ gsql_master() backend=$1 skipreasons=$2 + real_backend=$backend + if `echo $backend | grep -q '_'`; then + real_backend=$(echo $backend | awk -F '_' '{print $1}') + fi + if [ $context != ${backend}-nodnssec ] then - echo "${backend}-dnssec" >> pdns-${backend}.conf + echo "${real_backend}-dnssec" >> pdns-${backend}.conf fi for zone in $(grep 'zone ' named.conf | cut -f2 -d\") diff --git a/regression-tests/modules/libgodbcbackend.so b/regression-tests/modules/libgodbcbackend.so new file mode 120000 index 000000000..f8fa0ab86 --- /dev/null +++ b/regression-tests/modules/libgodbcbackend.so @@ -0,0 +1 @@ +../../modules/godbcbackend/.libs/libgodbcbackend.so \ No newline at end of file diff --git a/regression-tests/start-test-stop b/regression-tests/start-test-stop index 3a6c0913c..86f59fda1 100755 --- a/regression-tests/start-test-stop +++ b/regression-tests/start-test-stop @@ -201,6 +201,7 @@ context is one of: bind bind-dnssec bind-dnssec-nsec3 bind-dnssec-nsec3-optout bind-dnssec-nsec3-narrow geoipbackend geoipbackend-nsec3-narrow gmysql-nodnssec gmysql gmysql-nsec3 gmysql-nsec3-optout gmysql-nsec3-narrow +godbc_mssql-nodnssec godbc_mssql godbc_mssql-nsec3 godbc_mssql-nsec3-optout godbc_mssql-nsec3-narrow goracle-nodnssec goracle goracle-nsec3 goracle-nsec3-optout goracle-nsec3-narrow gpgsql-nodnssec gpgsql gpgsql-nsec3 gpgsql-nsec3-optout gpgsql-nsec3-narrow gsqlite3-nodnssec gsqlite3 gsqlite3-nsec3 gsqlite3-nsec3-optout gsqlite3-nsec3-narrow