]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
very first RPZ work. Can load RPZ from disk, most policies work. What doesn't: IPv6...
authorbert hubert <bert.hubert@powerdns.com>
Sun, 18 Oct 2015 17:03:29 +0000 (19:03 +0200)
committerbert hubert <bert.hubert@powerdns.com>
Sun, 18 Oct 2015 17:03:29 +0000 (19:03 +0200)
12 files changed:
pdns/Makefile.am
pdns/basic.rpz [new file with mode: 0644]
pdns/filterpo.cc [new file with mode: 0644]
pdns/filterpo.hh [new file with mode: 0644]
pdns/iputils.hh
pdns/pdns_recursor.cc
pdns/reczones.cc
pdns/rpzloader.cc [new file with mode: 0644]
pdns/rpzloader.hh [new file with mode: 0644]
pdns/syncres.cc
pdns/syncres.hh
pdns/zoneparser-tng.hh

index 8203f0419b5e6d4db1827f7b6dc0a7e77f55fc60..d31fe56ad2f65172bf6db9c2a31238fcae92df13 100644 (file)
@@ -1071,6 +1071,7 @@ pdns_recursor_SOURCES = \
        dnsparser.cc \
        dnsrecords.cc dnsrecords.hh \
        dnswriter.cc dnswriter.hh \
+       filterpo.cc filterpo.hh \
        iputils.cc \
        json.cc json.hh \
        logger.cc \
@@ -1094,6 +1095,7 @@ pdns_recursor_SOURCES = \
        reczones.cc \
        resolver.hh \
        responsestats.cc \
+       rpzloader.cc rpzloader.hh \
        secpoll-recursor.cc \
        secpoll-recursor.hh \
        selectmplexer.cc \
