]> granicus.if.org Git - pdns/commitdiff
lua2backend: Add new backend
authorAki Tuomi <cmouse@cmouse.fi>
Sun, 7 Jan 2018 17:39:47 +0000 (19:39 +0200)
committerAki Tuomi <cmouse@cmouse.fi>
Tue, 6 Mar 2018 07:56:41 +0000 (09:56 +0200)
This is API version 1 of lua2 backend.

It provides improved interface for Lua script to act as backends.

Configuration
 - `lua2-filename` - path to script
 - `lua2-query-logging` - log lua queries and results
 - `lua2-api' - API version (default 2)

40 files changed:
configure.ac
docs/backends/index.rst
docs/backends/lua2.rst [new file with mode: 0644]
modules/Makefile.am
modules/lua2backend/Makefile.am [new file with mode: 0644]
modules/lua2backend/OBJECTFILES [new file with mode: 0644]
modules/lua2backend/OBJECTLIBS [new file with mode: 0644]
modules/lua2backend/lua2api2.cc [new file with mode: 0644]
modules/lua2backend/lua2api2.hh [new file with mode: 0644]
modules/lua2backend/lua2backend.cc [new file with mode: 0644]
modules/lua2backend/lua2backend.hh [new file with mode: 0644]
modules/lua2backend/regression-tests/.gitignore [new file with mode: 0644]
modules/lua2backend/regression-tests/axfr/command [new file with mode: 0755]
modules/lua2backend/regression-tests/axfr/description [new file with mode: 0644]
modules/lua2backend/regression-tests/axfr/expected_result [new file with mode: 0644]
modules/lua2backend/regression-tests/axfr/expected_result.dnssec [new file with mode: 0644]
modules/lua2backend/regression-tests/basic-a-dnssec/command [new file with mode: 0755]
modules/lua2backend/regression-tests/basic-a-dnssec/description [new file with mode: 0644]
modules/lua2backend/regression-tests/basic-a-dnssec/expected_result [new file with mode: 0644]
modules/lua2backend/regression-tests/basic-a-dnssec/skip.nodnssec [new file with mode: 0644]
modules/lua2backend/regression-tests/basic-a-resolution/command [new file with mode: 0755]
modules/lua2backend/regression-tests/basic-a-resolution/description [new file with mode: 0644]
modules/lua2backend/regression-tests/basic-a-resolution/expected_result [new file with mode: 0644]
modules/lua2backend/regression-tests/basic-aaaa-resolution/command [new file with mode: 0755]
modules/lua2backend/regression-tests/basic-aaaa-resolution/description [new file with mode: 0644]
modules/lua2backend/regression-tests/basic-aaaa-resolution/expected_result [new file with mode: 0644]
modules/lua2backend/regression-tests/lua2-dnssec.lua [new file with mode: 0644]
modules/lua2backend/regression-tests/lua2.lua [new file with mode: 0644]
modules/lua2backend/regression-tests/nsec-2-dnssec/command [new file with mode: 0755]
modules/lua2backend/regression-tests/nsec-2-dnssec/description [new file with mode: 0644]
modules/lua2backend/regression-tests/nsec-2-dnssec/expected_result [new file with mode: 0644]
modules/lua2backend/regression-tests/nsec-2-dnssec/skip.nodnssec [new file with mode: 0644]
modules/lua2backend/regression-tests/nsec-dnssec/command [new file with mode: 0755]
modules/lua2backend/regression-tests/nsec-dnssec/description [new file with mode: 0644]
modules/lua2backend/regression-tests/nsec-dnssec/expected_result [new file with mode: 0644]
modules/lua2backend/regression-tests/nsec-dnssec/skip.nodnssec [new file with mode: 0644]
regression-tests/backends/common
regression-tests/backends/lua2-master [new file with mode: 0644]
regression-tests/modules/liblua2backend.so [new symlink]
regression-tests/start-test-stop

index e03561e129353673c384a2a01baf58fdfa3fef86..022c43bbaec42aa42bd7190766370fee3ae73e14 100644 (file)
@@ -237,6 +237,10 @@ AM_CONDITIONAL([SQLITE3], [test "x$needsqlite3" = "xyes"])
 
 for a in $modules; do
   AC_MSG_CHECKING([whether we can build module "${a}"])
+  AS_IF([test "x$a" = "xlua2"], [
+     AS_IF([test "x$with_lua" != "xyes"],
+           AC_MSG_ERROR([Cannot build lua2 module without lua]),[])
+  ])
   if [[ -d "$srcdir/modules/${a}backend" ]]; then
     AC_MSG_RESULT([yes])
     moduledirs="$moduledirs ${a}backend"
@@ -314,6 +318,7 @@ AC_CONFIG_FILES([
   modules/gsqlite3backend/Makefile
   modules/ldapbackend/Makefile
   modules/luabackend/Makefile
+  modules/lua2backend/Makefile
   modules/mydnsbackend/Makefile
   modules/opendbxbackend/Makefile
   modules/oraclebackend/Makefile
index 986ea0fb8c07ee2515d06222f905580c9ba83248..caa25276aa2a8b244221c49ad090318c7d16056d 100644 (file)
@@ -22,6 +22,8 @@ The following table describes the supported backends and some of their capabilit
 +------------------------------------------------+--------+--------+-------+--------------+-------------+---------------------------------+--------------+
 | :doc:`LDAP <ldap>`                             | Yes    | No     | No    | No           | No          | No                              | ``ldap``     |
 +------------------------------------------------+--------+--------+-------+--------------+-------------+---------------------------------+--------------+
+| :doc:`Lua2 <lua2>`                             | Yes    | Yes    | No    | No           | Yes         | Yes                             | ``lua2``     |
++------------------------------------------------+--------+--------+-------+--------------+-------------+---------------------------------+--------------+
 | :doc:`MyDNS <mydns>`                           | Yes    | No     | No    | No           | No          | No                              | ``mydns``    |
 +------------------------------------------------+--------+--------+-------+--------------+-------------+---------------------------------+--------------+
 | :doc:`OpenDBX <opendbx>`                       | Yes    | Yes    | Yes   | Yes          | No          | No                              | ``opendbx``  |
@@ -53,6 +55,7 @@ These backends have :doc:`features unique <generic-sql>` to the generic SQL back
   geoip
   ldap
   lua
+  lua2
   mydns
   opendbx
   oracle
diff --git a/docs/backends/lua2.rst b/docs/backends/lua2.rst
new file mode 100644 (file)
index 0000000..9725c8e
--- /dev/null
@@ -0,0 +1,188 @@
+Lua Backend
+===========
+
+* Native: Yes
+* Master: Yes
+* Slave: No
+* Superslave: No
+* Autoserial: No
+* DNSSEC: Yes
+* Disabled data: No
+* Comments: No
+* Module name: lua2
+* Launch name: ``lua2``
+
+This is a rewrite of existing Lua backend.
+This backend is stub between your Lua script and PowerDNS authoritative server.
+The backend uses AuthLua4 base class, and you can use same functions and types as in any other Lua script.
+
+.. warning::
+  Some of the function calls and configuration settings have been changed from original ``Luabackend``, please review this document carefully.
+
+.. warning::
+  All places which use DNS names now use DNSName class which cannot be compared directly to a string.
+  To compare them against a string use either ``tostring(dnsname)`` or ``newDN(string)``.
+
+.. warning::
+  There is no API version 1.
+  Use Luabackend if you need version 1.
+
+API description (v2)
+^^^^^^^^^^^^^^^^^^^^
+
+``bool dns_dnssec``
+~~~~~~~~~~~~~~~~~~~
+If your script supports DNSSEC, set this to true.
+
+``dns_lookup(qtype, qname, domain_id, ctx)``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Perform lookup of given resource record name and type.
+
+INPUT:
+ - QType qtype - Type of queried resource record
+ - DNSName qname - Name of queried resource record
+ - int domain_id - ID of associated domain
+ - table ctx - Query context table, contains ``source_address`` and ``real_source_address``.
+
+OUTPUT:
+ Expects a array which has tables with following keys:
+ - DNSName name - resource record name (can also be string)
+ - string type - type of resource record (can also be QType or valid integer)
+ - string content - resource record content
+ - int ttl - time to live for this resource record (default: configured value)
+ - int domain_id - ID of associated domain (default: -1)
+ - bool auth - Whether data is authoritative or not (default: true)
+ - int last_modified - UNIX timestamp of last modification
+ - int scope_mask - How many bytes of source IP netmask was used for this result
+
+NOTES:
+ Defaults are used for omitted keys.
+ Return empty array if you have no results.
+ The requested record type is unlikely to match what was asked from PowerDNS.
+ This function is **required**.
+
+
+``dns_list(target, domain_id)``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+List all resource records for target.
+
+INPUT:
+ - DNSName target - Zone name to list
+ - int domain_id - Associated domain ID
+
+OUTPUT:
+ Same as ``lookup`` function. Return false if not found or wanted.
+
+NOTES:
+ This function is **optional**.
+``dns_get_domaininfo(domain)``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Get domain information for given domain.
+
+INPUT:
+ - DNSName domain - Domain to get info for
+
+OUTPUT:
+ Return false if not supported or found, otherwise expects a table with keys:
+ - string account - Associated account of this domain (default: <empty>)
+ - string kind - Domain kind (NATIVE,MASTER,SLAVE) (default: NATIVE)
+ - int id - Associated domain ID (default: -1)
+ - int last_check - UNIX timestamp of last check from master (default: 0)
+ - table of strings masters - Master servers for this domain (default: <empty>)
+ - long notified_serial - Notified serial to slaves (default: 0)
+ - long serial - Current domain serial
+
+NOTES:
+ This function is **optional**.
+ Defaults are used for omitted keys.
+ ``last_check`` is for automatic serial.
+ ``masters``, ``account``, ``notified_serial`` are for master/slave interaction only.
+ If this function is missing, it will revert into looking up SOA record for the given domain,
+ and uses that, if found.
+
+``dns_get_all_domains()``
+~~~~~~~~~~~~~~~~~~~~~~~~~
+Get domain information for all domains.
+
+OUTPUT:
+ Return false if not supported or found, otherwise return a table of string, domaininfo. See ``dns_get_domaininfo```.
+
+NOTES:
+ This function is **optional**, except if you need master functionality.
+
+``dns_get_domain_metadata(domain, kind)``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Get metadata value(s) for given domain and metadata kind.
+
+INPUT:
+ - DNSName domain - Domain to get metadata for
+ - string kind - What kind of metadata to return
+
+OUTPUT:
+ - array of strings. Or false if not supported or found.
+
+NOTES:
+ This function is **required** if ``dns_dnssec`` is true.
+
+``dns_get_all_domain_metadata(domain)``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Get all metadata for domain.
+
+INPUT:
+ - DNSName domain - Domain to get metadata for
+
+OUTPUT:
+ Table with metadata keys containing array of strings. Or false if not supported or found.
+
+NOTES:
+ This function is **optional**.
+
+``dns_get_domain_keys(domain)``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Get DNSSEC key(s) for the given domain. Content must be valid key record in format that PowerDNS understands.
+
+INPUT:
+ - DNSName domain - Domain to get key(s) for
+
+OUTPUT:
+ Return false if not found or supported, otherwise expects array of tables with keys:
+ - int id - Key ID
+ - int flags - Key flags
+ - bool active - Is key active
+ - string content - Key itself
+
+NOTES:
+ This function is **optional**. However, not implementing this means you cannot do live signing.
+
+``dns_get_before_and_after_names_absolute(id, qname)``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Calculate NSEC before/after value for the given qname for domain with id.
+
+INPUT:
+ - int id - Associated domain id
+ - DNSName qname - DNS name to calculate
+
+OUTPUT:
+ Table with keys:
+ - unhashed - DNSName of the unhashed relative to domain
+ - before - (hashed) name of previous record relative to domain
+ - after - (hashed) name of next record relative to domain
+
+NOTES:
+ Strings are promoted to DNSNames (you can also return DNSNames directly)
+ This function is **required** if ``dns_dnssec`` is true.
+ Hashing is required with NSEC3/5.
+ ``before`` and ``after`` should wrap, so that after record of last record is apex record.
+ You can use ``DNSName#canonCompare`` to sort records in correct order.
+
+``dns_set_notified(id, serial)``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Called after NOTIFY so that backend can store the notified serial.
+
+INPUT:
+ - int id - Associated domain id
+ - long serial - Notified serial
+
+NOTES:
+ This function is **optional**. However, not implementing this can cause problems with master functionality.
index fa231a03cafe8d0b35174918c6a2a0c23dcfc4bf..a5da8a5a65e69c56c121c5064999dbb1535a7f28 100644 (file)
@@ -10,6 +10,7 @@ DIST_SUBDIRS = \
        gsqlite3backend \
        ldapbackend \
        luabackend \
