From: Bert Hubert Date: Sun, 9 Mar 2003 15:39:18 +0000 (+0000) Subject: new bindbackend X-Git-Tag: pdns-2.9.7~5 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=b532a4b122c0d46aedfbc30876e4c47aca4f856b;p=pdns new bindbackend git-svn-id: svn://svn.powerdns.com/pdns/trunk/pdns@158 d19b8d6e-7fed-0310-83ef-9ca221ded41b --- diff --git a/pdns/backends/bind/bindbackend2.cc b/pdns/backends/bind/bindbackend2.cc new file mode 100644 index 000000000..f30687b04 --- /dev/null +++ b/pdns/backends/bind/bindbackend2.cc @@ -0,0 +1,795 @@ +/* + PowerDNS Versatile Database Driven Nameserver + Copyright (C) 2002-2003 PowerDNS.COM BV + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License version 2 as + published by the Free Software Foundation; + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ +// $Id: bindbackend2.cc,v 1.1 2003/03/09 15:39:18 ahu Exp $ +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +#include "dns.hh" +#include "dnsbackend.hh" +#include "bindbackend2.hh" +#include "dnspacket.hh" + +#include "zoneparser.hh" +#include "bindparser.hh" +#include "logger.hh" +#include "arguments.hh" +#include "huffman.hh" +#include "qtype.hh" +#include "misc.hh" +#include "dynlistener.hh" +#include "lock.hh" +using namespace std; + +/** new scheme of things: + we have zone-id map + a zone-id has a vector of DNSResourceRecords */ + +map Bind2Backend::s_name_id_map; +map Bind2Backend::s_id_zone_map; +int Bind2Backend::s_first=1; +pthread_mutex_t Bind2Backend::s_startup_lock=PTHREAD_MUTEX_INITIALIZER; + +/* when a query comes in, we find the most appropriate zone and answer from that */ + +BB2DomainInfo::BB2DomainInfo() +{ + d_loaded=false; + d_last_check=0; + d_checknow=false; + d_rwlock=new pthread_rwlock_t; + d_status="Seen in bind configuration"; + d_confcount=0; + // cout<<"Generated a new bbdomaininfo: "<<(void*)d_rwlock<<"/"<d_lastnotified=serial; +} + +void Bind2Backend::setFresh(u_int32_t domain_id) +{ + s_id_zone_map[domain_id]->d_last_check=time(0); +} + +bool Bind2Backend::startTransaction(const string &qname, int id) +{ + + BB2DomainInfo &bbd=*s_id_zone_map[d_transaction_id=id]; + d_transaction_tmpname=bbd.d_filename+"."+itoa(random()); + d_of=new ofstream(d_transaction_tmpname.c_str()); + if(!*d_of) { + throw DBException("Unable to open temporary zonefile '"+d_transaction_tmpname+"': "+stringerror()); + unlink(d_transaction_tmpname.c_str()); + delete d_of; + d_of=0; + } + + *d_of<<"; Written by PowerDNS, don't edit!"<d_filename.c_str())<0) + throw DBException("Unable to commit (rename to: '"+s_id_zone_map[d_transaction_id]->d_filename+"') AXFRed zone: "+stringerror()); + + queueReload(s_id_zone_map[d_transaction_id]); + s_id_zone_map[d_transaction_id]->unlock(); + d_transaction_id=0; + + return true; +} + +bool Bind2Backend::abortTransaction() +{ + if(d_transaction_id) { + s_id_zone_map[d_transaction_id]->unlock(); + delete d_of; + d_of=0; + unlink(d_transaction_tmpname.c_str()); + d_transaction_id=0; + } + + return true; +} + +bool Bind2Backend::feedRecord(const DNSResourceRecord &r) +{ + string qname=r.qname; + string domain=s_id_zone_map[d_transaction_id]->d_name; + + if(!stripDomainSuffix(&qname,domain)) + throw DBException("out-of-zone data '"+qname+"' during AXFR of zone '"+domain+"'"); + + string content=r.content; + + // SOA needs stripping too! XXX FIXME + switch(r.qtype.getCode()) { + case QType::TXT: + *d_of< *changedDomains) +{ + SOAData soadata; + for(map::iterator i=s_id_zone_map.begin();i!=s_id_zone_map.end();++i) { + if(!i->second->d_master.empty()) + continue; + soadata.serial=0; + try { + getSOA(i->second->d_name,soadata); // we might not *have* a SOA yet + } + catch(...){} + DomainInfo di; + di.id=i->first; + di.serial=soadata.serial; + di.zone=i->second->d_name; + di.last_check=i->second->d_last_check; + di.backend=this; + di.kind=DomainInfo::Master; + if(!i->second->d_lastnotified) // don't do notification storm on startup + i->second->d_lastnotified=soadata.serial; + else + if(soadata.serial!=i->second->d_lastnotified) + changedDomains->push_back(di); + + } +} + +void Bind2Backend::getUnfreshSlaveInfos(vector *unfreshDomains) +{ + for(map::const_iterator i=s_id_zone_map.begin();i!=s_id_zone_map.end();++i) { + if(i->second->d_master.empty()) + continue; + DomainInfo sd; + sd.id=i->first; + sd.zone=i->second->d_name; + sd.master=i->second->d_master; + sd.last_check=i->second->d_last_check; + sd.backend=this; + sd.kind=DomainInfo::Slave; + SOAData soadata; + soadata.serial=0; + soadata.refresh=0; + soadata.serial=0; + soadata.db=(DNSBackend *)-1; // not sure if this is useful, inhibits any caches that might be around + try { + getSOA(i->second->d_name,soadata); // we might not *have* a SOA yet + } + catch(...){} + sd.serial=soadata.serial; + if(sd.last_check+soadata.refresh<(unsigned int)time(0)) + unfreshDomains->push_back(sd); + } +} + +bool Bind2Backend::getDomainInfo(const string &domain, DomainInfo &di) +{ + for(map::const_iterator i=s_id_zone_map.begin();i!=s_id_zone_map.end();++i) { + if(i->second->d_name==domain) { + di.id=i->first; + di.zone=domain; + di.master=i->second->d_master; + di.last_check=i->second->d_last_check; + di.backend=this; + di.kind=i->second->d_master.empty() ? DomainInfo::Master : DomainInfo::Slave; + di.serial=0; + try { + SOAData sd; + sd.serial=0; + + getSOA(i->second->d_name,sd); // we might not *have* a SOA yet + di.serial=sd.serial; + } + catch(...){} + + return true; + } + } + return false; +} + + +static string canonic(string ret) +{ + string::iterator i; + + for(i=ret.begin(); + i!=ret.end(); + ++i) + ; // *i=*i; //tolower(*i); + + + if(*(i-1)=='.') + ret.resize(i-ret.begin()-1); + return ret; +} + +map nbbds; +/** This function adds a record to a domain with a certain id. */ +void Bind2Backend::insert(int id, const string &qnameu, const string &qtype, const string &content, int ttl=300, int prio=25) +{ + static int s_count; + DLOG( + if(!((s_count++)%10000)) + cerr<<"\r"<d_records->push_back(rr); +} + + +static Bind2Backend *us; + +void Bind2Backend::reload() +{ + for(map::iterator i=us->s_id_zone_map.begin();i!=us->s_id_zone_map.end();++i) + i->second->d_checknow=true; +} + +string Bind2Backend::DLReloadNowHandler(const vector&parts, Utility::pid_t ppid) +{ + ostringstream ret; + bool doReload=false; + for(map::iterator j=us->s_id_zone_map.begin();j!=us->s_id_zone_map.end();++j) { + doReload=false; + if(parts.size()==1) + doReload=true; + else + for(vector::const_iterator i=parts.begin()+1;isecond->d_name) { + doReload=true; + break; + } + + if(doReload) { + j->second->lock(); + + us->queueReload(j->second); + j->second->unlock(); + ret<second->d_name<< (j->second->d_loaded ? "": "[rejected]") <<"\t"<second->d_status<<"\n"; + } + doReload=false; + } + + return ret.str(); +} + + +string Bind2Backend::DLDomStatusHandler(const vector&parts, Utility::pid_t ppid) +{ + string ret; + bool doPrint=false; + for(map::iterator j=us->s_id_zone_map.begin();j!=us->s_id_zone_map.end();++j) { + ostringstream line; + doPrint=false; + if(parts.size()==1) + doPrint=true; + else + for(vector::const_iterator i=parts.begin()+1;isecond->d_name) { + doPrint=true; + break; + } + + if(doPrint) + line<second->d_name<< (j->second->d_loaded ? "": "[rejected]") <<"\t"<second->d_status<<"\n"; + doPrint=false; + ret+=line.str(); + } + + return ret; +} + + +string Bind2Backend::DLListRejectsHandler(const vector&parts, Utility::pid_t ppid) +{ + ostringstream ret; + for(map::iterator j=us->s_id_zone_map.begin();j!=us->s_id_zone_map.end();++j) + if(!j->second->d_loaded) + ret<second->d_name<<"\t"<second->d_status<insert(domain_id,domain,qtype,content,ttl,prio); +} + + + +Bind2Backend::Bind2Backend(const string &suffix) +{ +#if __GNUC__ >= 3 + ios_base::sync_with_stdio(false); +#endif + d_logprefix="[bind2"+suffix+"backend]"; + setArgPrefix("bind2"+suffix); + Lock l(&s_startup_lock); + + d_transaction_id=0; + if(!s_first) { + return; + } + + s_first=0; + + if(mustDo("example-zones")) { + insert(0,"www.example.com","A","1.2.3.4"); + insert(0,"example.com","SOA","ns1.example.com hostmaster.example.com"); + insert(0,"example.com","NS","ns1.example.com",86400); + insert(0,"example.com","NS","ns2.example.com",86400); + insert(0,"example.com","MX","mail.example.com",3600,25); + insert(0,"example.com","MX","mail1.example.com",3600,25); + insert(0,"mail.example.com","A","4.3.2.1"); + insert(0,"mail1.example.com","A","5.4.3.2"); + insert(0,"ns1.example.com","A","4.3.2.1"); + insert(0,"ns2.example.com","A","5.4.3.2"); + + for(int i=0;i<1000;i++) + insert(0,"host-"+itoa(i)+".example.com","A","2.3.4.5"); + + s_id_zone_map[0]=new BB2DomainInfo; + BB2DomainInfo &bbd=*s_id_zone_map[0]; + bbd.d_name="example.com"; + bbd.d_filename=""; + bbd.d_id=0; + s_id_zone_map[0]->d_loaded=true; + s_id_zone_map[0]->d_status="parsed into memory at "+nowTime(); + } + + loadConfig(); + + + extern DynListener *dl; + us=this; + dl->registerFunc("BIND2-RELOAD-NOW", &DLReloadNowHandler); + dl->registerFunc("BIND2-DOMAIN-STATUS", &DLDomStatusHandler); + dl->registerFunc("BIND2-LIST-REJECTS", &DLListRejectsHandler); +} + + +void Bind2Backend::rediscover(string *status) +{ + loadConfig(status); +} + +void Bind2Backend::loadConfig(string* status) +{ + static int domain_id=1; + nbbds.clear(); + if(!getArg("config").empty()) { + BindParser BP; + try { + BP.parse(getArg("config")); + } + catch(AhuException &ae) { + L< domains=BP.getDomains(); + + us=this; + + ZP.setDirectory(BP.getDirectory()); + ZP.setCallback(&callback); + L<::const_iterator i=domains.begin(); + i!=domains.end(); + ++i) + { + BB2DomainInfo *bbd=0; + if(i->type!="master" && i->type!="slave") { + L<type<<"' zone '"<name<<"'"<::const_iterator j=s_id_zone_map.begin(); + for(;j!=s_id_zone_map.end();++j) + if(j->second->d_name==i->name) { + bbd=j->second; + break; + } + if(j==s_id_zone_map.end()) { // entirely new + bbd=new BB2DomainInfo; + bbd->d_records=new vector; + + bbd->d_id=domain_id++; + s_name_id_map[i->name]=bbd->d_id; + bbd->setCheckInterval(getArgAsNum("check-interval")); + bbd->d_lastnotified=0; + bbd->d_loaded=false; + } + + bbd->d_name=i->name; + bbd->d_filename=i->filename; + bbd->d_master=i->master; + + nbbds[bbd->d_id]=bbd; + if(!bbd->d_loaded) { + L<name<<"' from file '"<filename<<"'"<filename,i->name,bbd->d_id); // calls callback for us + nbbds[bbd->d_id]->setCtime(); + nbbds[bbd->d_id]->d_loaded=true; // does this perform locking for us? + nbbds[bbd->d_id]->d_status="parsed into memory at "+nowTime(); + + } + catch(AhuException &ae) { + ostringstream msg; + msg<<" error at "+nowTime()+" parsing '"<name<<"' from file '"<filename<<"': "<d_id]->d_status=msg.str(); + L< *>&tmp=d_zone_id_map[bbd.d_id]; // shrink trick + vector *>(tmp).swap(tmp); + */ + } + + + int remdomains=0; + set oldnames, newnames; + for(map::const_iterator j=s_id_zone_map.begin();j!=s_id_zone_map.end();++j) { + oldnames.insert(j->second->d_name); + } + for(map::const_iterator j=nbbds.begin();j!=nbbds.end();++j) { + newnames.insert(j->second->d_name); + } + + vector diff; + set_difference(oldnames.begin(), oldnames.end(), newnames.begin(), newnames.end(), back_inserter(diff)); + remdomains=diff.size(); + for(vector::const_iterator k=diff.begin();k!=diff.end();++k) { + delete s_id_zone_map[s_name_id_map[*k]]->d_records; + delete s_id_zone_map[s_name_id_map[*k]]; + s_name_id_map.erase(*k); + L<::iterator j=s_id_zone_map.begin();j!=s_id_zone_map.end();++j) { // O(N*M) + for(vector::const_iterator k=diff.begin();k!=diff.end();++k) + if(j->second->d_name==*k) { + L<second->d_name<<"' from memory"<second->lock(); + j->second->d_loaded=false; + nukeZoneRecords(j->second); + j->second->unlock(); + break; + } + } + + vector diff2; + set_difference(newnames.begin(), newnames.end(), oldnames.begin(), oldnames.end(), back_inserter(diff2)); + newdomains=diff2.size(); + + s_id_zone_map.swap(nbbds); // commit + ostringstream msg; + msg<<" Done parsing domains, "<d_loaded=0; // block further access + bbd->d_records->clear(); +} + +/** Must be called with bbd locked already. Will not be unlocked on return, is your own problem. + Does not throw errors or anything, may update d_status however */ + + +void Bind2Backend::queueReload(BB2DomainInfo *bbd) +{ + // we reload *now* for the time being + //cout<<"unlock domain"<unlock(); + //cout<<"lock it again"<lock(); + try { + nukeZoneRecords(bbd); + + ZoneParser ZP; + us=this; + ZP.setCallback(&callback); + ZP.parse(bbd->d_filename,bbd->d_name,bbd->d_id); + bbd->setCtime(); + // and raise d_loaded again! + bbd->d_loaded=1; + bbd->d_checknow=0; + bbd->d_status="parsed into memory at "+nowTime(); + L<d_name<<"' ("<d_filename<<") reloaded"<d_name<<"' from file '"<d_filename<<"': "<d_status=msg.str(); + } +} + +void Bind2Backend::lookup(const QType &qtype,const string &qname, DNSPacket *pkt_p, int zoneId ) +{ + d_handle=new Bind2Backend::handle; + d_handle->d_records=new vector; // WRONG + // cout<<"Lookup! for "<d_iter=d_handle->d_records->end(); // WRONG + d_handle->d_list=false; + d_handle->d_bbd=0; + return; + } + unsigned int id=s_name_id_map[domain]; + + // cout<<"domain: '"<qname=qname; + d_handle->parent=this; + d_handle->qtype=qtype; + string compressed=toLower(qname); + d_handle->d_records=s_id_zone_map[id]->d_records; + d_handle->d_bbd=0; + if(!d_handle->d_records->empty()) { + BB2DomainInfo& bbd=*s_id_zone_map[id]; + if(!bbd.d_loaded) { + delete d_handle; + throw DBException("Zone temporarily not available (file missing, or master dead)"); // fuck + } + + if(!bbd.tryRLock()) { + L<d_bbd=&bbd; + } + else { + DLOG(L<<"Query with no results"<d_iter=d_handle->d_records->begin(); + d_handle->d_list=false; +} + +Bind2Backend::handle::handle() +{ + d_bbd=0; + count=0; +} + +bool Bind2Backend::get(DNSResourceRecord &r) +{ + if(!d_handle->get(r)) { + delete d_handle; + d_handle=0; + return false; + } + return true; +} + +bool Bind2Backend::handle::get(DNSResourceRecord &r) +{ + if(d_list) + return get_list(r); + else + return get_normal(r); +} + +bool Bind2Backend::handle::get_normal(DNSResourceRecord &r) +{ + DLOG(L << "Bind2Backend get() was called for "<size()<<" available!"<end() && (d_iter->qname!=qname || !(qtype=="ANY" || (d_iter)->qtype==qtype))) { + DLOG(L<qtype).getName()<<": '"<content<<"'"<end()) { // we've reached the end + if(d_bbd) { + d_bbd->unlock(); + d_bbd=0; + } + return false; + } + DLOG(L << "Bind2Backend get() returning a rr with a "<qtype).getCode()<content; + r.domain_id=(d_iter)->domain_id; + r.qtype=(d_iter)->qtype; + r.ttl=(d_iter)->ttl; + r.priority=(d_iter)->priority; + d_iter++; + + return true; +} + +bool Bind2Backend::list(int id) +{ + cout<<"List of id "<d_qname_iter=s_id_zone_map[id]->d_records->begin(); + d_handle->d_qname_end=s_id_zone_map[id]->d_records->end(); // iter now points to a vector of pointers to vector + + d_handle->parent=this; + d_handle->id=id; + d_handle->d_list=true; + return true; + +} + +bool Bind2Backend::handle::get_list(DNSResourceRecord &r) +{ + if(d_qname_iter!=d_qname_end) { + r=*d_qname_iter; + d_qname_iter++; + return true; + } + return false; + +} + +bool Bind2Backend::isMaster(const string &name, const string &ip) +{ + for(map::iterator j=us->s_id_zone_map.begin();j!=us->s_id_zone_map.end();++j) + if(j->second->d_name==name) + return j->second->d_master==ip; + return false; +} + +class Bind2Factory : public BackendFactory +{ + public: + Bind2Factory() : BackendFactory("bind2") {} + + void declareArguments(const string &suffix="") + { + declare(suffix,"config","Location of named.conf",""); + declare(suffix,"example-zones","Install example zones","no"); + declare(suffix,"check-interval","Interval for zonefile changes","0"); + } + + DNSBackend *make(const string &suffix="") + { + return new Bind2Backend(suffix); + } +}; + +//! Magic class that is activated when the dynamic library is loaded +class Bind2Loader +{ +public: + Bind2Loader() + { + BackendMakers().report(new Bind2Factory); + L< +#include +#include +#include +#include +#include + +#include "huffman.hh" + +#if __GNUC__ >= 3 +# include +using namespace __gnu_cxx; +#else +# include +#endif + + +using namespace std; + +class BB2DomainInfo +{ +public: + BB2DomainInfo(); + + void setCtime(); + + bool current(); + + bool d_loaded; + string d_status; + bool d_checknow; + time_t d_ctime; + string d_name; + string d_filename; + unsigned int d_id; + time_t d_last_check; + string d_master; + int d_confcount; + u_int32_t d_lastnotified; + + bool tryRLock() + { + // cout<<"[trylock!] "<<(void*)d_rwlock<<"/"<* d_records; +private: + time_t getCtime(); + time_t d_checkinterval; + time_t d_lastcheck; + pthread_rwlock_t *d_rwlock; +}; + + + +class BBResourceRecord +{ +public: + bool operator==(const BBResourceRecord &o) const + { + return (o.domain_id==domain_id && o.qtype==qtype && o.content==content && + o.ttl==ttl && o.priority==priority); + } + + const string *qnameptr; // 4 + unsigned int domain_id; // 4 + unsigned short int qtype; // 2 + unsigned short int priority; // 2 + const string *content; // 4 + unsigned int ttl; // 4 + +}; + +struct compare_string +{ + bool operator()(const string& s1, const string& s2) const + { + return s1 == s2; + } +}; + +struct hash_string +{ + size_t operator()(const string& s) const + { + return __stl_hash_string(s.c_str()); + } +}; + +typedef hash_map, hash_string, compare_string> cmap_t; + + + +/** The Bind2Backend is a DNSBackend that can answer DNS related questions. It looks up data + in a Bind-style zone file + + How this all works is quite complex and prone to change. There are a number of containers involved which, + together, contain everything we need to know about a domain or a record. + + A domain consists of records. So, 'example.com' has 'www.example.com' as a record. + + All record names are stored in the hash_map d_qnames, with their name as index. Attached to that index + is a vector of BBResourceRecords ('Bind2Backend') belonging to that qname. Each record contains a d_domainid, + which is the ID of the domain it belongs to. + + Then there is the map called d_bbds which has as its key the Domain ID, and attached a BB2DomainInfo object, which + tells us domain metadata (place on disk, if it is a master or a slave etc). + + To allow for AXFRs, there is yet another container, the d_zone_id_map, which contains per domain_id a vector + of pointers to vectors of BBResourceRecords. When read in sequence, these deliver all records of a domain_id. + + As there is huge repitition in the right hand side of records, many records point to the same thing (IP address, nameserver), + a list of these is kept in s_contents, and each BBResourceRecord only contains a pointer to a record in s_contents. + + So, summarizing: + + class BBResourceRecord: + Everything you need to know about a record. In this context we call the name of a BBResourceRecord 'qname' + + class BB2DomainInfo: + Domain metadata, like location on disk, last time zone was checked + + d_qnames >: + If you know the qname of a record, this gives you all records under that name. + + sets_contents: + Set of all 'contents' of records, the right hand sides. + + map* > > d_zone_id_map: + If you know the zone_id, this has a vector of pointers to vectors in d_qnames, for AXFR + + mapd_bbds: + Map of all domains we know about and metadata about them. + + +*/ +class Bind2Backend : public DNSBackend +{ +public: + Bind2Backend(const string &suffix=""); //!< Makes our connection to the database. Calls exit(1) if it fails. + void getUnfreshSlaveInfos(vector *unfreshDomains); + void getUpdatedMasters(vector *changedDomains); + bool getDomainInfo(const string &domain, DomainInfo &di); + time_t getCtime(const string &fname); + + + void lookup(const QType &, const string &qdomain, DNSPacket *p=0, int zoneId=-1); + bool list(int id); + bool get(DNSResourceRecord &); + + static DNSBackend *maker(); + static pthread_mutex_t s_startup_lock; + + void setFresh(u_int32_t domain_id); + void setNotified(u_int32_t id, u_int32_t serial); + bool startTransaction(const string &qname, int id); + // bool Bind2Backend::stopTransaction(const string &qname, int id); + bool feedRecord(const DNSResourceRecord &r); + bool commitTransaction(); + bool abortTransaction(); + void insert(int id, const string &qname, const string &qtype, const string &content, int ttl, int prio); + void rediscover(string *status=0); + + bool isMaster(const string &name, const string &ip); +private: + class handle + { + public: + bool get(DNSResourceRecord &); + ~handle() { + if(d_bbd) + d_bbd->unlock(); + } + handle(); + + Bind2Backend *parent; + + vector*d_records; + vector::const_iterator d_iter; + + vector::const_iterator d_rend; + + vector::const_iterator d_qname_iter; + vector::const_iterator d_qname_end; + + bool d_list; + int id; + BB2DomainInfo* d_bbd; + string qname; + QType qtype; + private: + int count; + + bool get_normal(DNSResourceRecord &); + bool get_list(DNSResourceRecord &); + }; + + static map s_name_id_map; + static map s_id_zone_map; + static int s_first; + + string d_logprefix; + int d_transaction_id; + string d_transaction_tmpname; + ofstream *d_of; + handle *d_handle; + void queueReload(BB2DomainInfo *bbd); + BBResourceRecord resourceMaker(int id, const string &qtype, const string &content, int ttl, int prio); + void reload(); + static string DLDomStatusHandler(const vector&parts, Utility::pid_t ppid); + static string DLListRejectsHandler(const vector&parts, Utility::pid_t ppid); + static string DLReloadNowHandler(const vector&parts, Utility::pid_t ppid); + void loadConfig(string *status=0); + void nukeZoneRecords(BB2DomainInfo *bbd); +};