diff --git a/pdns/basic.rpz b/pdns/basic.rpz
new file mode 100644 (file)
index 0000000..60f8d5e
--- /dev/null
@@ -0,0 +1,62 @@
+$TTL 2h;
+$ORIGIN domain.example.com.
+@               SOA powerdns.example.net. hostmaster.example.com ( 1 12h 15m 3w 2h)
+                NS powerdns.example.net.  // out-of-zone no A/AAAA RR required
+; begin RPZ RR definitions
+
+;; QNAME Trigger
+
+; QNAME Trigger NXDOMAIN Action
+; kills whole domain
+nxdomain.org        CNAME .
+*.nxdomain-apex.org      CNAME .
+
+; QNAME Trigger PASSTHRU Action
+; typically only used for bypass
+mail.nxdomain-apix.org        CNAME rpz-passthru.
+
+; QNAME Trigger DROP Action
+; kills whole domain
+example.net        CNAME rpz-drop.
+*.example.net      CNAME rpz-drop.
+
+; QNAME Trigger Truncate Action
+; kills whole domain
+truncate.org        CNAME rpz-tcp-only.
+*.truncate-apex.org      CNAME rpz-tcp-only.
+
+; QNAME Trigger Local-Data Action
+; sends to a local website
+; kills whole domain
+local.org        CNAME explanation.example.com.
+*.local.org      CNAME explanation.example.com.
+
+local-a.org        A 192.168.2.5
+*.local-a-apex.org      A 192.168.2.5
+
+; CLIENT-IP Trigger DROP Action
+; kills all DNS activity from this client
+24.0.0.0.127.rpz-client-ip CNAME rpz-drop.
+
+; CLIENT-IP Trigger TCP-ONLY Action
+; slows-up all DNS activity from this client
+32.1.0.0.10.rpz-client-ip CNAME rpz-tcp-only.
+
+; IP Trigger NXDOMAIN Action
+; any answer containing IP range
+32.2.0.0.10.rpz-ip CNAME .
+
+;; NSDNAME Trigger
+;; if ns1.example.org appears in the authority section
+;; of any answer
+
+; NSDNAME Trigger NXDOMAIN Action
+; kills specific name server
+dns-eu1.powerdns.net.rpz-nsdname CNAME .
+; this will kill any name servers from example.org
+*.powerdns.net.rpz-nsdname   CNAME .
+
+; NSDNAME Trigger TCP-ONLY Action
+; kills specific name server
+*.gtld-servers.net.rpz-nsdname CNAME rpz-tcp-only.
+
diff --git a/pdns/filterpo.cc b/pdns/filterpo.cc
new file mode 100644 (file)
index 0000000..771e1ff
--- /dev/null
@@ -0,0 +1,133 @@
+#include "filterpo.hh"
+#include <iostream>
+#include "namespaces.hh"
+#include "dnsrecords.hh"
+
+DNSFilterEngine::DNSFilterEngine()
+{
+}
+
+bool findNamedPolicy(const map<DNSName, DNSFilterEngine::Policy>& polmap, const DNSName& qname, DNSFilterEngine::Policy& pol)
+{
+  DNSName s(qname);
+
+    /* for www.powerdns.com, we need to check:
+         www.powerdns.com.
+           *.powerdns.com.
+             powerdns.com.
+                   *.com.
+                      com.
+                       *.
+                        .       */
+  bool first=true;
+  do {
+    auto iter = polmap.find(s);
+    if(iter != polmap.end()) {
+      pol=iter->second;
+      return true;
+    }
+    if(!first) {
+      iter = polmap.find(DNSName("*")+s);
+      if(iter != polmap.end()) {
+       pol=iter->second;
+       return true;
+      }
+    }
+    first=false;
+  } while(s.chopOff());
+  return false;
+}
+
+DNSFilterEngine::Policy DNSFilterEngine::getProcessingPolicy(const DNSName& qname) const
+{
+  cout<<"Got question for nameserver name "<<qname<<endl;
+  Policy pol = Policy::NoAction;
+  for(const auto& z : d_zones) {
+    if(findNamedPolicy(z.propolName, qname, pol)) {
+      cerr<<"Had a hit on the nameserver used to process the query"<<endl;
+      return pol;
+    }
+  }
+  return pol;
+}  
+
+
+DNSFilterEngine::Policy DNSFilterEngine::getQueryPolicy(const DNSName& qname, const ComboAddress& ca) const
+{
+  cout<<"Got question for "<<qname<<" from "<<ca.toString()<<endl;
+
+  Policy pol = Policy::NoAction;
+  for(const auto& z : d_zones) {
+    if(findNamedPolicy(z.qpolName, qname, pol)) {
+      cerr<<"Had a hit on the name of the query"<<endl;
+      return pol;
+    }
+    
+    for(const auto& qa : z.qpolAddr) {
+      if(qa.first.match(ca)) {
+       cerr<<"Had a hit on the IP address of the client"<<endl;
+       return qa.second;
+      }
+    }
+  }
+
+  return Policy::NoAction;
+}
+
+DNSFilterEngine::Policy DNSFilterEngine::getPostPolicy(const vector<DNSRecord>& records) const
+{
+  ComboAddress ca;
+
+  for(const auto& r : records) {
+    if(r.d_place != DNSRecord::Answer) 
+      continue;
+    if(r.d_type == QType::A) 
+      ca = std::dynamic_pointer_cast<ARecordContent>(r.d_content)->getCA();
+    else if(r.d_type == QType::AAAA) 
+      ca = std::dynamic_pointer_cast<AAAARecordContent>(r.d_content)->getCA();
+    else
+      continue;
+
+    for(const auto& z : d_zones) {
+      for(const auto& qa : z.postpolAddr) {
+       if(qa.first.match(ca)) {
+         cerr<<"Had a hit on IP address in answer"<<endl;
+         return qa.second;
+       }
+      }
+    }
+  }
+  return Policy::NoAction;
+}
+
+void DNSFilterEngine::assureZones(int zone)
+{
+  if((int)d_zones.size() <= zone)
+    d_zones.resize(zone+1);
+
+}
+
+void DNSFilterEngine::addClientTrigger(const Netmask& nm, Policy pol, int zone)
+{
+  assureZones(zone);
+  d_zones[zone].qpolAddr.push_back({nm,pol});
+}
+
+void DNSFilterEngine::addResponseTrigger(const Netmask& nm, Policy pol, int zone)
+{
+  assureZones(zone);
+  d_zones[zone].postpolAddr.push_back({nm,pol});
+}
+
+void DNSFilterEngine::addQNameTrigger(const DNSName& n, Policy pol, int zone)
+{
+  assureZones(zone);
+  d_zones[zone].qpolName[n]=pol;
+}
+
+void DNSFilterEngine::addNSTrigger(const DNSName& n, Policy pol, int zone)
+{
+  assureZones(zone);
+  d_zones[zone].propolName[n]=pol;
+}
diff --git a/pdns/filterpo.hh b/pdns/filterpo.hh
new file mode 100644 (file)
index 0000000..c8ba699
--- /dev/null
@@ -0,0 +1,69 @@
+#pragma once
+#include "iputils.hh"
+#include "dns.hh"
+#include <map>
+
+/* This class implements a filtering policy that is able to fully implement RPZ, but is not bound to it.
+   In other words, it is generic enough to support RPZ, but could get its data from other places.
+
+
+   We know the following actions:
+   
+   No action - just pass it on
+   Drop - drop a query, no response
+   NXDOMAIN - fake up an NXDOMAIN for the query 
+   NODATA - just return no data for this qtype
+   Truncate - set TC bit
+   Modified - "we fake an answer for you"
+
+   These actions can be caused by the following triggers:
+   
+   qname - the query name
+   client-ip - the IP address of the requestor
+   response-ip - an IP address in the response
+   ns-name - the name of a server used in the delegation
+   ns-ip - the IP address of a server used in the delegation
+
+   This means we get several hook points:
+   1) when the query comes in: qname & client-ip
+   2) during processing: ns-name & ns-ip
+   3) after processing: response-ip
+
+   Triggers meanwhile can apply to:
+   Verbatim domain names
+   Wildcard versions (*.domain.com does NOT match domain.com)
+   Netmasks (IPv4 and IPv6)
+
+   Finally, triggers are grouped in different zones. The "first" zone that has a match
+   is consulted. Then within that zone, rules again have precedences. 
+*/
+
+
+class DNSFilterEngine
+{
+public:
+  enum class Policy { NoAction, Drop, NXDOMAIN, NODATA, Truncate};
+
+  DNSFilterEngine();
+  void clear();
+  void clear(int zone);
+  void addClientTrigger(const Netmask& nm, Policy pol, int zone=0);
+  void addQNameTrigger(const DNSName& nm, Policy pol, int zone=0);
+  void addNSTrigger(const DNSName& dn, Policy pol, int zone=0);
+  void addResponseTrigger(const Netmask& nm, Policy pol, int zone=0);
+
+  Policy getQueryPolicy(const DNSName& qname, const ComboAddress& nm) const;
+  Policy getProcessingPolicy(const DNSName& qname) const;
+  Policy getPostPolicy(const vector<DNSRecord>& records) const;
+
+private:
+  void assureZones(int zone);
+  struct Zone {
+    std::map<DNSName, Policy> qpolName;
+    std::vector<pair<Netmask, Policy>> qpolAddr;
+    std::map<DNSName, Policy> propolName;
+    std::vector<pair<Netmask, Policy>> postpolAddr;
+  };
+  vector<Zone> d_zones;
+
+};
index 52e6fe3d8df8f2f4dce89264f5c8e54ff201730a..672a9ff9dba1d9f8964d2720fa3d8b2548f8d97a 100644 (file)
@@ -347,6 +347,11 @@ public:
   {
     return d_network.sin4.sin_family == AF_INET;
   }