+       lua2backend \
        mydnsbackend \
        opendbxbackend \
        oraclebackend \
diff --git a/modules/lua2backend/Makefile.am b/modules/lua2backend/Makefile.am
new file mode 100644 (file)
index 0000000..841abe3
--- /dev/null
@@ -0,0 +1,13 @@
+AM_CPPFLAGS += $(LUA_CFLAGS) \
+       -I$(top_srcdir)/ext/luawrapper/include
+
+EXTRA_DIST = OBJECTFILES OBJECTLIBS
+
+pkglib_LTLIBRARIES = liblua2backend.la
+
+liblua2backend_la_SOURCES = \
+       lua2backend.cc lua2backend.hh \
+       lua2api2.hh lua2api2.cc
+
+liblua2backend_la_LDFLAGS = -module -avoid-version
+liblua2backend_la_LIBADD = $(LUA_LIBS)
diff --git a/modules/lua2backend/OBJECTFILES b/modules/lua2backend/OBJECTFILES
new file mode 100644 (file)
index 0000000..79d15f2
--- /dev/null
@@ -0,0 +1 @@
+lua2backend.lo lua2api2.lo
diff --git a/modules/lua2backend/OBJECTLIBS b/modules/lua2backend/OBJECTLIBS
new file mode 100644 (file)
index 0000000..9094a02
--- /dev/null
@@ -0,0 +1 @@
+$(LUA_LIBS)
diff --git a/modules/lua2backend/lua2api2.cc b/modules/lua2backend/lua2api2.cc
new file mode 100644 (file)
index 0000000..df53d46
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTAPILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "lua2backend.hh"
+
+Lua2BackendAPIv2::~Lua2BackendAPIv2() {
+  if (f_deinit)
+    f_deinit();
+}
diff --git a/modules/lua2backend/lua2api2.hh b/modules/lua2backend/lua2api2.hh
new file mode 100644 (file)
index 0000000..21519ea
--- /dev/null
@@ -0,0 +1,418 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTAPILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+#ifndef LUA2API_2_HH
+#define LUA2API_2_HH 1
+
+#include "boost/lexical_cast.hpp"
+#include "boost/algorithm/string/join.hpp"
+#include "pdns/arguments.hh"
+
+class Lua2BackendAPIv2 : public DNSBackend, AuthLua4 {
+private:
+  typedef std::function<void()> init_call_t;
+  typedef std::function<void()> deinit_call_t;
+
+  typedef std::vector<std::pair<string, string> > lookup_context_t;
+
+  typedef std::vector<std::pair<int, std::vector<std::pair<string, boost::variant<bool, int, DNSName, string, QType> > > > > lookup_result_t;
+  typedef std::function<lookup_result_t(const QType& qtype, const DNSName& qname, int domain_id, const lookup_context_t &ctx)> lookup_call_t;
+
+  typedef boost::variant<bool, lookup_result_t> list_result_t;
+  typedef std::function<list_result_t(const DNSName& qname, int domain_id)> list_call_t;
+
+  typedef vector<pair<string, boost::variant<bool, long, string, vector<string> > > > domaininfo_result_t;
+  typedef boost::variant<bool, domaininfo_result_t> get_domaininfo_result_t;
+  typedef vector<pair<DNSName, domaininfo_result_t> > get_all_domains_result_t;
+  typedef std::function<get_domaininfo_result_t(const DNSName& domain)> get_domaininfo_call_t;
+  typedef std::function<get_all_domains_result_t()> get_all_domains_call_t;
+
+  typedef vector<pair<int, string> > domain_metadata_result_t;
+  typedef boost::variant<bool, domain_metadata_result_t> get_domain_metadata_result_t;
+  typedef boost::variant<bool, vector<pair<string, domain_metadata_result_t> > > get_all_domain_metadata_result_t;
+  typedef std::function<get_domain_metadata_result_t(const DNSName& domain, const string& kind)> get_domain_metadata_call_t;
+  typedef std::function<get_all_domain_metadata_result_t(const DNSName& domain)> get_all_domain_metadata_call_t;
+
+  typedef vector<pair<string, boost::variant<bool, int, string> > > keydata_result_t;
+  typedef boost::variant<bool, vector<pair<int, keydata_result_t> > > get_domain_keys_result_t;
+  typedef std::function<get_domain_keys_result_t(const DNSName& domain)> get_domain_keys_call_t;
+
+  typedef std::vector<std::pair<string, boost::variant<string, DNSName> > > before_and_after_names_result_t;
+  typedef boost::variant<bool, before_and_after_names_result_t> get_before_and_after_names_absolute_result_t;
+  typedef std::function<get_before_and_after_names_absolute_result_t(int id, const DNSName& qname)> get_before_and_after_names_absolute_call_t;
+
+  typedef std::function<void(int, long)> set_notified_call_t;
+
+  typedef std::function<string(const string& cmd)> direct_backend_cmd_call_t;
+public:
+  Lua2BackendAPIv2(const string& suffix) {
+    setArgPrefix("lua2"+suffix);
+    d_debug_log = mustDo("query-logging");
+    prepareContext();
+    loadFile(getArg("filename"));
+  }
+
+  ~Lua2BackendAPIv2();
+
+  #define logCall(func, var) { if (d_debug_log) {  L<<Logger::Debug<<"["<<getPrefix()<<"] Calling "<<func<<"("<<var<<")"<< endl; } }
+  #define logResult(var) { if (d_debug_log) { L<<Logger::Debug<<"["<<getPrefix()<<"] Got result " << "'" << var << "'" << endl; } }
+
+  virtual void postPrepareContext() override {
+    AuthLua4::postPrepareContext();
+  }
+
+  virtual void postLoad() override {
+    f_lookup = d_lw->readVariable<boost::optional<lookup_call_t>>("dns_lookup").get_value_or(0);
+    f_list = d_lw->readVariable<boost::optional<list_call_t>>("dns_list").get_value_or(0);
+    f_get_all_domains = d_lw->readVariable<boost::optional<get_all_domains_call_t>>("dns_get_all_domains").get_value_or(0);
+    f_get_domaininfo = d_lw->readVariable<boost::optional<get_domaininfo_call_t>>("dns_get_domaininfo").get_value_or(0);
+    f_get_domain_metadata = d_lw->readVariable<boost::optional<get_domain_metadata_call_t>>("dns_get_domain_metadata").get_value_or(0);
+    f_get_all_domain_metadata = d_lw->readVariable<boost::optional<get_all_domain_metadata_call_t>>("dns_get_all_domain_metadata").get_value_or(0);
+    f_get_domain_keys = d_lw->readVariable<boost::optional<get_domain_keys_call_t>>("dns_get_domain_keys").get_value_or(0);
+    f_get_before_and_after_names_absolute = d_lw->readVariable<boost::optional<get_before_and_after_names_absolute_call_t>>("dns_get_before_and_after_names_absolute").get_value_or(0);
+    f_set_notified = d_lw->readVariable<boost::optional<set_notified_call_t>>("dns_set_notified").get_value_or(0);
+
+    auto init = d_lw->readVariable<boost::optional<init_call_t>>("dns_init").get_value_or(0);
+    if (init)
+      init();
+
+    f_deinit = d_lw->readVariable<boost::optional<deinit_call_t>>("dns_deinit").get_value_or(0);
+
+    if (f_lookup == nullptr)
+      throw PDNSException("dns_lookup missing");
+
+    /* see if dnssec support is wanted */
+    d_dnssec = d_lw->readVariable<boost::optional<bool>>("dns_dnssec").get_value_or(false);
+    if (d_dnssec) {
+      if (f_get_domain_metadata == nullptr)
+        throw PDNSException("dns_dnssec is true but dns_get_domain_metadata is missing");
+      if (f_get_before_and_after_names_absolute == nullptr)
+        throw PDNSException("dns_dnssec is true but dns_get_before_and_after_names_absolute is missing");
+      /* domain keys is not strictly speaking necessary for dnssec backend */
+      if (f_get_domain_keys == nullptr)
+        L<<Logger::Warning<<"dns_get_domain_keys missing - cannot do live signing"<<endl;
+    }
+  }
+
+  bool doesDNSSEC() override {
+    return d_dnssec;
+  }
+
+  void parseLookup(const lookup_result_t& result) {
+    for(const auto& row: result) {
+      DNSResourceRecord rec;
+      for(const auto& item: row.second) {
+         if (item.first == "type") {
+           if (item.second.which() == 1)
+             rec.qtype = QType(boost::get<int>(item.second));
+           else if (item.second.which() == 3)
+             rec.qtype = boost::get<string>(item.second);
+           else if (item.second.which() == 4)
+             rec.qtype = boost::get<QType>(item.second);
+           else
+             throw PDNSException("Unsupported value for type");
+        } else if (item.first == "name") {
+          if (item.second.which() == 3)
+            rec.qname = DNSName(boost::get<string>(item.second));
+          else if (item.second.which() == 2)
+            rec.qname = boost::get<DNSName>(item.second);
+          else
+            throw PDNSException("Unsupported value for name");
+        } else if (item.first == "domain_id")
+          rec.domain_id = boost::get<int>(item.second);
+        else if (item.first == "auth")
+          rec.auth = boost::get<bool>(item.second);
+        else if (item.first == "last_modified")
+          rec.last_modified = static_cast<time_t>(boost::get<int>(item.second));
+        else if (item.first == "ttl")
+          rec.ttl = boost::get<int>(item.second);
+        else if (item.first == "content")
+          rec.setContent(boost::get<string>(item.second));
+        else if (item.first == "scopeMask")
+          rec.scopeMask = boost::get<int>(item.second);
+        else
+          L<<Logger::Warning<<"Unsupported key '"<<item.first<<"' in lookup or list result"<<endl;
+
+      }
+      logResult(rec.qname<<" IN "<<rec.qtype.getName()<<" "<<rec.ttl<<" "<<rec.getZoneRepresentation());
+      d_result.push_back(rec);
+    }
+    if (d_result.empty() && d_debug_log)
+      L<<Logger::Debug<<"["<<getPrefix()<<"] Got empty result"<<endl;
+  }
+
+  bool list(const DNSName &target, int domain_id, bool include_disabled=false) override {
+    if (f_list == nullptr) {
+      L<<Logger::Error<<"["<<getPrefix()<<"] dns_list missing - cannot do AXFR"<<endl;
+      return false;
+    }
+
+    if (d_result.size() != 0)
+      throw PDNSException("list attempted while another was running");
+
+    logCall("list", "target="<<target<<",domain_id="<<domain_id);
+    list_result_t result = f_list(target, domain_id);
+
+    if (result.which() == 0)
+      return false;
+
+    parseLookup(boost::get<lookup_result_t>(result));
+
+    return true;
+  }
+
+  void lookup(const QType &qtype, const DNSName &qname, DNSPacket *p, int domain_id) override {
+    if (d_result.size() != 0)
+      throw PDNSException("lookup attempted while another was running");
+
+    lookup_context_t ctx;
+    if (p != NULL) {
+      ctx.emplace_back(lookup_context_t::value_type{"source_address", p->getRemote().toString()});
+      ctx.emplace_back(lookup_context_t::value_type{"real_source_address", p->getRealRemote().toString()});
+    }
+
+    logCall("lookup", "qtype="<<qtype.getName()<<",qname="<<qname<<",domain_id="<<domain_id);
+    lookup_result_t result = f_lookup(qtype, qname, domain_id, ctx);
+    parseLookup(result);
+  }
+
+  bool get(DNSResourceRecord &rr) override {
+    if (d_result.size() == 0)
+      return false;
+    rr = std::move(d_result.front());
+    d_result.pop_front();
+    return true;
+  }
+
+  string directBackendCmd(const string& querystr) override {
+    string::size_type pos = querystr.find_first_of(" \t");
+    string cmd = querystr;
+    string par = "";
+    if (pos != string::npos) {
+      cmd = querystr.substr(0, pos);
+      par = querystr.substr(pos+1);
+    }
+    direct_backend_cmd_call_t f = d_lw->readVariable<boost::optional<direct_backend_cmd_call_t>>(cmd).get_value_or(0);
+    if (f == nullptr) {
+      return cmd + "not found";
+    }
+    logCall(cmd, "parameter="<<par);
+    return f(par);
+  }
+
+  void setNotified(uint32_t id, uint32_t serial) override {
+    if (f_set_notified == NULL)
+      return;
+    logCall("dns_set_notified", "id="<<static_cast<int>(id)<<",serial="<<serial);
+    f_set_notified(static_cast<int>(id), serial);
+  }
+
+  void parseDomainInfo(const domaininfo_result_t& row, DomainInfo& di) {
+     for(const auto& item: row) {
+       if (item.first == "account")
+         di.account = boost::get<string>(item.second);
+       else if (item.first == "last_check")
+         di.last_check = static_cast<time_t>(boost::get<long>(item.second));
+       else if (item.first == "masters")
+         di.masters = boost::get<vector<string>>(item.second);
+       else if (item.first == "id")
+         di.id = static_cast<int>(boost::get<long>(item.second));
+       else if (item.first == "notified_serial")
+         di.notified_serial = static_cast<unsigned int>(boost::get<long>(item.second));
+       else if (item.first == "serial")
+         di.serial = static_cast<unsigned int>(boost::get<long>(item.second));
+       else if (item.first == "kind")
+         di.kind = DomainInfo::stringToKind(boost::get<string>(item.second));
+       else
+         L<<Logger::Warning<<"Unsupported key '"<<item.first<<"' in domaininfo result"<<endl;
+     }
+     di.backend = this;
+     logResult("zone="<<di.zone<<",serial="<<di.serial<<",kind="<<di.getKindString());
+  }
+
+  bool getDomainInfo(const DNSName& domain, DomainInfo& di) override {
+    if (f_get_domaininfo == nullptr) {
+      // use getAuth instead
+      SOAData sd;
+      if (!getAuth(domain, &sd))
+        return false;
+
+      di.zone = domain;
+      di.backend = this;
+      di.serial = sd.serial;
+      return true;
+    }
+
+    logCall("get_domaininfo","domain="<<domain);
+    get_domaininfo_result_t result = f_get_domaininfo(domain);
+
+    if (result.which() == 0)
+      return false;
+
+    di.zone = domain;
+    parseDomainInfo(boost::get<domaininfo_result_t>(result), di);
+
+    return true;
+  }
+
+  void getAllDomains(vector<DomainInfo> *domains, bool include_disabled=false) override {
+     if (f_get_all_domains == nullptr)
+       return;
+
+     logCall("get_all_domains", "");
+     for(const auto& row: f_get_all_domains()) {
+       DomainInfo di;
+       di.zone = row.first;
+       logResult(di.zone);
+       parseDomainInfo(row.second, di);
+       domains->push_back(di);
+     }
+  }
+
+  bool getAllDomainMetadata(const DNSName& name, std::map<std::string, std::vector<std::string> >& meta) override {
+    if (f_get_all_domain_metadata == nullptr)
+      return false;
+
+    logCall("get_all_domain_metadata","name="<<name);
+    get_all_domain_metadata_result_t result = f_get_all_domain_metadata(name);
+    if (result.which() == 0)
+      return false;
+
+    for(const auto& row: boost::get< vector<pair<string, domain_metadata_result_t> > >(result)) {
+       meta[row.first].clear();
+       for(const auto& item: row.second)
+          meta[row.first].push_back(item.second);
+       logResult("kind="<<row.first<<",value="<<boost::algorithm::join(meta[row.first], ", "));
+    }
+
+    return true;
+  }
+
+  bool getDomainMetadata(const DNSName& name, const std::string& kind, std::vector<std::string>& meta) override {
+    if (f_get_domain_metadata == nullptr)
+      return false;
+
+    logCall("get_domain_metadata","name="<<name<<",kind="<<kind);
+    get_domain_metadata_result_t result = f_get_domain_metadata(name, kind);
+    if (result.which() == 0)
+      return false;
+
+    meta.clear();
+    for(const auto& item: boost::get<domain_metadata_result_t>(result))
+      meta.push_back(item.second);
+
+    logResult("value="<<boost::algorithm::join(meta, ", "));
+    return true;
+  }
+
+  bool getDomainKeys(const DNSName& name, std::vector<DNSBackend::KeyData>& keys) override {
+    if (f_get_domain_keys == nullptr)
+      return false;
+
+    logCall("get_domain_keys","name="<<name);
+    get_domain_keys_result_t result = f_get_domain_keys(name);
+
+    if (result.which() == 0)
+      return false;
+
+    for(const auto& row: boost::get<vector<pair<int, keydata_result_t> > >(result)) {
+      DNSBackend::KeyData key;
+      for(const auto& item: row.second) {
+        if (item.first == "content")
+          key.content = boost::get<string>(item.second);
+        else if (item.first == "id")
+          key.id = static_cast<unsigned int>(boost::get<int>(item.second));
+        else if (item.first == "flags")
+          key.flags = static_cast<unsigned int>(boost::get<int>(item.second));
+        else if (item.first == "active")
+          key.active = boost::get<bool>(item.second);
+        else
+          L<<Logger::Warning<<"["<<getPrefix()<<"] Unsupported key '"<<item.first<<"' in keydata result"<<endl;
+      }
+      logResult("id="<<key.id<<",flags="<<key.flags<<",active="<<(key.active ? "true" : "false"));
+      keys.push_back(key);
+    }
+
+    return true;
+  }
+
+  bool getBeforeAndAfterNamesAbsolute(uint32_t id, const DNSName& qname, DNSName& unhashed, DNSName& before, DNSName& after) override {
+    if (f_get_before_and_after_names_absolute == nullptr)
+      return false;
+
+    logCall("get_before_and_after_names_absolute", "id=<<"<<id<<",qname="<<qname);
+    get_before_and_after_names_absolute_result_t result = f_get_before_and_after_names_absolute(id, qname);
+
+    if (result.which() == 0)
+      return false;
+
+    before_and_after_names_result_t row = boost::get<before_and_after_names_result_t>(result);
+    if (row.size() != 3) {
+      L<<Logger::Error<<"Invalid result from dns_get_before_and_after_names_absolute, expected array with 3 items, got "<<row.size()<<"item(s)"<<endl;
+      return false;
+    }
+    for(const auto& item: row) {
+      DNSName value;
+      if (item.second.which() == 0)
+         value = DNSName(boost::get<string>(item.second));
+      else
+         value = DNSName(boost::get<DNSName>(item.second));
+      if (item.first == "unhashed")
+        unhashed = value;
+      else if (item.first == "before")
+        before = value;
+      else if (item.first == "after")
+        after = value;
+      else {
+        L<<Logger::Error<<"Invalid result from dns_get_before_and_after_names_absolute, unexpected key "<<item.first;
+        return false;
+      }
+    }
+
+    logResult("unhashed="<<unhashed<<",before="<<before<<",after="<<after);
+    return true;
+  }
+
+private:
+  std::list<DNSResourceRecord> d_result;
+  bool d_debug_log;
+  bool d_dnssec;
+
+  lookup_call_t f_lookup;
+  list_call_t f_list;
+
+  get_domaininfo_call_t f_get_domaininfo;
+  get_all_domains_call_t f_get_all_domains;
+
+  get_domain_metadata_call_t f_get_domain_metadata;
+  get_all_domain_metadata_call_t f_get_all_domain_metadata;
+
+  get_domain_keys_call_t f_get_domain_keys;
+
+  get_before_and_after_names_absolute_call_t f_get_before_and_after_names_absolute;
+
+  set_notified_call_t f_set_notified;
+
+  deinit_call_t f_deinit;
+};
+
+#endif
diff --git a/modules/lua2backend/lua2backend.cc b/modules/lua2backend/lua2backend.cc
new file mode 100644 (file)
index 0000000..254061c
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTAPILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include "pdns/logger.hh"
+#include "pdns/arguments.hh"
+#include "lua2backend.hh"
+
+class Lua2Factory : public BackendFactory
+{
+public:
+  Lua2Factory() : BackendFactory("lua2") {}
+
+  void declareArguments(const string &suffix="")
+  {
+    declare(suffix,"filename","Filename of the script for lua backend","powerdns-luabackend.lua");
+    declare(suffix,"query-logging","Logging of the Lua2 Backend","no");
+    declare(suffix,"api","Lua backend API version","2");
+  }
+
+  DNSBackend *make(const string &suffix="")
+  {
+    const std::string apiSet = "lua2" + suffix + "-api";
+    const int api = ::arg().asNum(apiSet);
+    DNSBackend *be;
+    switch(api) {
+    case 1:
+      throw PDNSException("Use luabackend for api version 1");
+    case 2:
+      be = new Lua2BackendAPIv2(suffix); break;
+    default:
+      throw PDNSException("Unsupported ABI version " + ::arg()[apiSet]);
+    }
+    return be;
+  }
+};
+
+class Lua2Loader
+{
+public:
+  Lua2Loader()
+  {
+    BackendMakers().report(new Lua2Factory);
+
+    L << Logger::Info << "[lua2backend] This is the lua2 backend version " VERSION
+#ifndef REPRODUCIBLE
+      << " (" __DATE__ " " __TIME__ ")"
+#endif
+      << " reporting" << endl;
+  }
+};
+
+static Lua2Loader luaLoader;
diff --git a/modules/lua2backend/lua2backend.hh b/modules/lua2backend/lua2backend.hh
new file mode 100644 (file)
index 0000000..2539d46
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * This file is part of PowerDNS or dnsdist.
+ * Copyright -- PowerDNS.COM B.V. and its contributors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of version 2 of the GNU General Public License as
+ * published by the Free Software Foundation.
+ *
+ * In addition, for the avoidance of any doubt, permission is granted to
+ * link this program with OpenSSL and to (re)distribute the binaries
+ * produced as the result of such linking.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#pragma once
+#ifndef LUA2BACKEND_HH
+#define LUA2BACKEND_HH 1
+
+#include "pdns/dnsbackend.hh"
+#include "pdns/lua-auth4.hh"
+#include "lua2api2.hh"
+
+#endif
diff --git a/modules/lua2backend/regression-tests/.gitignore b/modules/lua2backend/regression-tests/.gitignore
new file mode 100644 (file)
index 0000000..8955fe5
--- /dev/null
@@ -0,0 +1,3 @@
+diff
+real_result
+*.out
diff --git a/modules/lua2backend/regression-tests/axfr/command b/modules/lua2backend/regression-tests/axfr/command
new file mode 100755 (executable)
index 0000000..d73d881
--- /dev/null
@@ -0,0 +1,2 @@
+#!/bin/sh
+cleandig test.invalid AXFR showflags unhash
diff --git a/modules/lua2backend/regression-tests/axfr/description b/modules/lua2backend/regression-tests/axfr/description
new file mode 100644 (file)
index 0000000..9f1f4fe
--- /dev/null
@@ -0,0 +1 @@
+This test tries to AXFR a domain
diff --git a/modules/lua2backend/regression-tests/axfr/expected_result b/modules/lua2backend/regression-tests/axfr/expected_result
new file mode 100644 (file)
index 0000000..56bd7ef
--- /dev/null
@@ -0,0 +1,14 @@
+_ssh._tcp.service.test.invalid.        60      IN      SRV     0 0 22 shell.test.invalid.
+ns1.test.invalid.      60      IN      A       127.0.0.1
+ns1.test.invalid.      60      IN      AAAA    fe80::1
+ns2.test.invalid.      60      IN      A       127.0.0.2
+ns2.test.invalid.      60      IN      AAAA    fe80::2
+shell.test.invalid.    60      IN      A       127.0.0.4
+shell.test.invalid.    60      IN      AAAA    fe80::4
+test.invalid.  60      IN      NS      ns1.test.invalid.
+test.invalid.  60      IN      NS      ns2.test.invalid.
+test.invalid.  60      IN      SOA     ns1.test.invalid. root.test.invalid. 20180115 1 2 3 4
+test.invalid.  60      IN      SOA     ns1.test.invalid. root.test.invalid. 20180115 1 2 3 4
+test.invalid.  60      IN      TXT     "this is a test record" "in two parts"
+www.test.invalid.      60      IN      A       127.0.0.3
+www.test.invalid.      60      IN      AAAA    fe80::3
diff --git a/modules/lua2backend/regression-tests/axfr/expected_result.dnssec b/modules/lua2backend/regression-tests/axfr/expected_result.dnssec
new file mode 100644 (file)
index 0000000..8b0e012
--- /dev/null
@@ -0,0 +1,41 @@
+_ssh._tcp.service.test.invalid.        4       IN      NSEC    shell.test.invalid. SRV RRSIG NSEC
+_ssh._tcp.service.test.invalid.        4       IN      RRSIG   NSEC 13 5 4 [expiry] [inception] [keytag] test.invalid. ...
+_ssh._tcp.service.test.invalid.        60      IN      RRSIG   SRV 13 5 60 [expiry] [inception] [keytag] test.invalid. ...
+_ssh._tcp.service.test.invalid.        60      IN      SRV     0 0 22 shell.test.invalid.
+ns1.test.invalid.      4       IN      NSEC    ns2.test.invalid. A AAAA RRSIG NSEC
+ns1.test.invalid.      4       IN      RRSIG   NSEC 13 3 4 [expiry] [inception] [keytag] test.invalid. ...
+ns1.test.invalid.      60      IN      A       127.0.0.1
+ns1.test.invalid.      60      IN      AAAA    fe80::1
+ns1.test.invalid.      60      IN      RRSIG   A 13 3 60 [expiry] [inception] [keytag] test.invalid. ...
+ns1.test.invalid.      60      IN      RRSIG   AAAA 13 3 60 [expiry] [inception] [keytag] test.invalid. ...
+ns2.test.invalid.      4       IN      NSEC    _ssh._tcp.service.test.invalid. A AAAA RRSIG NSEC
+ns2.test.invalid.      4       IN      RRSIG   NSEC 13 3 4 [expiry] [inception] [keytag] test.invalid. ...
+ns2.test.invalid.      60      IN      A       127.0.0.2
+ns2.test.invalid.      60      IN      AAAA    fe80::2
+ns2.test.invalid.      60      IN      RRSIG   A 13 3 60 [expiry] [inception] [keytag] test.invalid. ...
+ns2.test.invalid.      60      IN      RRSIG   AAAA 13 3 60 [expiry] [inception] [keytag] test.invalid. ...
+shell.test.invalid.    4       IN      NSEC    www.test.invalid. A AAAA RRSIG NSEC
+shell.test.invalid.    4       IN      RRSIG   NSEC 13 3 4 [expiry] [inception] [keytag] test.invalid. ...
+shell.test.invalid.    60      IN      A       127.0.0.4
+shell.test.invalid.    60      IN      AAAA    fe80::4
+shell.test.invalid.    60      IN      RRSIG   A 13 3 60 [expiry] [inception] [keytag] test.invalid. ...
+shell.test.invalid.    60      IN      RRSIG   AAAA 13 3 60 [expiry] [inception] [keytag] test.invalid. ...
+test.invalid.  4       IN      DNSKEY  256 3 13 ...
+test.invalid.  4       IN      DNSKEY  257 3 13 ...
+test.invalid.  4       IN      NSEC    ns1.test.invalid. NS SOA TXT RRSIG NSEC DNSKEY
+test.invalid.  4       IN      RRSIG   DNSKEY 13 2 4 [expiry] [inception] [keytag] test.invalid. ...
+test.invalid.  4       IN      RRSIG   NSEC 13 2 4 [expiry] [inception] [keytag] test.invalid. ...
+test.invalid.  60      IN      NS      ns1.test.invalid.
+test.invalid.  60      IN      NS      ns2.test.invalid.
+test.invalid.  60      IN      RRSIG   NS 13 2 60 [expiry] [inception] [keytag] test.invalid. ...
+test.invalid.  60      IN      RRSIG   SOA 13 2 60 [expiry] [inception] [keytag] test.invalid. ...
+test.invalid.  60      IN      RRSIG   TXT 13 2 60 [expiry] [inception] [keytag] test.invalid. ...
+test.invalid.  60      IN      SOA     ns1.test.invalid. root.test.invalid. 20180115 1 2 3 4
+test.invalid.  60      IN      SOA     ns1.test.invalid. root.test.invalid. 20180115 1 2 3 4
+test.invalid.  60      IN      TXT     "this is a test record" "in two parts"
+www.test.invalid.      4       IN      NSEC    test.invalid. A AAAA RRSIG NSEC
+www.test.invalid.      4       IN      RRSIG   NSEC 13 3 4 [expiry] [inception] [keytag] test.invalid. ...
+www.test.invalid.      60      IN      A       127.0.0.3
+www.test.invalid.      60      IN      AAAA    fe80::3
+www.test.invalid.      60      IN      RRSIG   A 13 3 60 [expiry] [inception] [keytag] test.invalid. ...
+www.test.invalid.      60      IN      RRSIG   AAAA 13 3 60 [expiry] [inception] [keytag] test.invalid. ...
diff --git a/modules/lua2backend/regression-tests/basic-a-dnssec/command b/modules/lua2backend/regression-tests/basic-a-dnssec/command
new file mode 100755 (executable)
index 0000000..e941466
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+cleandig www.test.invalid A dnssec
diff --git a/modules/lua2backend/regression-tests/basic-a-dnssec/description b/modules/lua2backend/regression-tests/basic-a-dnssec/description
new file mode 100644 (file)
index 0000000..6cd423e
--- /dev/null
@@ -0,0 +1 @@
+Basic DNSSEC test
diff --git a/modules/lua2backend/regression-tests/basic-a-dnssec/expected_result b/modules/lua2backend/regression-tests/basic-a-dnssec/expected_result
new file mode 100644 (file)
index 0000000..dad8004
--- /dev/null
@@ -0,0 +1,5 @@
+0      www.test.invalid.       IN      A       60      127.0.0.3
+0      www.test.invalid.       IN      RRSIG   60      A 13 3 60 [expiry] [inception] [keytag] test.invalid. ...
+2      .       IN      OPT     32768   
+Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='www.test.invalid.', qtype=A
diff --git a/modules/lua2backend/regression-tests/basic-a-dnssec/skip.nodnssec b/modules/lua2backend/regression-tests/basic-a-dnssec/skip.nodnssec
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/modules/lua2backend/regression-tests/basic-a-resolution/command b/modules/lua2backend/regression-tests/basic-a-resolution/command
new file mode 100755 (executable)
index 0000000..4750e28
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/sh
+cleandig www.test.invalid A
+
diff --git a/modules/lua2backend/regression-tests/basic-a-resolution/description b/modules/lua2backend/regression-tests/basic-a-resolution/description
new file mode 100644 (file)
index 0000000..51497ac
--- /dev/null
@@ -0,0 +1,2 @@
+This test tries to resolve a straight A record that is directly available in
+the database.
diff --git a/modules/lua2backend/regression-tests/basic-a-resolution/expected_result b/modules/lua2backend/regression-tests/basic-a-resolution/expected_result
new file mode 100644 (file)
index 0000000..c6d0549
--- /dev/null
@@ -0,0 +1,3 @@
+0      www.test.invalid.       IN      A       60      127.0.0.3
+Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='www.test.invalid.', qtype=A
diff --git a/modules/lua2backend/regression-tests/basic-aaaa-resolution/command b/modules/lua2backend/regression-tests/basic-aaaa-resolution/command
new file mode 100755 (executable)
index 0000000..e4f2fd8
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/sh
+cleandig www.test.invalid AAAA 
+
diff --git a/modules/lua2backend/regression-tests/basic-aaaa-resolution/description b/modules/lua2backend/regression-tests/basic-aaaa-resolution/description
new file mode 100644 (file)
index 0000000..2bfe713
--- /dev/null
@@ -0,0 +1,2 @@
+This test tries to resolve a straight AAAA record that is directly available in
+the database backend.
diff --git a/modules/lua2backend/regression-tests/basic-aaaa-resolution/expected_result b/modules/lua2backend/regression-tests/basic-aaaa-resolution/expected_result
new file mode 100644 (file)
index 0000000..2e4ec3c
--- /dev/null
@@ -0,0 +1,3 @@
+0      www.test.invalid.       IN      AAAA    60      fe80::3
+Rcode: 0 (No Error), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='www.test.invalid.', qtype=AAAA
diff --git a/modules/lua2backend/regression-tests/lua2-dnssec.lua b/modules/lua2backend/regression-tests/lua2-dnssec.lua
new file mode 100644 (file)
index 0000000..d53c7c0
--- /dev/null
@@ -0,0 +1,166 @@
+dns_dnssec = true
+
+domains = { "test.invalid.", "test.unit." }
+
+records = {}
+
+records["test.invalid."] = {
+  SOA = { "ns1.test.invalid. root.test.invalid. 20180115 1 2 3 4" },
+  NS = { "ns1.test.invalid.", "ns2.test.invalid." },
+  TXT = { "\"this is a test record\" \"in two parts\"" }
+}
+
+records["ns1.test.invalid."] = {
+  A = { "127.0.0.1" },
+  AAAA = { "fe80::1" }
+}
+
+records["ns2.test.invalid."] = {
+  A = { "127.0.0.2" },
+  AAAA = { "fe80::2" }
+}
+
+records["www.test.invalid."] = {
+  A = { "127.0.0.3" },
+  AAAA = { "fe80::3" }
+}
+
+records["shell.test.invalid."] = {
+  A = { "127.0.0.4" },
+  AAAA = { "fe80::4" }
+}
+
+records["_ssh._tcp.service.test.invalid."] = {
+  SRV = { "0 0 22 shell.test.invalid." }
+}
+
+records["test.unit."] = {
+  SOA = { "ns1.test.invalid. root.test.invalid. 20180115 1 2 3 4" },
+  NS = { "ns1.test.invalid.", "ns2.test.invalid." },
+}
+
+function dnsname_compare(a, b)
+   return a:canonCompare(b)
+end
+
+function table_keys(t)
+  local keyset = {}
+  for k,v in pairs(t) do
+    table.insert(keyset, newDN(k))
+  end
+  table.sort(keyset, dnsname_compare)
+  return keyset
+end
+
+function get_domain_id(qname)
+  for id, dom in ipairs(domains) do
+    if qname == newDN(dom) or qname:isPartOf(newDN(dom)) then
+      return id
+    end
+  end
+  return -1
+end
+
+function dns_lookup(qtype, qname, d_id, ctx)
+  ret = {}
+
+  d_id = get_domain_id(qname)
+
+  if d_id == -1 then
+    return {}
+  end
+
+  rr = records[tostring(qname)]
+  if rr ~= nil then
+     if qtype:getName() == "ANY" then
+       for k, v in pairs(rr) do
+         for idx,row in ipairs(v) do
+           table.insert(ret, { name = qname, type = newQType(k), content = row, ttl = 60, domain_id = d_id })
+         end
+       end
+     elseif rr[qtype:getName()] ~= nil then
+       for idx,row in ipairs(rr[qtype:getName()]) do
+         table.insert(ret, { name = qname, type = qtype, content = row, ttl = 60, domain_id = d_id })
+       end
+     end
+  end
+
+  return ret
+end
+
+function dns_list(qname, id)
+  if id == -1 then
+    id = get_domain_id(qname)
+    if id == -1 then
+      return false
+    end
+  end
+  qname = newDN(domains[id])
+
+  ret = {}
+
+  for name,rr in pairs(records) do
+    if newDN(name):isPartOf(qname) then
+      for k, v in pairs(rr) do
+        for idx,row in ipairs(v) do
+          table.insert(ret, { name = newDN(name), type = newQType(k), content = row, ttl = 60, domain_id = d_id })
+        end
+      end
+    end
+  end
+
+  return ret
+end
+
+function dns_get_domaininfo(dom)
+  if dom == newDN("test.invalid") then
+    return { id=1, serial=20180115 }
+  end
+  if dom == newDN("test.unit") then
+    return { id=2, serial=20180115 }
+  end
+
+  return false
+end
+
+function dns_get_domain_metadata(dom)
+  return false
+end
+
+function dns_get_domain_keys(dom)
+  if dom == newDN("test.unit") then
+    return { { flags=257, content="Private-key-format: v1.2\nAlgorithm: 13 (ECDSAP256SHA256)\nPrivateKey: 5CuAdTI5Y4btRkcDr9y2V2SDB7C/yVwRYe2nbbMc2wQ=", active=true, id=1 }, { flags=256, content="Private-key-format: v1.2\nAlgorithm: 13 (ECDSAP256SHA256)\nPrivateKey: ISTmkTAwILggbEbgyQmsaSpWgpBk/SbYWVJfWllH+Fo=", active=true, id=2 } }
+  end
+  if dom == newDN("test.invalid") then
+    return { { flags=257,content="Private-key-format: v1.2\nAlgorithm: 13 (ECDSAP256SHA256)\nPrivateKey: b4vB6HK3QqQ294d5WJOWtlXmXFDjUOHk/JHmOZ/Hf1Q=", active=true, id=3 }, { flags=256, content="Private-key-format: v1.2\nAlgorithm: 13 (ECDSAP256SHA256)\nPrivateKey: isruMlkroefkRHO0B0TOSByRXXwUy5BTfK2rLTGp8BM=", active=true, id=4 } }
+  end
+  return false
+end
+
+function dns_get_before_and_after_names_absolute(did, qname)
+  if did == -1 then
+    did = get_domain_id(qname)
+  end
+  local base = newDN(domains[did])
+  -- find out before and after name
+  local before = newDN("")
+  local after = newDN("")
+  local empty = newDN("")
+
+  for i, rr in ipairs(table_keys(records)) do   
+    if rr:isPartOf(base) then      
+      rr = rr:makeRelative(base)
+      if qname:canonCompare(rr) == false then
+        if before == empty then
+          before = rr
+        end
+      else
+        if after == empty then
+          after = rr
+        end
+      end
+    end
+  end
+
+  return { qname=qname,before=before,after=after }
+end
diff --git a/modules/lua2backend/regression-tests/lua2.lua b/modules/lua2backend/regression-tests/lua2.lua
new file mode 100644 (file)
index 0000000..3dc80aa
--- /dev/null
@@ -0,0 +1,109 @@
+domains = { "test.invalid.", "test.unit." }
+
+records = {}
+
+records["test.invalid."] = {
+  SOA = { "ns1.test.invalid. root.test.invalid. 20180115 1 2 3 4" },
+  NS = { "ns1.test.invalid.", "ns2.test.invalid." },
+  TXT = { "\"this is a test record\" \"in two parts\"" }
+}
+
+records["ns1.test.invalid."] = {
+  A = { "127.0.0.1" },
+  AAAA = { "fe80::1" }
+}
+
+records["ns2.test.invalid."] = {
+  A = { "127.0.0.2" },
+  AAAA = { "fe80::2" }
+}
+
+records["www.test.invalid."] = {
+  A = { "127.0.0.3" },
+  AAAA = { "fe80::3" }
+}
+
+records["shell.test.invalid."] = {
+  A = { "127.0.0.4" },
+  AAAA = { "fe80::4" }
+}
+
+records["_ssh._tcp.service.test.invalid."] = {
+  SRV = { "0 0 22 shell.test.invalid." }
+}
+
+records["test.unit."] = {
+  SOA = { "ns1.test.invalid. root.test.invalid. 20180115 1 2 3 4" },
+  NS = { "ns1.test.invalid.", "ns2.test.invalid." },
+}
+
+function get_domain_id(qname)
+  for id, dom in ipairs(domains) do
+    if qname == newDN(dom) or qname:isPartOf(newDN(dom)) then
+      return id
+    end
+  end
+  return -1
+end
+
+--function dns_get_domaininfo(dom)
+--  if dom == newDN("test.invalid") then
+--    return { id=1, serial=20180115 }
+--  end
+--  if dom == newDN("test.unit") then
+--    return { id=2, serial=20180115 }
+--  end
+--
+--  return false
+--end
+
+function dns_lookup(qtype, qname, d_id, ctx)
+  ret = {}
+
+  d_id = get_domain_id(qname)
+
+  if d_id == -1 then
+    return {}
+  end
+
+  rr = records[tostring(qname)]
+  if rr ~= nil then
+     if qtype:getName() == "ANY" then
+       for k, v in pairs(rr) do
+         for idx,row in ipairs(v) do
+           table.insert(ret, { name = qname, type = newQType(k), content = row, ttl = 60, domain_id = d_id })
+         end
+       end
+     elseif rr[qtype:getName()] ~= nil then
+       for idx,row in ipairs(rr[qtype:getName()]) do
+         table.insert(ret, { name = qname, type = qtype, content = row, ttl = 60, domain_id = d_id })
+       end
+     end
+  end
+
+  return ret
+end
+
+function dns_list(qname, id)
+  if id == -1 then
+    id = get_domain_id(qname)
+    if id == -1 then
+      return false
+    end
+  end
+  qname = newDN(domains[id])
+
+  ret = {}
+
+  for name,rr in pairs(records) do
+    if newDN(name):isPartOf(qname) then
+      for k, v in pairs(rr) do
+        for idx,row in ipairs(v) do
+          table.insert(ret, { name = newDN(name), type = newQType(k), content = row, ttl = 60, domain_id = d_id })
+        end
+      end
+    end
+  end
+
+  return ret
+end
diff --git a/modules/lua2backend/regression-tests/nsec-2-dnssec/command b/modules/lua2backend/regression-tests/nsec-2-dnssec/command
new file mode 100755 (executable)
index 0000000..1b490c8
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+cleandig wap.test.invalid A dnssec
diff --git a/modules/lua2backend/regression-tests/nsec-2-dnssec/description b/modules/lua2backend/regression-tests/nsec-2-dnssec/description
new file mode 100644 (file)
index 0000000..6cd423e
--- /dev/null
@@ -0,0 +1 @@
+Basic DNSSEC test
diff --git a/modules/lua2backend/regression-tests/nsec-2-dnssec/expected_result b/modules/lua2backend/regression-tests/nsec-2-dnssec/expected_result
new file mode 100644 (file)
index 0000000..4be5b12
--- /dev/null
@@ -0,0 +1,9 @@
+1      ns1.test.invalid.       IN      NSEC    4       www.test.invalid. A AAAA RRSIG NSEC
+1      ns1.test.invalid.       IN      RRSIG   4       NSEC 13 3 4 [expiry] [inception] [keytag] test.invalid. ...
+1      test.invalid.   IN      NSEC    4       ns1.test.invalid. NS SOA TXT RRSIG NSEC DNSKEY
+1      test.invalid.   IN      RRSIG   4       NSEC 13 2 4 [expiry] [inception] [keytag] test.invalid. ...
+1      test.invalid.   IN      RRSIG   4       SOA 13 2 60 [expiry] [inception] [keytag] test.invalid. ...
+1      test.invalid.   IN      SOA     4       ns1.test.invalid. root.test.invalid. 20180115 1 2 3 4
+2      .       IN      OPT     32768   
+Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='wap.test.invalid.', qtype=A
diff --git a/modules/lua2backend/regression-tests/nsec-2-dnssec/skip.nodnssec b/modules/lua2backend/regression-tests/nsec-2-dnssec/skip.nodnssec
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/modules/lua2backend/regression-tests/nsec-dnssec/command b/modules/lua2backend/regression-tests/nsec-dnssec/command
new file mode 100755 (executable)
index 0000000..59daa2e
--- /dev/null
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+cleandig middle.test.invalid A dnssec
diff --git a/modules/lua2backend/regression-tests/nsec-dnssec/description b/modules/lua2backend/regression-tests/nsec-dnssec/description
new file mode 100644 (file)
index 0000000..6cd423e
--- /dev/null
@@ -0,0 +1 @@
+Basic DNSSEC test
diff --git a/modules/lua2backend/regression-tests/nsec-dnssec/expected_result b/modules/lua2backend/regression-tests/nsec-dnssec/expected_result
new file mode 100644 (file)
index 0000000..1a93837
--- /dev/null
@@ -0,0 +1,7 @@
+1      test.invalid.   IN      NSEC    4       ns1.test.invalid. NS SOA TXT RRSIG NSEC DNSKEY
+1      test.invalid.   IN      RRSIG   4       NSEC 13 2 4 [expiry] [inception] [keytag] test.invalid. ...
+1      test.invalid.   IN      RRSIG   4       SOA 13 2 60 [expiry] [inception] [keytag] test.invalid. ...
+1      test.invalid.   IN      SOA     4       ns1.test.invalid. root.test.invalid. 20180115 1 2 3 4
+2      .       IN      OPT     32768   
+Rcode: 3 (Non-Existent domain), RD: 0, QR: 1, TC: 0, AA: 1, opcode: 0
+Reply to question for qname='middle.test.invalid.', qtype=A
diff --git a/modules/lua2backend/regression-tests/nsec-dnssec/skip.nodnssec b/modules/lua2backend/regression-tests/nsec-dnssec/skip.nodnssec
new file mode 100644 (file)
index 0000000..e69de29
index 220765dc8ff5cf90728ae89021d66e04575234d7..2da721020c10643a69ee5cded248816dc29eb800 100644 (file)
@@ -59,6 +59,10 @@ start_master ()
                                source ./backends/ldap-master
                                ;;
 
