From 69389510ade011268c8e5e06ed8ffa27c99daf63 Mon Sep 17 00:00:00 2001 From: Remi Gacogne Date: Thu, 3 Nov 2016 12:40:25 +0100 Subject: [PATCH] dnsdist: Add `includeDirectory(dir)` --- pdns/README-dnsdist.md | 1 + pdns/dnsdist-lua2.cc | 69 +++++++++++++++++++ .../test-include-dir/test.conf | 1 + regression-tests.dnsdist/test_Advanced.py | 49 +++++++++++++ 4 files changed, 120 insertions(+) create mode 100644 regression-tests.dnsdist/test-include-dir/test.conf diff --git a/pdns/README-dnsdist.md b/pdns/README-dnsdist.md index 43ae7bbd67..960c905312 100644 --- a/pdns/README-dnsdist.md +++ b/pdns/README-dnsdist.md @@ -1223,6 +1223,7 @@ Here are all functions: * `shutdown()`: shut down `dnsdist` * quit or ^D: exit the console * `webserver(address:port, password [, apiKey [, customHeaders ]])`: launch a webserver with stats on that address with that password + * `includeDirectory(dir)`: all files ending in `.conf` in the directory `dir` are loaded into the configuration * ACL related: * `addACL(netmask)`: add to the ACL set who can use this server * `setACL({netmask, netmask})`: replace the ACL set with these netmasks. Use `setACL({})` to reset the list, meaning no one can use us diff --git a/pdns/dnsdist-lua2.cc b/pdns/dnsdist-lua2.cc index f578774e80..099fbf5033 100644 --- a/pdns/dnsdist-lua2.cc +++ b/pdns/dnsdist-lua2.cc @@ -32,8 +32,13 @@ #include #include #include "statnode.hh" +#include +#include +#include +#include boost::tribool g_noLuaSideEffect; +static bool g_included{false}; /* this is a best effort way to prevent logging calls with no side-effects in the output of delta() Functions can declare setLuaNoSideEffect() and if nothing else does declare a side effect, or nothing @@ -1034,4 +1039,68 @@ void moreLua(bool client) } return res; }); + + g_lua.writeFunction("includeDirectory", [](const std::string& dirname) { + if (g_configurationDone) { + errlog("includeDirectory() cannot be used at runtime!"); + g_outputBuffer="includeDirectory() cannot be used at runtime!\n"; + return; + } + + if (g_included) { + errlog("includeDirectory() cannot be used recursively!"); + g_outputBuffer="includeDirectory() cannot be used recursively!\n"; + return; + } + + g_included = true; + struct stat st; + + if (stat(dirname.c_str(), &st)) { + errlog("The included directory %s does not exist!", dirname.c_str()); + g_outputBuffer="The included directory " + dirname + " does not exist!"; + return; + } + + if (!S_ISDIR(st.st_mode)) { + errlog("The included directory %s is not a directory!", dirname.c_str()); + g_outputBuffer="The included directory " + dirname + " is not a directory!"; + return; + } + + DIR *dirp; + struct dirent *ent; + if (!(dirp = opendir(dirname.c_str()))) { + errlog("Error opening the included directory %s!", dirname.c_str()); + g_outputBuffer="Error opening the included directory " + dirname + "!"; + return; + } + + while((ent = readdir(dirp)) != NULL) { + if (ent->d_name[0] == '.') { + continue; + } + + if (boost::ends_with(ent->d_name, ".conf")) { + std::ostringstream namebuf; + namebuf << dirname.c_str() << "/" << ent->d_name; + + if (stat(namebuf.str().c_str(), &st) || !S_ISREG(st.st_mode)) { + continue; + } + + std::ifstream ifs(namebuf.str()); + if (!ifs) { + warnlog("Unable to read configuration from '%s'", namebuf.str()); + } else { + vinfolog("Read configuration from '%s'", namebuf.str()); + } + + g_lua.executeCode(ifs); + } + } + closedir(dirp); + + g_included = false; + }); } diff --git a/regression-tests.dnsdist/test-include-dir/test.conf b/regression-tests.dnsdist/test-include-dir/test.conf new file mode 100644 index 0000000000..b0926f6766 --- /dev/null +++ b/regression-tests.dnsdist/test-include-dir/test.conf @@ -0,0 +1 @@ +addAction(makeRule("includedir.advanced.tests.powerdns.com."), AllowAction()) diff --git a/regression-tests.dnsdist/test_Advanced.py b/regression-tests.dnsdist/test_Advanced.py index d98392fd73..8f4b77906c 100644 --- a/regression-tests.dnsdist/test_Advanced.py +++ b/regression-tests.dnsdist/test_Advanced.py @@ -1136,3 +1136,52 @@ class TestAdvancedWireLengthRule(DNSDistTest): (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False) self.assertEquals(receivedResponse, expectedResponse) + +class TestAdvancedIncludeDir(DNSDistTest): + + _config_template = """ + -- this directory contains a file allowing includedir.advanced.tests.powerdns.com. + includeDirectory('test-include-dir') + addAction(AllRule(), RCodeAction(dnsdist.REFUSED)) + newServer{address="127.0.0.1:%s"} + """ + + def testAdvancedIncludeDirAllowed(self): + """ + Advanced: includeDirectory() + """ + name = 'includedir.advanced.tests.powerdns.com.' + query = dns.message.make_query(name, 'A', 'IN') + response = dns.message.make_response(query) + rrset = dns.rrset.from_text(name, + 3600, + dns.rdataclass.IN, + dns.rdatatype.A, + '192.0.2.1') + response.answer.append(rrset) + + (receivedQuery, receivedResponse) = self.sendUDPQuery(query, response) + self.assertTrue(receivedQuery) + self.assertTrue(receivedResponse) + receivedQuery.id = query.id + self.assertEquals(query, receivedQuery) + self.assertEquals(response, receivedResponse) + + (receivedQuery, receivedResponse) = self.sendTCPQuery(query, response) + self.assertTrue(receivedQuery) + self.assertTrue(receivedResponse) + receivedQuery.id = query.id + self.assertEquals(query, receivedQuery) + self.assertEquals(response, receivedResponse) + + # this one should be refused + name = 'notincludedir.advanced.tests.powerdns.com.' + query = dns.message.make_query(name, 'A', 'IN') + expectedResponse = dns.message.make_response(query) + expectedResponse.set_rcode(dns.rcode.REFUSED) + + (_, receivedResponse) = self.sendUDPQuery(query, response=None, useQueue=False) + self.assertEquals(receivedResponse, expectedResponse) + + (_, receivedResponse) = self.sendTCPQuery(query, response=None, useQueue=False) + self.assertEquals(receivedResponse, expectedResponse) -- 2.47.2