+
+  bool operator<(const Netmask& rhs) const 
+  {
+    return tie(d_network, d_bits) < tie(rhs.d_network, rhs.d_bits);
+  }
 private:
   ComboAddress d_network;
   uint32_t d_mask;
index a0632736adfa9248a0cedb06e24e0e9ad090b262..f3c7d9220a1bc10bd7a2c8cbf27a82fb3b60d20f 100644 (file)
@@ -71,6 +71,8 @@
 #include "responsestats.hh"
 #include "secpoll-recursor.hh"
 #include "dnsname.hh"
+#include "filterpo.hh"
+#include "rpzloader.hh"
 #ifndef RECURSOR
 #include "statbag.hh"
 StatBag S;
@@ -91,6 +93,8 @@ __thread addrringbuf_t* t_remotes, *t_servfailremotes, *t_largeanswerremotes;
 __thread boost::circular_buffer<pair<DNSName, uint16_t> >* t_queryring, *t_servfailqueryring;
 __thread shared_ptr<Regex>* t_traceRegex;
 
+DNSFilterEngine g_dfe;
+
 RecursorControlChannel s_rcc; // only active in thread 0
 
 // for communicating with our threads
@@ -608,6 +612,31 @@ void startDoResolve(void *p)
 
     // if there is a RecursorLua active, and it 'took' the query in preResolve, we don't launch beginResolve
 
