]> granicus.if.org Git - pdns/commitdiff
implement gODBC backend; loosely based on old godbc code by Michel Stol
authorPeter van Dijk <peter.van.dijk@netherlabs.nl>
Mon, 29 Jun 2015 14:34:09 +0000 (16:34 +0200)
committerPeter van Dijk <peter.van.dijk@powerdns.com>
Mon, 23 Nov 2015 14:25:02 +0000 (15:25 +0100)
32 files changed:
.travis.yml
build-scripts/build-auth-rpm
build-scripts/debian-authoritative/config/pdns.local.godbc.conf [new file with mode: 0644]
build-scripts/debian-authoritative/control
build-scripts/debian-authoritative/pdns-backend-odbc.docs [new file with mode: 0644]
build-scripts/debian-authoritative/pdns-backend-odbc.install [new file with mode: 0644]
build-scripts/debian-authoritative/pdns-backend-odbc.postinst [new file with mode: 0644]
build-scripts/debian-authoritative/pdns-backend-odbc.postrm [new file with mode: 0644]
build-scripts/debian-authoritative/pdns-backend-odbc.prerm [new file with mode: 0644]
build-scripts/debian-authoritative/rules
build-scripts/test-auth
configure.ac
docs/markdown/authoritative/backend-godbc.md [new file with mode: 0644]
docs/markdown/authoritative/index.md
docs/mkdocs.yml
m4/pdns_with_unixodbc.m4 [new file with mode: 0644]
modules/Makefile.am
modules/godbcbackend/Makefile.am [new file with mode: 0644]
modules/godbcbackend/OBJECTFILES [new file with mode: 0644]
modules/godbcbackend/OBJECTLIBS [new file with mode: 0644]
modules/godbcbackend/godbcbackend.cc [new file with mode: 0644]
modules/godbcbackend/godbcbackend.hh [new file with mode: 0644]
modules/godbcbackend/schema.mssql.sql [new file with mode: 0644]
modules/godbcbackend/sodbc.cc [new file with mode: 0644]
modules/godbcbackend/sodbc.hh [new file with mode: 0644]
regression-tests/backends/common
regression-tests/backends/godbc_mssql-master [new file with mode: 0644]
regression-tests/backends/godbc_mssql-slave [new file with mode: 0644]
regression-tests/backends/godbc_sqlite3-master [new file with mode: 0644]
regression-tests/backends/gsql-common
regression-tests/modules/libgodbcbackend.so [new symlink]
regression-tests/start-test-stop

index 15a6156b0c5c2965b569676088d89dc5ddb80788..0f5e5060554a0441acb3d3284c6d51d8f928bc7c 100644 (file)
@@ -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
index fb79abb7b753094bdd8bc7deee8a944e539213a9..80cfcd877e9830e3f37d331a62a3af735334b479 100755 (executable)
@@ -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 (file)
index 0000000..a80b71b
--- /dev/null
@@ -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=
index 6af8203df7d9400fa5bf4065912724af51cde399..25880ff112a43b3ff7305317031a54ab72dea253 100644 (file)
@@ -4,7 +4,7 @@ Priority: extra
 Standards-Version: 3.9.6
 Maintainer: PowerDNS Autobuilder <powerdns.support@powerdns.com>
 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 (file)
index 0000000..76cc1e0
--- /dev/null
@@ -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 (file)
index 0000000..8a5062d
--- /dev/null
@@ -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 (file)
index 0000000..ace1f63
--- /dev/null
@@ -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 (file)
index 0000000..d3e4a15
--- /dev/null
@@ -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 (file)
index 0000000..d02c6bd
--- /dev/null
@@ -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
+
index 6080d3ad904c8d582cb6533bdab8b69146417fa7..94b8f9c1431a55c954ae9913b6efbd0ca8198144 100755 (executable)
@@ -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)
 
