root/trunk/pdns/pdns/backends/bind/bindbackend2.cc @ 2467

Revision 2467, 39.4 KB (checked in by peter, 16 months ago)

Various pdnssec improvements, submitted by Ruben d'Arco:
- documentation updates
- check-all-zones and rectify-all-zones
- other minor fixes

*-all-zones have been tested for bindbackend and mysqlbackend, not for sqlite and pg

  • Property svn:eol-style set to native
  • Property svn:keywords set to author date id revision
Line 
1/*
2    PowerDNS Versatile Database Driven Nameserver
3    Copyright (C) 2002 - 2011  PowerDNS.COM BV
4
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License version 2 as
7    published by the Free Software Foundation;
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program; if not, write to the Free Software
16    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17*/
18
19#include <errno.h>
20#include <string>
21#include <map>
22#include <set>
23#include <sys/types.h>
24#include <sys/stat.h>
25#include <unistd.h>
26#include <fstream>
27#include <fcntl.h>
28#include <sstream>
29#include <boost/bind.hpp>
30#include <boost/algorithm/string.hpp>
31#include <boost/foreach.hpp>
32#include "dnsseckeeper.hh"
33#include "dnssecinfra.hh"
34#include "base32.hh"
35#include "namespaces.hh"
36
37#include "dns.hh"
38#include "dnsbackend.hh"
39#include "bindbackend2.hh"
40#include "dnspacket.hh"
41#include "zoneparser-tng.hh"
42#include "bindparser.hh"
43#include "logger.hh"
44#include "arguments.hh"
45#include "qtype.hh"
46#include "misc.hh"
47#include "dynlistener.hh"
48#include "lock.hh"
49#include "namespaces.hh"
50
51/** new scheme of things:
52    we have zone-id map
53    a zone-id has a vector of DNSResourceRecords
54    on start of query, we find the best zone to answer from
55*/
56
57// this map contains BB2DomainInfo structs, each of which contains a *pointer* to domain data
58shared_ptr<Bind2Backend::State> Bind2Backend::s_state;
59
60/* the model is that all our state hides in s_state. This State instance consists of the id_zone_map, which contains all our zone information, indexed by id.
61   Then there is the name_id_map, which allows us to map a query to a zone id.
62
63   The s_state is never written to, and it is a reference counted shared_ptr. Any function which needs to access the state
64   should do so by making a shared_ptr copy of it, and do all its work on that copy.
65
66   When I said s_state is never written to, I lied. No elements are ever added to it, or removed from it.
67   Its values however may be changed, but not the keys.
68
69   When it is necessary to change the State, a deep copy is made, which is changed. Afterwards,
70   the s_state pointer is made to point to the new State.
71
72   Anybody who is currently accessing the original holds a reference counted handle (shared_ptr) to it, which means it will stay around
73   To save memory, we hold the records as a shared_ptr as well.
74
75   Changes made to s_state directly should take the s_state_lock, so as to prevent writing to a stale copy.
76*/
77
78int Bind2Backend::s_first=1;
79
80pthread_mutex_t Bind2Backend::s_startup_lock=PTHREAD_MUTEX_INITIALIZER;
81pthread_mutex_t Bind2Backend::s_state_lock=PTHREAD_MUTEX_INITIALIZER;
82pthread_mutex_t Bind2Backend::s_state_swap_lock=PTHREAD_MUTEX_INITIALIZER;
83string Bind2Backend::s_binddirectory; 
84/* when a query comes in, we find the most appropriate zone and answer from that */
85
86
87BB2DomainInfo::BB2DomainInfo()
88{
89  d_loaded=false;
90  d_lastcheck=0;
91  d_checknow=false;
92  d_status="Unknown";
93}
94
95void BB2DomainInfo::setCheckInterval(time_t seconds)
96{
97  d_checkinterval=seconds;
98}
99
100bool BB2DomainInfo::current()
101{
102  if(d_checknow)
103    return false;
104
105  if(!d_checkinterval) 
106    return true;
107
108  if(time(0) - d_lastcheck < d_checkinterval)
109    return true;
110 
111  if(d_filename.empty())
112    return true;
113
114  return (getCtime()==d_ctime);
115}
116
117time_t BB2DomainInfo::getCtime()
118{
119  struct stat buf;
120 
121  if(d_filename.empty() || stat(d_filename.c_str(),&buf)<0)
122    return 0; 
123  d_lastcheck=time(0);
124  return buf.st_ctime;
125}
126
127void BB2DomainInfo::setCtime()
128{
129  struct stat buf;
130  if(stat(d_filename.c_str(),&buf)<0)
131    return; 
132  d_ctime=buf.st_ctime;
133}
134
135void Bind2Backend::setNotified(uint32_t id, uint32_t serial)
136{
137  Lock l(&s_state_lock);
138  s_state->id_zone_map[id].d_lastnotified=serial;
139}
140
141void Bind2Backend::setFresh(uint32_t domain_id)
142{
143  Lock l(&s_state_lock);
144  s_state->id_zone_map[domain_id].d_lastcheck=time(0);
145}
146
147shared_ptr<Bind2Backend::State> Bind2Backend::getState()
148{
149  shared_ptr<State> ret;
150  {
151    Lock l(&s_state_swap_lock);
152    ret = s_state; // is only read from
153  }
154  return ret;
155}
156
157bool Bind2Backend::startTransaction(const string &qname, int id)
158{
159  if(id < 0) {
160    d_transaction_tmpname.clear();
161    d_transaction_id=id;
162    return true;
163  }
164  shared_ptr<State> state = getState(); 
165
166  const BB2DomainInfo &bbd=state->id_zone_map[d_transaction_id=id];
167
168  d_transaction_tmpname=bbd.d_filename+"."+itoa(random());
169  d_of=new ofstream(d_transaction_tmpname.c_str());
170  if(!*d_of) {
171    throw DBException("Unable to open temporary zonefile '"+d_transaction_tmpname+"': "+stringerror());
172    unlink(d_transaction_tmpname.c_str());
173    delete d_of;
174    d_of=0;
175  }
176 
177  *d_of<<"; Written by PowerDNS, don't edit!"<<endl;
178  *d_of<<"; Zone '"+bbd.d_name+"' retrieved from master "<<endl<<"; at "<<nowTime()<<endl; // insert master info here again
179
180  return true;
181}
182
183bool Bind2Backend::commitTransaction()
184{
185  if(d_transaction_id < 0)
186    return true;
187  delete d_of;
188  d_of=0;
189  shared_ptr<State> state = getState(); 
190
191  // this might fail if s_state was cycled during the AXFR
192  if(rename(d_transaction_tmpname.c_str(), state->id_zone_map[d_transaction_id].d_filename.c_str())<0)
193    throw DBException("Unable to commit (rename to: '" + state->id_zone_map[d_transaction_id].d_filename+"') AXFRed zone: "+stringerror());
194
195  queueReload(&state->id_zone_map[d_transaction_id]);
196
197  d_transaction_id=0;
198
199  return true;
200}
201
202bool Bind2Backend::abortTransaction()
203{
204  if(d_transaction_id >= 0) {
205    delete d_of;
206    d_of=0;
207    unlink(d_transaction_tmpname.c_str());
208    d_transaction_id=0;
209  }
210
211  return true;
212}
213
214bool Bind2Backend::updateDNSSECOrderAndAuthAbsolute(uint32_t domain_id, const std::string& qname, const std::string& ordername, bool auth)
215{
216  #if 0
217  const shared_ptr<State> state = getState();
218  BB2DomainInfo& bbd = state->id_zone_map[domain_id];
219
220  string sqname;
221
222  if(bbd.d_name.empty())
223    sqname=qname;
224  else if(strcasecmp(qname.c_str(), bbd.d_name.c_str()))
225    sqname=qname.substr(0,qname.size() - bbd.d_name.length()-1); // strip domain name
226
227  sqname = labelReverse(sqname);
228 
229  if(!auth)
230    d_authDelayed[sqname] = auth;
231 
232  #endif
233  return false;
234}
235
236bool Bind2Backend::feedRecord(const DNSResourceRecord &r)
237{
238  string qname=r.qname;
239
240  const shared_ptr<State> state = getState();
241  string domain = state->id_zone_map[d_transaction_id].d_name;
242
243  if(!stripDomainSuffix(&qname,domain)) 
244    throw DBException("out-of-zone data '"+qname+"' during AXFR of zone '"+domain+"'");
245
246  string content=r.content;
247
248  // SOA needs stripping too! XXX FIXME - also, this should not be here I think
249  switch(r.qtype.getCode()) {
250  case QType::MX:
251    if(!stripDomainSuffix(&content, domain))
252      content+=".";
253  case QType::SRV:
254    *d_of<<qname<<"\t"<<r.ttl<<"\t"<<r.qtype.getName()<<"\t"<<r.priority<<"\t"<<content<<endl;
255    break;
256  case QType::CNAME:
257  case QType::NS:
258    if(!stripDomainSuffix(&content, domain))
259      content+=".";
260    *d_of<<qname<<"\t"<<r.ttl<<"\t"<<r.qtype.getName()<<"\t"<<content<<endl;
261    break;
262  default:
263    *d_of<<qname<<"\t"<<r.ttl<<"\t"<<r.qtype.getName()<<"\t"<<r.content<<endl;
264    break;
265  }
266  return true;
267}
268
269void Bind2Backend::getUpdatedMasters(vector<DomainInfo> *changedDomains)
270{
271  SOAData soadata;
272  shared_ptr<State> state = getState(); 
273
274  for(id_zone_map_t::const_iterator i = state->id_zone_map.begin(); i != state->id_zone_map.end() ; ++i) {
275    if(!i->second.d_masters.empty() && this->alsoNotify.empty() && i->second.d_also_notify.empty())
276      continue;
277    soadata.serial=0;
278    try {
279      this->getSOA(i->second.d_name, soadata); // we might not *have* a SOA yet, but this might trigger a load of it
280    }
281    catch(...){}
282    DomainInfo di;
283    di.id=i->first;
284    di.serial=soadata.serial;
285    di.zone=i->second.d_name;
286    di.last_check=i->second.d_lastcheck;
287    di.backend=this;
288    di.kind=DomainInfo::Master;
289    if(!i->second.d_lastnotified)  {          // don't do notification storm on startup
290      Lock l(&s_state_lock);
291      s_state->id_zone_map[i->first].d_lastnotified=soadata.serial;
292    }
293    else
294      if(soadata.serial!=i->second.d_lastnotified)
295        changedDomains->push_back(di);
296  }
297}
298
299void Bind2Backend::getAllDomains(vector<DomainInfo> *domains) {
300  SOAData soadata;
301
302  shared_ptr<State> state = getState(); 
303
304  for(id_zone_map_t::const_iterator i = state->id_zone_map.begin(); i != state->id_zone_map.end() ; ++i) {
305    soadata.db=(DNSBackend *)-1; // makes getSOA() skip the cache.
306    this->getSOA(i->second.d_name, soadata);
307    DomainInfo di;
308    di.id=i->first;
309    di.serial=soadata.serial;
310    di.zone=i->second.d_name;
311    di.last_check=i->second.d_lastcheck;
312    di.backend=this;
313    di.kind=i->second.d_masters.empty() ? DomainInfo::Master : DomainInfo::Slave; //TODO: what about Native?
314
315    domains->push_back(di);
316  }
317}
318
319
320void Bind2Backend::getUnfreshSlaveInfos(vector<DomainInfo> *unfreshDomains)
321{
322  shared_ptr<State> state = getState();
323  for(id_zone_map_t::const_iterator i = state->id_zone_map.begin(); i != state->id_zone_map.end() ; ++i) {
324    if(i->second.d_masters.empty())
325      continue;
326    DomainInfo sd;
327    sd.id=i->first;
328    sd.zone=i->second.d_name;
329    sd.masters=i->second.d_masters;
330    sd.last_check=i->second.d_lastcheck;
331    sd.backend=this;
332    sd.kind=DomainInfo::Slave;
333    SOAData soadata;
334    soadata.refresh=0;
335    soadata.serial=0;
336    soadata.db=(DNSBackend *)-1; // not sure if this is useful, inhibits any caches that might be around
337    try {
338      getSOA(i->second.d_name,soadata); // we might not *have* a SOA yet
339    }
340    catch(...){}
341    sd.serial=soadata.serial;
342    if(sd.last_check+soadata.refresh<(unsigned int)time(0))
343      unfreshDomains->push_back(sd);   
344  }
345}
346
347bool Bind2Backend::getDomainInfo(const string &domain, DomainInfo &di)
348{
349  shared_ptr<State> state = getState();
350  for(id_zone_map_t::const_iterator i = state->id_zone_map.begin(); i != state->id_zone_map.end() ; ++i) { // why is this a linear scan??
351    if(pdns_iequals(i->second.d_name,domain)) {
352      di.id=i->first;
353      di.zone=domain;
354      di.masters=i->second.d_masters;
355      di.last_check=i->second.d_lastcheck;
356      di.backend=this;
357      di.kind=i->second.d_masters.empty() ? DomainInfo::Master : DomainInfo::Slave;
358      di.serial=0;
359      try {
360        SOAData sd;
361        sd.serial=0;
362       
363        getSOA(i->second.d_name,sd); // we might not *have* a SOA yet
364        di.serial=sd.serial;
365      }
366      catch(...){}
367
368      return true;
369    }
370  }
371  return false;
372}
373
374void Bind2Backend::alsoNotifies(const string &domain, set<string> *ips)
375{
376  shared_ptr<State> state = getState();
377  // combine global list with local list
378  for(set<string>::iterator i = this->alsoNotify.begin(); i != this->alsoNotify.end(); i++) {
379    (*ips).insert(*i);
380  }
381  for(id_zone_map_t::const_iterator i = state->id_zone_map.begin(); i != state->id_zone_map.end() ; ++i) {
382    if(i->second.d_name==domain) {
383      for(set<string>::iterator it = i->second.d_also_notify.begin(); it != i->second.d_also_notify.end(); it++) {
384        (*ips).insert(*it);
385      }
386      return;
387    }
388  }   
389}
390
391//! lowercase, strip trailing .
392static string canonic(string ret)
393{
394  string::iterator i;
395
396  for(i=ret.begin();
397      i!=ret.end();
398      ++i)
399    *i=tolower(*i);
400
401
402  if(*(i-1)=='.')
403    ret.resize(i-ret.begin()-1);
404  return ret;
405}
406
407/** THIS IS AN INTERNAL FUNCTION! It does moadnsparser prio impedence matching
408    This function adds a record to a domain with a certain id.
409    Much of the complication is due to the efforts to benefit from std::string reference counting copy on write semantics */
410void Bind2Backend::insert(shared_ptr<State> stage, int id, const string &qnameu, const QType &qtype, const string &content, int ttl, int prio, const std::string& hashed)
411{
412  BB2DomainInfo bb2 = stage->id_zone_map[id];
413  Bind2DNSRecord bdr;
414
415  recordstorage_t& records=*bb2.d_records; 
416
417  bdr.qname=toLower(canonic(qnameu));
418  if(bb2.d_name.empty())
419    ;
420  else if(bdr.qname==toLower(bb2.d_name))
421    bdr.qname.clear();
422  else if(bdr.qname.length() > bb2.d_name.length())
423    bdr.qname.resize(bdr.qname.length() - (bb2.d_name.length() + 1));
424  else
425    throw AhuException("Trying to insert non-zone data, name='"+bdr.qname+"', qtype="+qtype.getName()+", zone='"+bb2.d_name+"'");
426
427  bdr.qname.swap(bdr.qname);
428
429
430  if(!records.empty() && bdr.qname==boost::prior(records.end())->qname)
431    bdr.qname=boost::prior(records.end())->qname;
432
433  //  cerr<<"Before reverse: '"<<bdr.qname<<"', ";
434  bdr.qname=labelReverse(bdr.qname);
435  //  cerr<<"After: '"<<bdr.qname<<"'"<<endl;
436
437  bdr.qtype=qtype.getCode();
438  bdr.content=content; 
439  bdr.nsec3hash = hashed;
440
441  if(bdr.qtype == QType::MX || bdr.qtype == QType::SRV) { 
442    prio=atoi(bdr.content.c_str());
443   
444    string::size_type pos = bdr.content.find_first_not_of("0123456789");
445    if(pos != string::npos)
446      boost::erase_head(bdr.content, pos);
447    trim_left(bdr.content);
448  }
449 
450  if(bdr.qtype==QType::CNAME || bdr.qtype==QType::MX || bdr.qtype==QType::NS || bdr.qtype==QType::AFSDB)
451    bdr.content=canonic(bdr.content); // I think this is wrong, the zoneparser should not come up with . terminated stuff XXX FIXME
452
453  bdr.ttl=ttl;
454  bdr.priority=prio;
455 
456  records.insert(bdr);
457}
458
459void Bind2Backend::reload()
460{
461  Lock l(&s_state_lock);
462  for(id_zone_map_t::iterator i = s_state->id_zone_map.begin(); i != s_state->id_zone_map.end(); ++i) 
463    i->second.d_checknow=true;
464}
465
466string Bind2Backend::DLReloadNowHandler(const vector<string>&parts, Utility::pid_t ppid)
467{
468  shared_ptr<State> state = getState();
469  ostringstream ret;
470
471  for(vector<string>::const_iterator i=parts.begin()+1;i<parts.end();++i) {
472    if(state->name_id_map.count(*i)) {
473      BB2DomainInfo& bbd=state->id_zone_map[state->name_id_map[*i]];
474      Bind2Backend bb2;
475      bb2.queueReload(&bbd);
476      ret<< *i << ": "<< (bbd.d_loaded ? "": "[rejected]") <<"\t"<<bbd.d_status<<"\n";     
477    }
478    else
479      ret<< *i << " no such domain\n";
480  }   
481  if(ret.str().empty())
482    ret<<"no domains reloaded";
483  return ret.str();
484}
485
486
487string Bind2Backend::DLDomStatusHandler(const vector<string>&parts, Utility::pid_t ppid)
488{
489  ostringstream ret;
490  shared_ptr<State> state = getState();
491     
492  if(parts.size() > 1) {
493    for(vector<string>::const_iterator i=parts.begin()+1;i<parts.end();++i) {
494      if(state->name_id_map.count(*i)) {
495        BB2DomainInfo& bbd=state->id_zone_map[state->name_id_map[*i]];  // XXX s_name_id_map needs trick as well
496        ret<< *i << ": "<< (bbd.d_loaded ? "": "[rejected]") <<"\t"<<bbd.d_status<<"\n";     
497    }
498      else
499        ret<< *i << " no such domain\n";
500    }   
501  }
502  else
503    for(id_zone_map_t::iterator i=state->id_zone_map.begin(); i!=state->id_zone_map.end(); ++i) 
504      ret<< i->second.d_name << ": "<< (i->second.d_loaded ? "": "[rejected]") <<"\t"<<i->second.d_status<<"\n";     
505
506  if(ret.str().empty())
507    ret<<"no domains passed";
508
509  return ret.str();
510}
511
512
513string Bind2Backend::DLListRejectsHandler(const vector<string>&parts, Utility::pid_t ppid)
514{
515  shared_ptr<State> state = getState();
516
517  ostringstream ret;
518  for(id_zone_map_t::iterator j = state->id_zone_map.begin(); j != state->id_zone_map.end(); ++j) 
519    if(!j->second.d_loaded)
520      ret<<j->second.d_name<<"\t"<<j->second.d_status<<endl;
521       
522  return ret.str();
523}
524
525Bind2Backend::Bind2Backend(const string &suffix, bool loadZones)
526{
527#if __GNUC__ >= 3
528    std::ios_base::sync_with_stdio(false);
529#endif
530  d_logprefix="[bind"+suffix+"backend]";
531  setArgPrefix("bind"+suffix);
532  Lock l(&s_startup_lock);
533 
534  d_transaction_id=0;
535  setupDNSSEC();
536  if(!s_first) {
537    return;
538  }
539 
540  s_state = shared_ptr<State>(new State);
541  if(loadZones) {
542    loadConfig();
543    s_first=0;
544  }
545 
546  extern DynListener *dl;
547  dl->registerFunc("BIND-RELOAD-NOW", &DLReloadNowHandler, "bindbackend: reload domains", "<domains>");
548  dl->registerFunc("BIND-DOMAIN-STATUS", &DLDomStatusHandler, "bindbackend: list status of all domains", "[domains]");
549  dl->registerFunc("BIND-LIST-REJECTS", &DLListRejectsHandler, "bindbackend: list rejected domains");
550}
551
552Bind2Backend::~Bind2Backend()
553{
554
555}
556
557void Bind2Backend::rediscover(string *status)
558{
559  loadConfig(status);
560}
561#if 0
562static void prefetchFile(const std::string& fname)
563{
564
565  static int fd;
566  if(fd > 0)
567    close(fd);
568  fd=open(fname.c_str(), O_RDONLY);
569  if(fd < 0)
570    return;
571
572  posix_fadvise(fd, 0, 0, POSIX_FADV_WILLNEED);
573}
574#endif
575
576void Bind2Backend::fixupAuth(shared_ptr<recordstorage_t> records)
577{
578  pair<recordstorage_t::const_iterator, recordstorage_t::const_iterator> range;
579  string sqname;
580 
581  recordstorage_t nssets;
582  BOOST_FOREACH(const Bind2DNSRecord& bdr, *records) {
583    if(bdr.qtype==QType::NS) 
584      nssets.insert(bdr);
585  }
586 
587  BOOST_FOREACH(const Bind2DNSRecord& bdr, *records) {
588    bdr.auth=true;
589   
590    if(bdr.qtype == QType::DS) // as are delegation signer records
591      continue;
592
593    sqname = labelReverse(bdr.qname);
594   
595    do {
596      if(sqname.empty()) // this is auth of course!
597        continue; 
598      if(bdr.qtype == QType::NS || nssets.count(sqname)) { // NS records which are not apex are unauth by definition
599        bdr.auth=false;
600      }
601    } while(chopOff(sqname));
602  }
603}
604
605void Bind2Backend::loadConfig(string* status)
606{
607  // Interference with createSlaveDomain()
608  Lock l(&s_state_lock);
609 
610  static int domain_id=1;
611
612  shared_ptr<State> staging = shared_ptr<State>(new State);
613
614  if(!getArg("config").empty()) {
615    BindParser BP;
616    try {
617      BP.parse(getArg("config"));
618    }
619    catch(AhuException &ae) {
620      L<<Logger::Error<<"Error parsing bind configuration: "<<ae.reason<<endl;
621      throw;
622    }
623     
624    vector<BindDomainInfo> domains=BP.getDomains();
625    this->alsoNotify = BP.getAlsoNotify();
626
627    s_binddirectory=BP.getDirectory();
628    //    ZP.setDirectory(d_binddirectory);
629
630    L<<Logger::Warning<<d_logprefix<<" Parsing "<<domains.size()<<" domain(s), will report when done"<<endl;
631   
632    int rejected=0;
633    int newdomains=0;
634
635    //    random_shuffle(domains.begin(), domains.end());
636    struct stat st;
637     
638    for(vector<BindDomainInfo>::iterator i=domains.begin(); i!=domains.end(); ++i) 
639    {
640      if(stat(i->filename.c_str(), &st) == 0) {
641        i->d_dev = st.st_dev;
642        i->d_ino = st.st_ino;
643      }
644    }
645
646    sort(domains.begin(), domains.end()); // put stuff in inode order
647    for(vector<BindDomainInfo>::const_iterator i=domains.begin();
648        i!=domains.end();
649        ++i) 
650      {
651        if(i->type!="master" && i->type!="slave") {
652          L<<Logger::Warning<<d_logprefix<<" Warning! Skipping '"<<i->type<<"' zone '"<<i->name<<"'"<<endl;
653          continue;
654        }
655
656        BB2DomainInfo* bbd=0;
657
658        if(!s_state->name_id_map.count(i->name)) { // is it fully new?
659          bbd=&staging->id_zone_map[domain_id];
660          bbd->d_id=domain_id++;
661       
662          // this isn't necessary, we do this on the actual load
663          //      bbd->d_records=shared_ptr<recordstorage_t > (new recordstorage_t);
664
665          bbd->setCheckInterval(getArgAsNum("check-interval"));
666          bbd->d_lastnotified=0;
667          bbd->d_loaded=false;
668        }
669        else {  // no, we knew about it already
670          staging->id_zone_map[s_state->name_id_map[i->name]] = s_state->id_zone_map[s_state->name_id_map[i->name]]; // these should all be read-only on s_state
671          bbd = &staging->id_zone_map[s_state->name_id_map[i->name]];
672        }
673       
674        staging->name_id_map[i->name]=bbd->d_id; // fill out name -> id map
675
676        // overwrite what we knew about the domain
677        bbd->d_name=i->name;
678
679        bool filenameChanged = (bbd->d_filename!=i->filename);
680        bbd->d_filename=i->filename;
681        bbd->d_masters=i->masters;
682        bbd->d_also_notify=i->alsoNotify;
683       
684        if(filenameChanged || !bbd->d_loaded || !bbd->current()) {
685          L<<Logger::Info<<d_logprefix<<" parsing '"<<i->name<<"' from file '"<<i->filename<<"'"<<endl;
686
687          NSEC3PARAMRecordContent ns3pr;
688          bool nsec3zone=getNSEC3PARAM(i->name, &ns3pr);
689       
690          try {
691            // we need to allocate a new vector so we don't kill the original, which is still in use!
692            bbd->d_records=shared_ptr<recordstorage_t> (new recordstorage_t()); 
693
694            ZoneParserTNG zpt(i->filename, i->name, BP.getDirectory());
695            DNSResourceRecord rr;
696            string hashed;
697            while(zpt.get(rr)) {
698              if(nsec3zone)
699                hashed=toLower(toBase32Hex(hashQNameWithSalt(ns3pr.d_iterations, ns3pr.d_salt, rr.qname)));
700              insert(staging, bbd->d_id, rr.qname, rr.qtype, rr.content, rr.ttl, rr.priority, hashed);
701            }
702       
703            // sort(staging->id_zone_map[bbd->d_id].d_records->begin(), staging->id_zone_map[bbd->d_id].d_records->end());
704           
705            shared_ptr<recordstorage_t > records=staging->id_zone_map[bbd->d_id].d_records;
706            fixupAuth(records);
707           
708            staging->id_zone_map[bbd->d_id].setCtime();
709            staging->id_zone_map[bbd->d_id].d_loaded=true; 
710            staging->id_zone_map[bbd->d_id].d_status="parsed into memory at "+nowTime();
711           
712            //  s_stage->id_zone_map[bbd->d_id].d_records->swap(*s_staging_zone_map[bbd->d_id].d_records);
713          }
714          catch(AhuException &ae) {
715            ostringstream msg;
716            msg<<" error at "+nowTime()+" parsing '"<<i->name<<"' from file '"<<i->filename<<"': "<<ae.reason;
717
718            if(status)
719              *status+=msg.str();
720            staging->id_zone_map[bbd->d_id].d_status=msg.str();
721            L<<Logger::Warning<<d_logprefix<<msg.str()<<endl;
722            rejected++;
723          }
724          catch(std::exception &ae) {
725            ostringstream msg;
726            msg<<" error at "+nowTime()+" parsing '"<<i->name<<"' from file '"<<i->filename<<"': "<<ae.what();
727
728            if(status)
729              *status+=msg.str();
730            staging->id_zone_map[bbd->d_id].d_status=msg.str();
731            L<<Logger::Warning<<d_logprefix<<msg.str()<<endl;
732            rejected++;
733          }
734        }
735        /*
736        vector<vector<BBResourceRecord> *>&tmp=d_zone_id_map[bbd.d_id];  // shrink trick
737        vector<vector<BBResourceRecord> *>(tmp).swap(tmp);
738        */
739      }
740
741    // figure out which domains were new and which vanished
742    int remdomains=0;
743    set<string> oldnames, newnames;
744    for(id_zone_map_t::const_iterator j=s_state->id_zone_map.begin();j != s_state->id_zone_map.end();++j) {
745      oldnames.insert(j->second.d_name);
746    }
747    for(id_zone_map_t::const_iterator j=staging->id_zone_map.begin(); j!= staging->id_zone_map.end(); ++j) {
748      newnames.insert(j->second.d_name);
749    }
750
751    vector<string> diff;
752    set_difference(oldnames.begin(), oldnames.end(), newnames.begin(), newnames.end(), back_inserter(diff));
753    remdomains=diff.size();
754
755#if 0       
756    // remove domains from the *name* map, delete their pointer
757    for(vector<string>::const_iterator k=diff.begin();k!=diff.end(); ++k) {
758      L<<Logger::Error<<"Removing domain: "<<*k<<endl;
759      s_state->name_id_map.erase(*k);
760    }
761
762    // now remove from the s_state->id_zone_map
763    for(id_zone_map_t::iterator j=s_state->id_zone_map.begin();j!=s_state->id_zone_map.end();++j) { // O(N*M)
764      for(vector<string>::const_iterator k=diff.begin();k!=diff.end();++k)
765        if(j->second.d_name==*k) {
766          L<<Logger::Error<<"Removing records from zone '"<<j->second.d_name<<"' from memory"<<endl;
767
768          j->second.d_loaded=false;
769          nukeZoneRecords(&j->second);
770
771          break;
772        }
773    }
774#endif
775
776    // count number of entirely new domains
777    vector<string> diff2;
778    set_difference(newnames.begin(), newnames.end(), oldnames.begin(), oldnames.end(), back_inserter(diff2));
779    newdomains=diff2.size();
780   
781    Lock l(&s_state_swap_lock);
782    s_state.swap(staging); 
783
784    // report
785    ostringstream msg;
786    msg<<" Done parsing domains, "<<rejected<<" rejected, "<<newdomains<<" new, "<<remdomains<<" removed"; 
787    if(status)
788      *status=msg.str();
789
790    L<<Logger::Error<<d_logprefix<<msg.str()<<endl;
791  }
792}
793
794/** nuke all records from memory, keep bbd intact though. */
795void Bind2Backend::nukeZoneRecords(BB2DomainInfo *bbd)
796{
797  bbd->d_loaded=0; // block further access
798  bbd->d_records = shared_ptr<recordstorage_t > (new recordstorage_t);
799}
800
801
802void Bind2Backend::queueReload(BB2DomainInfo *bbd)
803{
804  Lock l(&s_state_lock);
805
806  shared_ptr<State> staging(new State);
807
808  // we reload *now* for the time being
809
810  try {
811    nukeZoneRecords(bbd); // ? do we need this?
812    staging->id_zone_map[bbd->d_id]=s_state->id_zone_map[bbd->d_id];
813    staging->id_zone_map[bbd->d_id].d_records=shared_ptr<recordstorage_t > (new recordstorage_t);  // nuke it
814
815    ZoneParserTNG zpt(bbd->d_filename, bbd->d_name, s_binddirectory);
816    DNSResourceRecord rr;
817    string hashed;
818    NSEC3PARAMRecordContent ns3pr;
819    bool nsec3zone=getNSEC3PARAM(bbd->d_name, &ns3pr);
820    while(zpt.get(rr)) {
821      if(nsec3zone)
822        hashed=toLower(toBase32Hex(hashQNameWithSalt(ns3pr.d_iterations, ns3pr.d_salt, rr.qname)));
823      insert(staging, bbd->d_id, rr.qname, rr.qtype, rr.content, rr.ttl, rr.priority, hashed);
824    }
825    // cerr<<"Start sort of "<<staging->id_zone_map[bbd->d_id].d_records->size()<<" records"<<endl;       
826    // sort(staging->id_zone_map[bbd->d_id].d_records->begin(), staging->id_zone_map[bbd->d_id].d_records->end());
827    // cerr<<"Sorting done"<<endl;
828   
829    fixupAuth(staging->id_zone_map[bbd->d_id].d_records);
830    staging->id_zone_map[bbd->d_id].setCtime();
831
832    s_state->id_zone_map[bbd->d_id]=staging->id_zone_map[bbd->d_id]; // move over
833
834    bbd->setCtime();
835    // and raise d_loaded again!
836    bbd->d_loaded=1;
837    bbd->d_checknow=0;
838    bbd->d_status="parsed into memory at "+nowTime();
839    L<<Logger::Warning<<"Zone '"<<bbd->d_name<<"' ("<<bbd->d_filename<<") reloaded"<<endl;
840  }
841  catch(AhuException &ae) {
842    ostringstream msg;
843    msg<<" error at "+nowTime()+" parsing '"<<bbd->d_name<<"' from file '"<<bbd->d_filename<<"': "<<ae.reason;
844    bbd->d_status=msg.str();
845  }
846  catch(std::exception &ae) {
847    ostringstream msg;
848    msg<<" error at "+nowTime()+" parsing '"<<bbd->d_name<<"' from file '"<<bbd->d_filename<<"': "<<ae.what();
849    bbd->d_status=msg.str();
850  }
851}
852
853bool Bind2Backend::findBeforeAndAfterUnhashed(BB2DomainInfo& bbd, const std::string& qname, std::string& unhashed, std::string& before, std::string& after)
854{
855  string domain=toLower(qname);
856
857  //cout<<"starting lower bound for: '"<<domain<<"'"<<endl;
858
859  recordstorage_t::const_iterator iter = bbd.d_records->lower_bound(domain);
860
861  if (iter == bbd.d_records->end() || (iter->qname) > domain)
862  {
863    before = boost::prior(iter)->qname;
864  }
865  else
866  {
867    before = qname;
868  }
869
870  //cerr<<"Now upper bound"<<endl;
871  iter = bbd.d_records->upper_bound(domain);
872
873  if(iter == bbd.d_records->end()) {
874    //cerr<<"\tFound the end, begin storage: '"<<bbd.d_records->begin()->qname<<"', '"<<bbd.d_name<<"'"<<endl;
875    after.clear(); // this does the right thing
876  } else {
877    //cerr<<"\tFound: '"<<(iter->qname)<<"' (nsec3hash='"<<(iter->nsec3hash)<<"')"<<endl;
878    after = (iter)->qname;
879  }
880
881  //cerr<<"Before: '"<<before<<"', after: '"<<after<<"'\n";
882  return true;
883}
884
885bool Bind2Backend::getBeforeAndAfterNamesAbsolute(uint32_t id, const std::string& qname, std::string& unhashed, std::string& before, std::string& after)
886{
887  shared_ptr<State> state = s_state;
888  BB2DomainInfo& bbd = state->id_zone_map[id]; 
889  NSEC3PARAMRecordContent ns3pr;
890  string auth=state->id_zone_map[id].d_name;
891   
892  if(!getNSEC3PARAM(auth, &ns3pr)) {
893    //cerr<<"in bind2backend::getBeforeAndAfterAbsolute: no nsec3 for "<<auth<<endl;
894    return findBeforeAndAfterUnhashed(bbd, qname, unhashed, before, after);
895 
896  }
897  else {
898    string lqname = toLower(qname);
899    //cerr<<"\nin bind2backend::getBeforeAndAfterAbsolute: nsec3 HASH for "<<auth<<", asked for: "<<lqname<< " (auth: "<<auth<<".)"<<endl;
900    typedef recordstorage_t::index<HashedTag>::type records_by_hashindex_t;
901    records_by_hashindex_t& hashindex=boost::multi_index::get<HashedTag>(*bbd.d_records);
902   
903//    BOOST_FOREACH(const Bind2DNSRecord& bdr, hashindex) {
904//      cerr<<"Hash: "<<bdr.nsec3hash<<"\t"<< (lqname < bdr.nsec3hash) <<endl;
905//    }
906   
907    records_by_hashindex_t::const_iterator lowIter = hashindex.lower_bound(lqname);
908    records_by_hashindex_t::const_iterator highIter = hashindex.upper_bound(lqname);
909//    cerr<<"iter == hashindex.begin(): "<< (iter == hashindex.begin()) << ", ";
910  //  cerr<<"iter == hashindex.end(): "<< (iter == hashindex.end()) << endl;
911    if(lowIter == hashindex.end()) { 
912//      cerr<<"This hash is beyond the highest hash, wrapping around"<<endl;
913      before = hashindex.rbegin()->nsec3hash; // highest hash
914      after = hashindex.begin()->nsec3hash; // lowest hash
915      unhashed = dotConcat(labelReverse(hashindex.rbegin()->qname), auth);
916    }
917    else if(lowIter->nsec3hash == lqname) { // exact match
918      before = lowIter->nsec3hash;
919      unhashed = dotConcat(labelReverse(lowIter->qname), auth);
920  //    cerr<<"Had direct hit, setting unhashed: "<<unhashed<<endl;     
921      if(highIter != hashindex.end())
922       after = highIter->nsec3hash;
923      else
924       after = hashindex.begin()->nsec3hash;
925    }
926    else  {
927     // iter will always be HIGHER than lqname, but that's not what we need
928     //  rest .. before pos_iter/after pos
929     //             lqname
930      if(highIter != hashindex.end())
931       after = highIter->nsec3hash; // that one is easy
932      else
933       after = hashindex.begin()->nsec3hash;
934       
935      if(lowIter != hashindex.begin()) {
936       --lowIter;
937       before = lowIter->nsec3hash;
938       unhashed = dotConcat(labelReverse(lowIter->qname), auth);
939      }
940      else {
941       before = hashindex.rbegin()->nsec3hash;
942       unhashed = dotConcat(labelReverse(hashindex.rbegin()->qname), auth);       
943      }
944    }
945   
946    //cerr<<"Before: '"<<before<<"', after: '"<<after<<"'\n";
947    return true;
948  }
949}
950
951void Bind2Backend::lookup(const QType &qtype, const string &qname, DNSPacket *pkt_p, int zoneId )
952{
953  d_handle.reset();
954
955  string domain=toLower(qname);
956
957  static bool mustlog=::arg().mustDo("query-logging");
958  if(mustlog) 
959    L<<Logger::Warning<<"Lookup for '"<<qtype.getName()<<"' of '"<<domain<<"'"<<endl;
960
961  shared_ptr<State> state = s_state;
962
963  name_id_map_t::const_iterator iditer;
964  while((iditer=state->name_id_map.find(domain)) == state->name_id_map.end() && chopOff(domain))
965    ;
966
967  if(iditer==state->name_id_map.end()) {
968    if(mustlog)
969      L<<Logger::Warning<<"Found no authoritative zone for "<<qname<<endl;
970    d_handle.d_list=false;
971    return;
972  }
973  //  unsigned int id=*iditer;
974  if(mustlog)
975    L<<Logger::Warning<<"Found a zone '"<<domain<<"' (with id " << iditer->second<<") that might contain data "<<endl;
976   
977  d_handle.id=iditer->second;
978 
979  DLOG(L<<"Bind2Backend constructing handle for search for "<<qtype.getName()<<" for "<<
980       qname<<endl);
981 
982  if(domain.empty())
983    d_handle.qname=qname;
984  else if(strcasecmp(qname.c_str(),domain.c_str()))
985    d_handle.qname=qname.substr(0,qname.size()-domain.length()-1); // strip domain name
986
987  d_handle.qtype=qtype;
988  d_handle.domain=qname.substr(qname.size()-domain.length());
989
990  BB2DomainInfo& bbd = state->id_zone_map[iditer->second];
991  if(!bbd.d_loaded) {
992    d_handle.reset();
993    throw DBException("Zone for '"+bbd.d_name+"' in '"+bbd.d_filename+"' temporarily not available (file missing, or master dead)"); // fsck
994  }
995   
996  if(!bbd.current()) {
997    L<<Logger::Warning<<"Zone '"<<bbd.d_name<<"' ("<<bbd.d_filename<<") needs reloading"<<endl;
998    queueReload(&bbd);  // how can this be safe - ok, everybody should have their own reference counted copy of 'records'
999    state = s_state;
1000  }
1001
1002  d_handle.d_records = bbd.d_records; // give it a reference counted copy
1003 
1004  if(d_handle.d_records->empty())
1005    DLOG(L<<"Query with no results"<<endl);
1006
1007  pair<recordstorage_t::const_iterator, recordstorage_t::const_iterator> range;
1008
1009  string lname=labelReverse(toLower(d_handle.qname));
1010  //cout<<"starting equal range for: '"<<d_handle.qname<<"', search is for: '"<<lname<<"'"<<endl;
1011 
1012  range = d_handle.d_records->equal_range(lname);
1013  //cout<<"End equal range"<<endl;
1014  d_handle.mustlog = mustlog;
1015 
1016  if(range.first==range.second) {
1017    // cerr<<"Found nothing!"<<endl;
1018    d_handle.d_list=false;
1019    d_handle.d_iter = d_handle.d_end_iter  = range.first;
1020    return;
1021  }
1022  else {
1023    // cerr<<"Found something!"<<endl;
1024    d_handle.d_iter=range.first;
1025    d_handle.d_end_iter=range.second;
1026  }
1027
1028  d_handle.d_list=false;
1029}
1030
1031Bind2Backend::handle::handle()
1032{
1033  mustlog=false;
1034}
1035
1036bool Bind2Backend::get(DNSResourceRecord &r)
1037{
1038  if(!d_handle.d_records) {
1039    if(d_handle.mustlog)
1040      L<<Logger::Warning<<"There were no answers"<<endl;
1041    return false;
1042  }
1043
1044  if(!d_handle.get(r)) {
1045    if(d_handle.mustlog)
1046      L<<Logger::Warning<<"End of answers"<<endl;
1047
1048    d_handle.reset();
1049
1050    return false;
1051  }
1052  if(d_handle.mustlog)
1053    L<<Logger::Warning<<"Returning: '"<<r.qtype.getName()<<"' of '"<<r.qname<<"', content: '"<<r.content<<"', prio: "<<r.priority<<endl;
1054  return true;
1055}
1056
1057bool Bind2Backend::handle::get(DNSResourceRecord &r)
1058{
1059  if(d_list)
1060    return get_list(r);
1061  else
1062    return get_normal(r);
1063}
1064
1065//#define DLOG(x) x
1066bool Bind2Backend::handle::get_normal(DNSResourceRecord &r)
1067{
1068  DLOG(L << "Bind2Backend get() was called for "<<qtype.getName() << " record for '"<<
1069       qname<<"' - "<<d_records->size()<<" available in total!"<<endl);
1070 
1071  if(d_iter==d_end_iter) {
1072    return false;
1073  }
1074
1075  while(d_iter!=d_end_iter && !(qtype.getCode()==QType::ANY || (d_iter)->qtype==qtype.getCode())) {
1076    DLOG(L<<Logger::Warning<<"Skipped "<<qname<<"/"<<QType(d_iter->qtype).getName()<<": '"<<d_iter->content<<"'"<<endl);
1077    d_iter++;
1078  }
1079  if(d_iter==d_end_iter) {
1080    return false;
1081  }
1082  DLOG(L << "Bind2Backend get() returning a rr with a "<<QType(d_iter->qtype).getCode()<<endl);
1083
1084  r.qname=qname.empty() ? domain : (qname+"."+domain);
1085  r.domain_id=id;
1086  r.content=(d_iter)->content;
1087  //  r.domain_id=(d_iter)->domain_id;
1088  r.qtype=(d_iter)->qtype;
1089  r.ttl=(d_iter)->ttl;
1090  r.priority=(d_iter)->priority;
1091
1092  //if(!d_iter->auth && r.qtype.getCode() != QType::A && r.qtype.getCode()!=QType::AAAA && r.qtype.getCode() != QType::NS)
1093  //  cerr<<"Warning! Unauth response for qtype "<< r.qtype.getName() << " for '"<<r.qname<<"'"<<endl;
1094  r.auth = d_iter->auth;
1095
1096  d_iter++;
1097
1098  return true;
1099}
1100
1101bool Bind2Backend::list(const string &target, int id)
1102{
1103  shared_ptr<State> state = s_state;
1104  if(!state->id_zone_map.count(id))
1105    return false;
1106
1107  d_handle.reset(); 
1108  DLOG(L<<"Bind2Backend constructing handle for list of "<<id<<endl);
1109
1110  d_handle.d_records=state->id_zone_map[id].d_records; // give it a copy, which will stay around
1111  d_handle.d_qname_iter= d_handle.d_records->begin();
1112  d_handle.d_qname_end=d_handle.d_records->end();   // iter now points to a vector of pointers to vector<BBResourceRecords>
1113
1114  d_handle.id=id;
1115  d_handle.d_list=true;
1116  return true;
1117
1118}
1119
1120bool Bind2Backend::handle::get_list(DNSResourceRecord &r)
1121{
1122  if(d_qname_iter!=d_qname_end) {
1123    r.qname=d_qname_iter->qname.empty() ? domain : (labelReverse(d_qname_iter->qname)+"."+domain);
1124    r.domain_id=id;
1125    r.content=(d_qname_iter)->content;
1126    r.qtype=(d_qname_iter)->qtype;
1127    r.ttl=(d_qname_iter)->ttl;
1128    r.priority=(d_qname_iter)->priority;
1129    r.auth = d_qname_iter->auth;
1130    d_qname_iter++;
1131    return true;
1132  }
1133  return false;
1134
1135}
1136
1137// this function really is too slow
1138bool Bind2Backend::isMaster(const string &name, const string &ip)
1139{
1140  shared_ptr<State> state = getState(); 
1141  for(id_zone_map_t::iterator j=state->id_zone_map.begin(); j!=state->id_zone_map.end();++j) {
1142    if(j->second.d_name==name) {
1143      for(vector<string>::const_iterator iter = j->second.d_masters.begin(); iter != j->second.d_masters.end(); ++iter)
1144        if(*iter==ip)
1145          return true;
1146    }
1147  }
1148  return false;
1149}
1150
1151bool Bind2Backend::superMasterBackend(const string &ip, const string &domain, const vector<DNSResourceRecord>&nsset, string *account, DNSBackend **db)
1152{
1153  // Check whether we have a configfile available.
1154  if (getArg("supermaster-config").empty())
1155    return false;
1156
1157  ifstream c_if(getArg("supermasters").c_str(), std::ios::in); // this was nocreate?
1158  if (!c_if) {
1159    L << Logger::Error << "Unable to open supermasters file for read: " << stringerror() << endl;
1160    return false;
1161  }
1162
1163  // Format:
1164  // <ip> <accountname>
1165  string line, sip, saccount;
1166  while (getline(c_if, line)) {
1167    std::istringstream ii(line);
1168    ii >> sip;
1169    if (sip == ip) {
1170      ii >> saccount;
1171      break;
1172    }
1173  } 
1174  c_if.close();
1175
1176  if (sip != ip)  // ip not found in authorization list - reject
1177    return false;
1178 
1179  // ip authorized as supermaster - accept
1180  *db = this;
1181  if (saccount.length() > 0)
1182      *account = saccount.c_str();
1183
1184  return true;
1185}
1186
1187bool Bind2Backend::createSlaveDomain(const string &ip, const string &domain, const string &account)
1188{
1189  // Interference with loadConfig(), use locking
1190  Lock l(&s_state_lock);
1191
1192  string filename = getArg("supermaster-destdir")+'/'+domain;
1193 
1194  L << Logger::Warning << d_logprefix
1195    << " Writing bind config zone statement for superslave zone '" << domain
1196    << "' from supermaster " << ip << endl;
1197       
1198  ofstream c_of(getArg("supermaster-config").c_str(),  std::ios::app);
1199  if (!c_of) {
1200    L << Logger::Error << "Unable to open supermaster configfile for append: " << stringerror() << endl;
1201    throw DBException("Unable to open supermaster configfile for append: "+stringerror());
1202  }
1203 
1204  c_of << endl;
1205  c_of << "# Superslave zone " << domain << " (added: " << nowTime() << ") (account: " << account << ')' << endl;
1206  c_of << "zone \"" << domain << "\" {" << endl;
1207  c_of << "\ttype slave;" << endl;
1208  c_of << "\tfile \"" << filename << "\";" << endl;
1209  c_of << "\tmasters { " << ip << "; };" << endl;
1210  c_of << "};" << endl;
1211  c_of.close();
1212
1213  int newid=0;
1214  // Find a free zone id nr. 
1215 
1216  if (!s_state->id_zone_map.empty()) {
1217    id_zone_map_t::reverse_iterator i = s_state->id_zone_map.rbegin();
1218    newid = i->second.d_id + 1;
1219  }
1220 
1221  BB2DomainInfo &bbd = s_state->id_zone_map[newid];
1222
1223  bbd.d_records = shared_ptr<recordstorage_t >(new recordstorage_t);
1224  bbd.d_name = domain;
1225  bbd.setCheckInterval(getArgAsNum("check-interval"));
1226  bbd.d_masters.push_back(ip);
1227  bbd.d_filename = filename;
1228
1229  s_state->name_id_map[domain] = bbd.d_id;
1230 
1231  return true;
1232}
1233
1234class Bind2Factory : public BackendFactory
1235{
1236   public:
1237      Bind2Factory() : BackendFactory("bind") {}
1238
1239      void declareArguments(const string &suffix="")
1240      {
1241         declare(suffix,"config","Location of named.conf","");
1242         //         declare(suffix,"example-zones","Install example zones","no");
1243         declare(suffix,"check-interval","Interval for zonefile changes","0");
1244         declare(suffix,"supermaster-config","Location of (part of) named.conf where pdns can write zone-statements to","");
1245         declare(suffix,"supermasters","List of IP-addresses of supermasters","");
1246         declare(suffix,"supermaster-destdir","Destination directory for newly added slave zones",::arg()["config-dir"]);
1247         declare(suffix,"dnssec-db","Filename to store & access our DNSSEC metadatabase, empty for none", "");
1248      }
1249
1250      DNSBackend *make(const string &suffix="")
1251      {
1252         return new Bind2Backend(suffix);
1253      }
1254     
1255      DNSBackend *makeMetadataOnly(const string &suffix="")
1256      {
1257        return new Bind2Backend(suffix, false);
1258      }
1259
1260};
1261
1262//! Magic class that is activated when the dynamic library is loaded
1263class Bind2Loader
1264{
1265public:
1266  Bind2Loader()
1267  {
1268    BackendMakers().report(new Bind2Factory);
1269    L<<Logger::Notice<<"[Bind2Backend] This is the bind backend version "VERSION" ("__DATE__", "__TIME__") reporting"<<endl;
1270  }
1271};
1272static Bind2Loader bind2loader;
Note: See TracBrowser for help on using the browser.