+    switch(g_dfe.getQueryPolicy(dc->d_mdp.d_qname, dc->d_remote)) {
+    case DNSFilterEngine::Policy::NoAction:
+      break;
+    case DNSFilterEngine::Policy::Drop:
+      g_stats.policyDrops++;
+      delete dc;
+      dc=0;
+      return; 
+    case DNSFilterEngine::Policy::NXDOMAIN:
+      res=RCode::NXDomain;
+      goto haveAnswer;
+
+    case DNSFilterEngine::Policy::NODATA:
+      res=RCode::NoError;
+      goto haveAnswer;
+
+    case DNSFilterEngine::Policy::Truncate:
+      if(!dc->d_tcp) {
+       res=RCode::NoError;     
+       pw.getHeader()->tc=1;
+       goto haveAnswer;
+      }
+      break;
+    }
+
     if(!t_pdl->get() || !(*t_pdl)->preresolve(dc->d_remote, local, dc->d_mdp.d_qname, QType(dc->d_mdp.d_qtype), ret, res, &variableAnswer)) {
       try {
         res = sr.beginResolve(dc->d_mdp.d_qname, QType(dc->d_mdp.d_qtype), dc->d_mdp.d_qclass, ret);
@@ -618,6 +647,34 @@ void startDoResolve(void *p)
         res = RCode::ServFail;
       }
 
+      switch(g_dfe.getPostPolicy(ret)) {
+      case DNSFilterEngine::Policy::NoAction:
+       break;
+      case DNSFilterEngine::Policy::Drop:
+       g_stats.policyDrops++;
+       delete dc;
+       dc=0;
+       return; 
+      case DNSFilterEngine::Policy::NXDOMAIN:
+       ret.clear();
+       res=RCode::NXDomain;
+       goto haveAnswer;
+       
+      case DNSFilterEngine::Policy::NODATA:
+       ret.clear();
+       res=RCode::NoError;
+       goto haveAnswer;
+       
+      case DNSFilterEngine::Policy::Truncate:
+       if(!dc->d_tcp) {
+         ret.clear();
+         res=RCode::NoError;   
+         pw.getHeader()->tc=1;
+         goto haveAnswer;
+       }
+       break;
+      }
+      
       if(t_pdl->get()) {
         if(res == RCode::NoError) {
                auto i=ret.cbegin();
@@ -628,12 +685,13 @@ void startDoResolve(void *p)
                   (*t_pdl)->nodata(dc->d_remote,local, dc->d_mdp.d_qname, QType(dc->d_mdp.d_qtype), ret, res, &variableAnswer);
               }
               else if(res == RCode::NXDomain)
-          (*t_pdl)->nxdomain(dc->d_remote,local, dc->d_mdp.d_qname, QType(dc->d_mdp.d_qtype), ret, res, &variableAnswer);
-
-      (*t_pdl)->postresolve(dc->d_remote,local, dc->d_mdp.d_qname, QType(dc->d_mdp.d_qtype), ret, res, &variableAnswer);
+               (*t_pdl)->nxdomain(dc->d_remote,local, dc->d_mdp.d_qname, QType(dc->d_mdp.d_qtype), ret, res, &variableAnswer);
+       
+       
+       (*t_pdl)->postresolve(dc->d_remote,local, dc->d_mdp.d_qname, QType(dc->d_mdp.d_qtype), ret, res, &variableAnswer);
       }
     }