index 56a61ef3352a1c36800c4962f4d8929c2cb5aebd..8c1f0e82663a517f3cf5dfc66871e9b2bcf6885f 100755 (executable)
@@ -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
index 06e9c8dd1fa5b6fc06bea527ebf48cc6956b3547..8634b8a413f949f587d61a04a5de22f4a6a3699d 100644 (file)
@@ -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 (file)
index 0000000..79a47fd
--- /dev/null
@@ -0,0 +1,120 @@
+# Generic ODBC Backend
+|&nbsp;|&nbsp;|
+|:--|:--|
+|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.
index c788c1d5c3050d5c4e2ea37ad8c1d8843cfe75d7..f9a306b3ec6017d6cbd313183107a423f1ed8308 100644 (file)
@@ -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` |
index 2475ef93a635e0ed7651017ee545361d578db142..d1f12800a299173654a895850145fa2eb209605d 100644 (file)
@@ -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 (file)
index 0000000..95c26c9
--- /dev/null
@@ -0,0 +1,106 @@
+AC_DEFUN([PDNS_WITH_UNIXODBC],[
+  AC_ARG_WITH([unixodbc],
+    [AS_HELP_STRING([--with-unixodbc=<path>], [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=<path>], [file path to odbc_config])],
+    [UNIXODBC_config_check=$withval]
+  )
+
+  AC_ARG_WITH([unixodbc-lib],
+    [AS_HELP_STRING([--with-unixodbc-lib=<path>], [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=<path>], [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)
+])
index 7dcb115434cb06e312197b93cfd9f9a651cfb343..fa231a03cafe8d0b35174918c6a2a0c23dcfc4bf 100644 (file)
@@ -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 (file)
index 0000000..199b848
--- /dev/null
@@ -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 (file)
index 0000000..0f6af2a
--- /dev/null
@@ -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 (file)
index 0000000..3660bcf
--- /dev/null
@@ -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 (file)
index 0000000..c5d1811
--- /dev/null
@@ -0,0 +1,159 @@
+// The Generic ODBC Backend
+// By Michel Stol <michel@powerdns.com>
+
+#include "pdns/utility.hh"
+#include <map>
+#include <sstream>
+#include <string>
+
+#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<<Logger::Error<< mode << " Connection failed: " << e.txtReason() << std::endl;
+    throw PDNSException( "Unable to launch " + mode + " connection: " + e.txtReason());
+  }
+
+  L << Logger::Warning << mode << " Connection successful" << std::endl;
+}
+
+
+//! Constructs a gODBCBackend
+class gODBCFactory : public BackendFactory
+{
+public:
+  //! Constructor.
+  gODBCFactory( const std::string & mode ) : BackendFactory( mode ), d_mode( mode )
+  {
+  }
+
+  //! Declares all needed arguments.
+  void declareArguments( const std::string & suffix = "" )
+  {
+    declare( suffix, "datasource", "Datasource (DSN) to use","PowerDNS");
+    declare( suffix, "username", "User to connect as","powerdns");
+    declare( suffix, "password", "Password to connect with","");
+    declare(suffix,"dnssec","Enable DNSSEC processing","no");
+
+    string record_query = "SELECT content,ttl,prio,type,domain_id,disabled,name,auth FROM records WHERE";
+
+    declare(suffix, "basic-query", "Basic query", record_query+" disabled=0 and type=? and name=?");
+    declare(suffix, "id-query", "Basic with ID query", record_query+" disabled=0 and type=? and name=? and domain_id=?");
+    declare(suffix, "any-query", "Any query", record_query+" disabled=0 and name=?");
+    declare(suffix, "any-id-query", "Any with ID query", record_query+" disabled=0 and name=? and domain_id=?");
+
+    declare(suffix, "list-query", "AXFR query", record_query+" (disabled=0 OR disabled=?) and domain_id=? order by name, type");
+    declare(suffix, "list-subzone-query", "Subzone listing", record_query+" disabled=0 and (name=? OR name like ?) and domain_id=?");
+
+    declare(suffix, "remove-empty-non-terminals-from-zone-query", "remove all empty non-terminals from zone", "delete from records where domain_id=? and type is null");
+    declare(suffix, "insert-empty-non-terminal-query", "insert empty non-terminal in zone", "insert into records (domain_id,name,type,disabled,auth) values (?,?,null,0,1)");
+    declare(suffix, "delete-empty-non-terminal-query", "delete empty non-terminal from zone", "delete from records where domain_id=? and name=? and type is null");
+
+    declare(suffix,"master-zone-query","Data", "select master from domains where name=? and type='SLAVE'");
+
+    declare(suffix,"info-zone-query","","select id,name,master,last_check,notified_serial,type,account from domains where name=?");
+
+    declare(suffix,"info-all-slaves-query","","select id,name,master,last_check from domains where type='SLAVE'");
+    declare(suffix,"supermaster-query","", "select account from supermasters where ip=? and nameserver=?");
+    declare(suffix,"supermaster-name-to-ips", "", "select ip,account from supermasters where nameserver=? and account=?");
+
+    declare(suffix,"insert-zone-query","", "insert into domains (type,name) values('NATIVE',?)");
+    declare(suffix,"insert-slave-query","", "insert into domains (type,name,master,account) values('SLAVE',?,?,?)");
+
+    declare(suffix, "insert-record-query", "", "insert into records (content,ttl,prio,type,domain_id,disabled,name,auth) values (?,?,?,?,?,?,?,?)");
+    declare(suffix, "insert-record-order-query", "", "insert into records (content,ttl,prio,type,domain_id,disabled,name,ordername,auth) values (?,?,?,?,?,?,?,convert(varbinary(255),?),?)");
+    declare(suffix, "insert-ent-query", "insert empty non-terminal in zone", "insert into records (type,domain_id,disabled,name,auth) values (null,?,0,?,?)");
+    declare(suffix, "insert-ent-order-query", "insert empty non-terminal in zone", "insert into records (type,domain_id,disabled,name,ordername,auth) values (null,?,0,?,convert(varbinary(255),?),?)");
+
+    declare(suffix, "get-order-first-query", "DNSSEC Ordering Query, first", "select top 1 convert(varchar(255), ordername) from records where domain_id=? and disabled=0 and ordername is not null order by 1 asc");
+    declare(suffix, "get-order-before-query", "DNSSEC Ordering Query, before", "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, "get-order-after-query", "DNSSEC Ordering Query, after", "select convert(varchar(255), min(ordername)) from records where ordername > 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<<Logger::Warning << "This is module godbcbackend reporting" << std::endl;
+  }
+};
+
+//! Reports the backendloader to the UeberBackend.
+static gODBCLoader godbcloader;
diff --git a/modules/godbcbackend/godbcbackend.hh b/modules/godbcbackend/godbcbackend.hh
new file mode 100644 (file)
index 0000000..f90bb94
--- /dev/null
@@ -0,0 +1,14 @@
+// The Generic ODBC Backend
+// By Michel Stol <michel@powerdns.com>
+
+#include <string>
+#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 (file)
index 0000000..7a4c235
--- /dev/null
@@ -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 (file)
index 0000000..fe032d2
--- /dev/null
@@ -0,0 +1,440 @@
+#include "pdns/utility.hh"
+#include <sstream>
+#include "sodbc.hh"
+#include <string.h>
+
+static void testResult( SQLRETURN result, SQLSMALLINT type, SQLHANDLE handle, const std::string & message )
+{
+  // cerr<<"result = "<<result<<endl;
+  if ( result == SQL_SUCCESS) // FIXME: testing only? || result == SQL_SUCCESS_WITH_INFO )
+    return;
+
+  ostringstream errmsg;
+
+  errmsg << message << ": ";
+
+  if ( result != SQL_ERROR && result != SQL_SUCCESS_WITH_INFO ) {
+    cerr<<"handle "<<handle<<" got result "<<result<<endl;
+    errmsg << "SQL function returned "<<result<<", no additional information available"<<endl;
+    throw SSqlException( errmsg.str() );
+  }
+
+  SQLINTEGER i = 0;
+  SQLINTEGER native;
+  SQLCHAR state[ 7 ];
+  SQLCHAR text[256];
+  SQLSMALLINT len;
+  SQLRETURN ret;
+
+  do
+  {
+    // cerr<<"getting sql diag record "<<i<<endl;
+    ret = SQLGetDiagRec(type, handle, ++i, state, &native, text,
+    sizeof(text), &len );
+    // cerr<<"getdiagrec said "<<ret<<endl;
+    if (SQL_SUCCEEDED(ret)) { // cerr<<"got it"<<endl;
+      errmsg<<state<<i<<native<<text<<"/";
+    }
+  }
+  while( ret == SQL_SUCCESS );
+  throw SSqlException( errmsg.str() );
+}
+
+class SODBCStatement: public SSqlStatement
+{
+public:
+  SODBCStatement(const string& query, bool dolog, int nparams, SQLHDBC connection)
+  {
+    SQLRETURN result;
+
+    d_query = query;
+    // d_nparams = nparams;
+    d_conn = connection;
+    d_dolog = dolog;
+    d_residx = 0;
+    d_paridx = 0;
+
+    // Allocate statement handle.
+    result = SQLAllocHandle( SQL_HANDLE_STMT, d_conn, &d_statement );
+    testResult( result, SQL_HANDLE_DBC, d_conn, "Could not allocate a statement handle." );
+
+    result = SQLPrepare(d_statement, (SQLCHAR *) query.c_str(), SQL_NTS);
+    testResult( result, SQL_HANDLE_STMT, d_statement, "Could not prepare query." );
+
+    SQLSMALLINT paramcount;
+    result = SQLNumParams(d_statement, &paramcount);
+    testResult( result, SQL_HANDLE_STMT, d_statement, "Could not get parameter count." );
+
+    if (paramcount != nparams)
+      throw SSqlException("Provided parameter count does not match statement: " + d_query);
+
+    d_parnum = nparams;
+    // cerr<<"prepared ("<<query<<")"<<endl;
+  }
+
+  typedef struct {
+    SQLPOINTER      ParameterValuePtr;
+    SQLLEN*         LenPtr;
+  } ODBCParam;
+
+  vector<ODBCParam> 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 "<<value<<endl;
+    // cerr<<"d_req_bind.size()="<<d_req_bind.size()<<endl;
+    // cerr<<"d_parnum="<<d_parnum<<endl;
+
+    if(d_req_bind.size() > (d_parnum+1)) throw SSqlException("Trying to bind too many parameters.");
+
+    ODBCParam p;
+
+    p.ParameterValuePtr = new long[1];
+    *((long*)p.ParameterValuePtr) = value;
+    p.LenPtr = new SQLLEN;
+    *(p.LenPtr) = sizeof(long);
+
+    d_req_bind.push_back(p);
+
+    SQLRETURN result = SQLBindParameter(
+      d_statement,           // StatementHandle,
+      d_paridx+1,            // ParameterNumber,
+      SQL_PARAM_INPUT,       // InputOutputType,
+      SQL_C_SLONG,           // ValueType,
+      SQL_BIGINT,            // ParameterType,
+      0,                     // ColumnSize,
+      0,                     // DecimalDigits,
+      p.ParameterValuePtr,   // ParameterValuePtr,
+      0,                     // BufferLength,
+      p.LenPtr               // StrLen_or_IndPtr
+    );
+    testResult( result, SQL_HANDLE_STMT, d_statement, "Could not bind parameter.");
+    d_paridx++;
+
+    return this;
+  }
+  SSqlStatement* bind(const string& name, uint32_t value) { return bind(name, (long)value); }
+  SSqlStatement* bind(const string& name, int value) { return bind(name, (long)value); }
+  SSqlStatement* bind(const string& name, unsigned long value) { return bind(name, (long)value);}
+  SSqlStatement* bind(const string& name, long long value) { return bind(name, (long)value); };
+  SSqlStatement* bind(const string& name, unsigned long long value) { return bind(name, (long)value); }
+  SSqlStatement* bind(const string& name, const std::string& value) {
+
+    // cerr<<"asked to bind string "<<value<<endl;
+
+    if(d_req_bind.size() > (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("<<d_query<<")"<<endl;
+    if (d_dolog) {
+      // L<<Logger::Warning<<"Query: "<<d_query<<endl;
+    }
+
+    result = SQLExecute(d_statement);
+    if(result != SQL_NO_DATA)  // odbc+sqlite returns this on 'no rows updated'
+        testResult( result, SQL_HANDLE_STMT, d_statement, "Could not execute query ("+d_query+")." );
+
+    // Determine the number of columns.
+    result = SQLNumResultCols( d_statement, &m_columncount );
+    testResult( result, SQL_HANDLE_STMT, d_statement, "Could not determine the number of columns." );
+    // cerr<<"got "<<m_columncount<<" columns"<<endl;
+
+    if(m_columncount) {
+      // cerr<<"first SQLFetch"<<endl;
+      d_result = SQLFetch(d_statement);
+      // cerr<<"first SQLFetch done, d_result="<<d_result<<endl;
+    }
+    else
+      d_result = SQL_NO_DATA;
+
+    if(d_result != SQL_NO_DATA)
+        testResult( d_result, SQL_HANDLE_STMT, d_statement, "Could not do first SQLFetch for ("+d_query+")." );
+    return this;
+  }
+
+  bool hasNextRow() {
+    // cerr<<"hasNextRow d_result="<<d_result<<endl;
+    return d_result!=SQL_NO_DATA;
+  }
+  SSqlStatement* nextRow(row_t& row);
+
+  SSqlStatement* getResult(result_t& result) {
+    result.clear();
+    // if (d_res == NULL) return this;
+    row_t row;
+    while(hasNextRow()) { nextRow(row); result.push_back(row); }
+    return this;
+  }
+
+  SSqlStatement* reset() {
+    SQLCloseCursor(d_statement); // hack, this probably violates some state transitions
+
+    for(auto &i: d_req_bind) { delete [] (char*) i.ParameterValuePtr; delete i.LenPtr; }
+    d_req_bind.clear();
+    d_residx = 0;
+    d_paridx = 0;
+    return this;
+  }
+  const std::string& getQuery() { return d_query; }
+
+private:
+  string d_query;
+  bool d_dolog;
+  bool d_havenextrow;
+  int d_residx, d_paridx, d_parnum;
+  SQLRETURN d_result;
+
+  SQLHDBC d_conn;
+  SQLHSTMT d_statement;    //!< Database statement handle.
+
+  //! Column type.
+  struct column_t
+  {
+    SQLSMALLINT m_type;       //!< Type of the column.
+    SQLULEN     m_size;       //!< Column size.
+    SQLPOINTER  m_pData;      //!< Pointer to the memory where to store the data.
+    bool        m_canBeNull;  //!< Can this column be null?
+  };
+
+  //! Column info.
+  SQLSMALLINT m_columncount;
+
+};
+
+SSqlStatement* SODBCStatement::nextRow(row_t& row)
+{
+  SQLRETURN result;
+
+  row.clear();
+
+  result = d_result;
+  // cerr<<"at start of nextRow, previous SQLFetch result is "<<result<<endl;
+  // FIXME handle errors (SQL_NO_DATA==100, anything other than the two SUCCESS options below is bad news)
+  if ( result == SQL_SUCCESS || result == SQL_SUCCESS_WITH_INFO )
+  {
+    // cerr<<"got row"<<endl;
+    // We've got a data row, now lets get the results.
+    SQLLEN len;
+    for ( int i = 0; i < m_columncount; i++ )
+    {
+      // Clear buffer.
+      // cerr<<"clearing m_pData of size "<<m_columnInfo[ i ].m_size<<endl;
+      SQLCHAR         coldata[128*1024];
+
+      // FIXME: because we cap m_size to 128kbyte, this can truncate silently. see Retrieving Variable-Length Data in Parts at https://msdn.microsoft.com/en-us/library/ms715441(v=vs.85).aspx
+      result = SQLGetData( d_statement, i + 1, SQL_C_CHAR, (SQLPOINTER) coldata, 128*1024-1, &len );
+      // cerr<<"len="<<len<<endl;
+      testResult( result, SQL_HANDLE_STMT, d_statement, "Could not get data." );
+      if ( len == SQL_NULL_DATA ) {
+        // Column is NULL, so we can skip the converting part.
+        row.push_back( "" );
+      }
+      else
+      {
+        row.push_back(reinterpret_cast<char*>(coldata)); // FIXME: not NUL-safe, use len
+      }
+    }
+
+    // Done!
+    d_residx++;
+    // cerr<<"SQLFetch"<<endl;
+    d_result = SQLFetch(d_statement);
+    // cerr<<"subsequent SQLFetch done, d_result="<<d_result<<endl;
+    if(d_result == SQL_NO_DATA) {
+      SQLRETURN result = SQLMoreResults(d_statement);
+      // cerr<<"SQLMoreResults done, result="<<d_result<<endl;
+      if (result == SQL_NO_DATA) {
+        d_result = result;
+      }
+      else {
+        testResult( result, SQL_HANDLE_STMT, d_statement, "Could not fetch next result set for ("+d_query+").");
+      d_result = SQLFetch(d_statement);
+      }
+    }
+    testResult( result, SQL_HANDLE_STMT, d_statement, "Could not do subsequent SQLFetch for ("+d_query+")." );
+
+    return this;
+  }
+
+  SQLFreeStmt( d_statement, SQL_CLOSE );
+  throw SSqlException( "Should not get here." );
+  return this;
+}
+
+// Constructor.
+SODBC::SODBC(
+             const std::string & dsn,
+             const std::string & username,
+             const std::string & password
+            )
+{
+  SQLRETURN     result;
+
+  // Allocate an environment handle.
+  result = SQLAllocHandle( SQL_HANDLE_ENV, SQL_NULL_HANDLE, &m_environment );
+  testResult( result, SQL_NULL_HANDLE, NULL, "Could not allocate an environment handle." );
+
+  // Set ODBC version. (IEUW!)
+  result = SQLSetEnvAttr( m_environment, SQL_ATTR_ODBC_VERSION, reinterpret_cast< void * >( 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"<<endl;
+  SQLRETURN result;
+  result = SQLSetConnectAttr(m_connection, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF, 0);
+  testResult( result, SQL_HANDLE_DBC, m_connection, "startTransaction (enable autocommit) failed" );
+}
+
+void SODBC::commit() {
+  // cerr<<"commit!"<<endl;
+  SQLRETURN result;
+
+  result = SQLEndTran(SQL_HANDLE_DBC, m_connection, SQL_COMMIT); // don't really need this, AUTOCOMMIT_OFF below will also commit
+  testResult( result, SQL_HANDLE_DBC, m_connection, "commit failed" );
+
+  result = SQLSetConnectAttr(m_connection, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF, 0);
+  testResult( result, SQL_HANDLE_DBC, m_connection, "disabling autocommit after commit failed" );
+}
+
+void SODBC::rollback() {
+  // cerr<<"rollback!"<<endl;
+  SQLRETURN result;
+
+  result = SQLEndTran(SQL_HANDLE_DBC, m_connection, SQL_ROLLBACK);
+  testResult( result, SQL_HANDLE_DBC, m_connection, "rollback failed" );
+
+  result = SQLSetConnectAttr(m_connection, SQL_ATTR_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF, 0);
+  testResult( result, SQL_HANDLE_DBC, m_connection, "disabling autocommit after rollback failed" );
+}
diff --git a/modules/godbcbackend/sodbc.hh b/modules/godbcbackend/sodbc.hh
new file mode 100644 (file)
index 0000000..9101b26
--- /dev/null
@@ -0,0 +1,62 @@
+// The Generic ODBC Backend
+// By Michel Stol <michel@powerdns.com>
+
+#ifndef SODBC_HH
+#define SODBC_HH
+
+#include <string>
+#include <vector>
+
+#include <sql.h>
+#include <sqlext.h>
+
+#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
index 7000298a4b47648e3662c1f11c8b191e64ca6bd2..be3b784c98331b72e71bc0d7cf5abf172182c0ec 100644 (file)
@@ -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 (file)
index 0000000..6ece162
--- /dev/null
@@ -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 (file)
index 0000000..d32d0f5
--- /dev/null
@@ -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 (file)
index 0000000..d977e4d
--- /dev/null
@@ -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
index 3e27d8d695ef3f91de0ed1a63c5035a58640f8cc..9dc9cea01ab9a1d52856206e4427c2ecdd642dab 100644 (file)
@@ -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 (symlink)
index 0000000..f8fa0ab
--- /dev/null
@@ -0,0 +1 @@
+../../modules/godbcbackend/.libs/libgodbcbackend.so
\ No newline at end of file
index 3a6c0913c81ed18b0fe0e2c9e3461218e5620ec0..86f59fda1f22ceb016973f2209f84999e3210b38 100755 (executable)
@@ -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