+                       lua2*)
+                               source ./backends/lua2-master
+                               ;;
+
                        ext-nsd*)
                                source ./ext/nsd-master
                                ;;
diff --git a/regression-tests/backends/lua2-master b/regression-tests/backends/lua2-master
new file mode 100644 (file)
index 0000000..bc3f884
--- /dev/null
@@ -0,0 +1,40 @@
+case $context in
+       lua2*)
+               lua2sec=$(echo $context | cut -d- -f 2)
+               testsdir=../modules/lua2backend/regression-tests/
+
+               # cleanup unbound-host.conf to avoid failures
+               rm -f unbound-host.conf
+
+               skipreasons="nodnssec nodyndns"
+
+               luascript="lua2.lua"
+
+               if [ "$lua2sec" = "dnssec" ]
+               then
+                       lua2dosec="yes"
+                       extracontexts="dnssec"
+                       skipreasons="nonsec3 nonarrow nodyndns"
+                       luascript="lua2-dnssec.lua"
+               fi
+
+               # generate pdns.conf for pdnsutil
+               cat > pdns-lua2.conf <<EOF
+module-dir=./modules
+launch=lua2
+lua2-filename=$testsdir/$luascript
+lua2-api=2
+allow-axfr-ips=0.0.0.0/0,::/0
+EOF
+
+               $RUNWRAPPER $PDNS --daemon=no --local-address=$address --local-port=$port --socket-dir=./ \
+                       --no-shuffle --launch=lua2 \
+                       --cache-ttl=$cachettl --dname-processing --no-config \
+                       --distributor-threads=1 \
+                       --allow-axfr-ips=0.0.0.0/0,::/0 \
+                       --lua2-filename=$testsdir/$luascript --lua2-api=2 --module-dir=./modules &
+               ;;
+
+       *)
+               nocontext=yes
+esac
diff --git a/regression-tests/modules/liblua2backend.so b/regression-tests/modules/liblua2backend.so
new file mode 120000 (symlink)
index 0000000..75f28c9
--- /dev/null
@@ -0,0 +1 @@
+../../modules/lua2backend/.libs/liblua2backend.so
\ No newline at end of file
index 15d13d3a341c26dc17f8ff2e917c6221ce287aaa..ea99f91b8819e22ada29c4beed39e457e5e88bfd 100755 (executable)
@@ -189,6 +189,7 @@ remotebackend-pipe-dnssec remotebackend-unix-dnssec remotebackend-http-dnssec re
 #remotebackend-pipe-nsec3-narrow remotebackend-unix-nsec3-narrow remotebackend-http-nsec3-narrow
 tinydns
 ldap-tree ldap-simple ldap-strict
+lua2 lua2-dnssec lua2-nsec3 lua2-nsec3-narrow
 #ext-nsd ext-nsd-nsec ext-nsd-nsec3 ext-bind ext-bind-nsec ext-bind-nsec3
 
 * Add -presigned to any ext-nsd, ext-bind, bind, gmysql or gsqlite3 test (except narrow)