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

Revision 1932, 38.3 KB (checked in by ahu, 2 years ago)

silence a lot of debugging output at loglevel 9 (you did not see it, but it did slow things down)
fix up pre-signed zones in hybrid installations (bind + generic). Because the BIND Backend needs the DBDnssecKeeper,
while the dbdnsseckeeper needs the bindbackend to function, getting presignatures from the dbdnsseckeeper failed.
We now pass an explicit database connection for this purpose. Spotted by Christof Meerwaald.

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