-
+  haveAnswer:;
     if(res == PolicyDecision::DROP) {
       g_stats.policyDrops++;
       delete dc;
@@ -1929,6 +1987,7 @@ void parseACLs()
 
 int serviceMain(int argc, char*argv[])
 {
+
   L.setName(s_programname);
   L.setLoglevel((Logger::Urgency)(6)); // info and up
 
@@ -2050,6 +2109,8 @@ int serviceMain(int argc, char*argv[])
   if(!s_pidfname.empty())
     unlink(s_pidfname.c_str()); // remove possible old pid file
 
+  loadRPZFiles();
+
   if(::arg().mustDo("daemon")) {
     L<<Logger::Warning<<"Calling daemonize, going to background"<<endl;
     L.toConsole(Logger::Critical);
@@ -2064,6 +2125,8 @@ int serviceMain(int argc, char*argv[])
   g_maxMThreads = ::arg().asNum("max-mthreads");
   checkOrFixFDS();
 
+
+
   int newgid=0;
   if(!::arg()["setgid"].empty())
     newgid=Utility::makeGidNumeric(::arg()["setgid"]);
@@ -2332,6 +2395,8 @@ int main(int argc, char **argv)
     ::arg().set("spoof-nearmiss-max", "If non-zero, assume spoofing after this many near misses")="20";
     ::arg().set("single-socket", "If set, only use a single socket for outgoing queries")="off";
     ::arg().set("auth-zones", "Zones for which we have authoritative data, comma separated domain=file pairs ")="";
+    ::arg().set("rpz-files", "RPZ files to load in order, domain or domain=policy pairs separated by commas")="";
+
     ::arg().set("forward-zones", "Zones for which we forward queries, comma separated domain=ip pairs")="";
     ::arg().set("forward-zones-recurse", "Zones for which we forward queries with recursion bit, comma separated domain=ip pairs")="";
     ::arg().set("forward-zones-file", "File with (+)domain=ip pairs for forwarding")="";
index 08eec27c94172ea073cbc3c4a779fdc503de496b..ca472ca53de1641eac39f050f02110597710ab76 100644 (file)
@@ -29,6 +29,7 @@
 #include "logger.hh"
 #include "dnsrecords.hh"
 #include <boost/foreach.hpp>
+#include "rpzloader.hh"
 
 extern int g_argc;
 extern char** g_argv;
@@ -313,6 +314,16 @@ string reloadAuthAndForwards()
   return "reloading failed, see log\n";
 }
 
+void loadRPZFiles()
+{
+  vector<string> fnames;
+  stringtok(fnames, ::arg()["rpz-files"],",");
+  int count=0;
+  for(const auto& f : fnames) {
+    loadRPZFromFile(f, g_dfe, count++);
+  }
+}
+
 SyncRes::domainmap_t* parseAuthAndForwards()
 {
   TXTRecordContent::report();
diff --git a/pdns/rpzloader.cc b/pdns/rpzloader.cc
new file mode 100644 (file)
index 0000000..088b263
--- /dev/null
@@ -0,0 +1,95 @@
+#include "rpzloader.hh"
+#include "zoneparser-tng.hh"
+#include "dnsparser.hh"
+#include "dnsrecords.hh"
+#include "syncres.hh"
+
+static Netmask makeNetmaskFromRPZ(const DNSName& name)
+{
+  auto parts = name.getRawLabels();
+  if(parts.size() < 5) 
+    throw PDNSException("Invalid IP address in RPZ: "+name.toString());
+  return Netmask(parts[4]+"."+parts[3]+"."+parts[2]+"."+parts[1]+"/"+parts[0]);
+}
+
+int loadRPZFromFile(const std::string& fname, DNSFilterEngine& target, int place)
+{
+  ZoneParserTNG zpt(fname);
+  DNSResourceRecord drr;
+
+  static const DNSName drop("rpz-drop."), truncate("rpz-tcp-only."), noaction("rpz-passthru.");
+
+  static const DNSName rpzClientIP("rpz-client-ip"), rpzIP("rpz-ip"),
+    rpzNSDname("rpz-nsdname"), rpzNSIP("rpz-nsip.");
+    
+                                                          
+  
+  DNSName domain;
+  while(zpt.get(drr)) {
+    DNSFilterEngine::Policy pol=DNSFilterEngine::Policy::NoAction;
+
+    try {
+      if(drr.qtype.getCode() == QType::CNAME && drr.content.empty())
+       drr.content=".";
+      DNSRecord dr(drr);
+      if(dr.d_type == QType::SOA) {
+       domain = dr.d_name;
+       cerr<<"Origin is "<<domain<<endl;
+      }
+      if(dr.d_type == QType::CNAME) {
+       dr.d_name=dr.d_name.makeRelative(domain);
+       auto target=std::dynamic_pointer_cast<CNAMERecordContent>(dr.d_content)->getTarget();
+       if(target.isRoot()) {
+         cerr<<"Wants NXDOMAIN for "<<dr.d_name<<": ";
+         pol = DNSFilterEngine::Policy::NXDOMAIN;
+       } else if(target==DNSName("*")) {
+         cerr<<"Wants NODATA for "<<dr.d_name<<": ";
+         pol = DNSFilterEngine::Policy::NODATA;
+       }
+       else if(target==drop) {
+         cerr<<"Wants DROP for "<<dr.d_name<<": ";
+         pol = DNSFilterEngine::Policy::Drop;
+       }
+       else if(target==truncate) {
+         cerr<<"Wants TRUNCATE for "<<dr.d_name<<": ";
+         pol = DNSFilterEngine::Policy::Truncate;
+       }
+       else if(target==noaction) {
+         cerr<<"Wants NOACTION for "<<dr.d_name<<": ";
+         pol = DNSFilterEngine::Policy::NoAction;
+       }
+       else
+         cerr<<"Wants custom "<<target<<" for "<<dr.d_name<<": ";
+
+       if(dr.d_name.isPartOf(rpzNSDname)) {
+         DNSName filt=dr.d_name.makeRelative(rpzNSDname);
+         cerr<<"Should apply '"<<filt<<"' to nameserver policy"<<endl;
+         g_dfe.addNSTrigger(filt, pol);
+       } else  if(dr.d_name.isPartOf(rpzClientIP)) {
+         cerr<<"Should apply to client IP policy"<<endl;
+         auto nm=makeNetmaskFromRPZ(dr.d_name);
+         cout<<"Parsed as "<<nm.toString()<<endl;
+         g_dfe.addClientTrigger(nm, pol);
+
+       } else  if(dr.d_name.isPartOf(rpzIP)) {
+         cerr<<"Should apply answer content IP policy: "<<dr.d_name<<endl;
+         auto nm=makeNetmaskFromRPZ(dr.d_name);
+         cout<<"Parsed as "<<nm.toString()<<endl;
+         g_dfe.addResponseTrigger(nm, pol);
+       } else  if(dr.d_name.isPartOf(rpzNSIP)) {
+         cerr<<"Should apply to nameserver IP address policy"<<endl;
+       } else {
+         cerr<<"Should apply to query names"<<endl;
+         g_dfe.addQNameTrigger(dr.d_name, pol);
+       }
+
+      }
+    }
+    catch(PDNSException& pe) {
+      cerr<<"Issue parsing '"<<drr.qname<<"' '"<<drr.content<<"' at "<<zpt.getLineOfFile()<<endl;
+      cerr<<pe.reason<<endl;
+    }
+  }
+  
+  return place;
+}
diff --git a/pdns/rpzloader.hh b/pdns/rpzloader.hh
new file mode 100644 (file)
index 0000000..ac72e25
--- /dev/null
@@ -0,0 +1,5 @@
+#pragma once
+#include "filterpo.hh"
+#include <string>
+
+int loadRPZFromFile(const std::string& fname, DNSFilterEngine& target, int place);
index e967d76bdccd77c285ed537dec86c3c017d6849d..df6c66fa78bc58bc34d6d8b9ca6bd2bedb516672 100644 (file)
@@ -919,6 +919,9 @@ int SyncRes::doResolveAt(set<DNSName> nameservers, DNSName auth, bool flawedNSSe
         LOG(prefix<<qname.toString()<<": Trying to resolve NS '"<<tns->toString()<< "' ("<<1+tns-rnameservers.begin()<<"/"<<(unsigned int)rnameservers.size()<<")"<<endl);
        ;
 
+       if(g_dfe.getProcessingPolicy(*tns) != DNSFilterEngine::Policy::NoAction)
+         throw ImmediateServFailException("Dropped because of policy");
+
         if(!isCanonical(*tns)) {
           LOG(prefix<<qname.toString()<<": Domain has hardcoded nameserver(s)"<<endl);
 
index 3af2c9086c002f10f7ee9e2924db8356540cee34..99a10256c3ccb0ec59270a1d3d534efd6a1bd21f 100644 (file)
@@ -22,6 +22,9 @@
 #include <boost/tuple/tuple_comparison.hpp>
 #include "mtasker.hh"
 #include "iputils.hh"
+#include "filterpo.hh"
+
+extern DNSFilterEngine g_dfe;
 
 void primeHints(void);
 class RecursorLua;
@@ -637,6 +640,7 @@ int directResolve(const DNSName& qname, const QType& qtype, int qclass, vector<D
 template<class T> T broadcastAccFunction(const boost::function<T*()>& func, bool skipSelf=false);
 
 SyncRes::domainmap_t* parseAuthAndForwards();
+void loadRPZFiles();
 
 uint64_t* pleaseGetNsSpeedsSize();
 uint64_t* pleaseGetCacheSize();
index 0691634259528fae724586f8792e3b1b75a2f185..0631516765bfee6f887a20edd6699335e50e469c 100644 (file)
@@ -39,12 +39,13 @@ public:
   bool get(DNSResourceRecord& rr, std::string* comment=0);
   typedef runtime_error exception;
   typedef deque<pair<string::size_type, string::size_type> > parts_t;
+  string getLineOfFile();
 private:
   bool getLine();
   bool getTemplateLine();
   void stackFile(const std::string& fname);
   unsigned makeTTLFromZone(const std::string& str);
-  string getLineOfFile();
+
   struct filestate {
     filestate(FILE* fp, string filename) : d_fp(fp), d_filename(filename), d_lineno(0){}
     FILE *d_fp;