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

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

teach backends not to delete a zone if a negative zone-id is passed to startTransaction, but only to start a transaction in that case

  • 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 
853  if(!dk.getNSEC3PARAM(auth, &ns3pr)) {
854    cerr<<"in bind2backend::getBeforeAndAfterAbsolute: no nsec3 for "<<auth<<endl;
855    return findBeforeAndAfterUnhashed(bbd, qname, unhashed, before, after);
856 
857  }
858  else {
859    string lqname = toLower(qname);
860    cerr<<"\nin bind2backend::getBeforeAndAfterAbsolute: nsec3 HASH for "<<auth<<", asked for: "<<lqname<< " (auth: "<<auth<<".)"<<endl;
861    typedef recordstorage_t::index<HashedTag>::type records_by_hashindex_t;
862    records_by_hashindex_t& hashindex=boost::multi_index::get<HashedTag>(*bbd.d_records);
863   
864//    BOOST_FOREACH(const Bind2DNSRecord& bdr, hashindex) {
865//      cerr<<"Hash: "<<bdr.nsec3hash<<"\t"<< (lqname < bdr.nsec3hash) <<endl;
866//    }
867   
868    records_by_hashindex_t::const_iterator lowIter = hashindex.lower_bound(lqname);
869    records_by_hashindex_t::const_iterator highIter = hashindex.upper_bound(lqname);
870//    cerr<<"iter == hashindex.begin(): "<< (iter == hashindex.begin()) << ", ";
871  //  cerr<<"iter == hashindex.end(): "<< (iter == hashindex.end()) << endl;
872    if(lowIter == hashindex.end()) { 
873      cerr<<"This hash is beyond the highest hash, wrapping around"<<endl;
874      before = hashindex.rbegin()->nsec3hash; // highest hash
875      after = hashindex.begin()->nsec3hash; // lowest hash
876      unhashed = dotConcat(labelReverse(hashindex.rbegin()->qname), auth);
877    }
878    else if(lowIter->nsec3hash == lqname) { // exact match
879      before = lowIter->nsec3hash;
880      unhashed = dotConcat(labelReverse(lowIter->qname), auth);
881      cerr<<"Had direct hit, setting unhashed: "<<unhashed<<endl;     
882      if(highIter != hashindex.end())
883       after = highIter->nsec3hash;
884      else
885       after = hashindex.begin()->nsec3hash;
886    }
887    else  {
888     // iter will always be HIGER than lqname, but that's not what we need
889     //  rest .. before pos_iter/after pos
890     //             lqname
891      if(highIter != hashindex.end())
892       after = highIter->nsec3hash; // that one is easy
893      else
894       after = hashindex.begin()->nsec3hash;
895       
896      if(lowIter != hashindex.begin()) {
897       --lowIter;
898       before = lowIter->nsec3hash;
899       unhashed = dotConcat(labelReverse(lowIter->qname), auth);
900      }
901      else {
902       before = hashindex.rbegin()->nsec3hash;
903       unhashed = dotConcat(labelReverse(hashindex.rbegin()->qname), auth);       
904      }
905    }
906   
907    cerr<<"Before: '"<<before<<"', after: '"<<after<<"'\n";
908    return true;
909  }
910}
911
912void Bind2Backend::lookup(const QType &qtype, const string &qname, DNSPacket *pkt_p, int zoneId )
913{
914  d_handle.reset();
915
916  string domain=toLower(qname);
917
918  static bool mustlog=::arg().mustDo("query-logging");
919  if(mustlog) 
920    L<<Logger::Warning<<"Lookup for '"<<qtype.getName()<<"' of '"<<domain<<"'"<<endl;
921
922  shared_ptr<State> state = s_state;
923
924  name_id_map_t::const_iterator iditer;
925  while((iditer=state->name_id_map.find(domain)) == state->name_id_map.end() && chopOff(domain))
926    ;
927
928
929  if(iditer==state->name_id_map.end()) {
930    if(mustlog)
931      L<<Logger::Warning<<"Found no authoritative zone for "<<qname<<endl;
932    d_handle.d_list=false;
933    return;
934  }
935  //  unsigned int id=*iditer;
936  if(mustlog)
937    L<<Logger::Warning<<"Found a zone '"<<domain<<"' (with id " << iditer->second<<") that might contain data "<<endl;
938   
939  d_handle.id=iditer->second;
940 
941  DLOG(L<<"Bind2Backend constructing handle for search for "<<qtype.getName()<<" for "<<
942       qname<<endl);
943 
944  if(domain.empty())
945    d_handle.qname=qname;
946  else if(strcasecmp(qname.c_str(),domain.c_str()))
947    d_handle.qname=qname.substr(0,qname.size()-domain.length()-1); // strip domain name
948
949  d_handle.qtype=qtype;
950  d_handle.domain=qname.substr(qname.size()-domain.length());
951
952  BB2DomainInfo& bbd = state->id_zone_map[iditer->second];
953  if(!bbd.d_loaded) {
954    d_handle.reset();
955    throw DBException("Zone for '"+bbd.d_name+"' in '"+bbd.d_filename+"' temporarily not available (file missing, or master dead)"); // fsck
956  }
957   
958  if(!bbd.current()) {
959    L<<Logger::Warning<<"Zone '"<<bbd.d_name<<"' ("<<bbd.d_filename<<") needs reloading"<<endl;
960    queueReload(&bbd);  // how can this be safe - ok, everybody should have their own reference counted copy of 'records'
961    state = s_state;
962  }
963
964  d_handle.d_records = bbd.d_records; // give it a reference counted copy
965 
966  if(d_handle.d_records->empty())
967    DLOG(L<<"Query with no results"<<endl);
968
969  pair<recordstorage_t::const_iterator, recordstorage_t::const_iterator> range;
970
971  string lname=labelReverse(toLower(d_handle.qname));
972  // cout<<"starting equal range for: '"<<d_handle.qname<<"', search is for: '"<<lname<<"'"<<endl;
973  range=equal_range(d_handle.d_records->begin(), d_handle.d_records->end(), lname);
974  d_handle.mustlog = mustlog;
975 
976  if(range.first==range.second) {
977    // cerr<<"Found nothign!"<<endl;
978    d_handle.d_list=false;
979    d_handle.d_iter = d_handle.d_end_iter  = range.first;
980    return;
981  }
982  else {
983    // cerr<<"Found something!"<<endl;
984    d_handle.d_iter=range.first;
985    d_handle.d_end_iter=range.second;
986  }
987
988  d_handle.d_list=false;
989}
990
991Bind2Backend::handle::handle()
992{
993  mustlog=false;
994}
995
996bool Bind2Backend::get(DNSResourceRecord &r)
997{
998  if(!d_handle.d_records) {
999    if(d_handle.mustlog)
1000      L<<Logger::Warning<<"There were no answers"<<endl;
1001    return false;
1002  }
1003
1004  if(!d_handle.get(r)) {
1005    if(d_handle.mustlog)
1006      L<<Logger::Warning<<"End of answers"<<endl;
1007
1008    d_handle.reset();
1009
1010    return false;
1011  }
1012  if(d_handle.mustlog)
1013    L<<Logger::Warning<<"Returning: '"<<r.qtype.getName()<<"' of '"<<r.qname<<"', content: '"<<r.content<<"', prio: "<<r.priority<<endl;
1014  return true;
1015}
1016
1017bool Bind2Backend::handle::get(DNSResourceRecord &r)
1018{
1019  if(d_list)
1020    return get_list(r);
1021  else
1022    return get_normal(r);
1023}
1024
1025//#define DLOG(x) x
1026bool Bind2Backend::handle::get_normal(DNSResourceRecord &r)
1027{
1028  DLOG(L << "Bind2Backend get() was called for "<<qtype.getName() << " record for '"<<
1029       qname<<"' - "<<d_records->size()<<" available in total!"<<endl);
1030 
1031  if(d_iter==d_end_iter) {
1032    return false;
1033  }
1034
1035  while(d_iter!=d_end_iter && !(qtype.getCode()==QType::ANY || (d_iter)->qtype==qtype.getCode())) {
1036    DLOG(L<<Logger::Warning<<"Skipped "<<qname<<"/"<<QType(d_iter->qtype).getName()<<": '"<<d_iter->content<<"'"<<endl);
1037    d_iter++;
1038  }
1039  if(d_iter==d_end_iter) {
1040    return false;
1041  }
1042  DLOG(L << "Bind2Backend get() returning a rr with a "<<QType(d_iter->qtype).getCode()<<endl);
1043
1044  r.qname=qname.empty() ? domain : (qname+"."+domain);
1045  r.domain_id=id;
1046  r.content=(d_iter)->content;
1047  //  r.domain_id=(d_iter)->domain_id;
1048  r.qtype=(d_iter)->qtype;
1049  r.ttl=(d_iter)->ttl;
1050  r.priority=(d_iter)->priority;
1051
1052  if(!d_iter->auth && r.qtype.getCode() != QType::A && r.qtype.getCode()!=QType::AAAA && r.qtype.getCode() != QType::NS)
1053    cerr<<"Warning! Unauth response!"<<endl;
1054  r.auth = d_iter->auth;
1055
1056  d_iter++;
1057
1058  return true;
1059}
1060
1061bool Bind2Backend::list(const string &target, int id)
1062{
1063  shared_ptr<State> state = s_state;
1064  if(!state->id_zone_map.count(id))
1065    return false;
1066
1067  d_handle.reset(); 
1068  DLOG(L<<"Bind2Backend constructing handle for list of "<<id<<endl);
1069
1070  d_handle.d_records=state->id_zone_map[id].d_records; // give it a copy, which will stay around
1071  d_handle.d_qname_iter= d_handle.d_records->begin();
1072  d_handle.d_qname_end=d_handle.d_records->end();   // iter now points to a vector of pointers to vector<BBResourceRecords>
1073
1074  d_handle.id=id;
1075  d_handle.d_list=true;
1076  return true;
1077
1078}
1079
1080bool Bind2Backend::handle::get_list(DNSResourceRecord &r)
1081{
1082  if(d_qname_iter!=d_qname_end) {
1083    r.qname=d_qname_iter->qname.empty() ? domain : (labelReverse(d_qname_iter->qname)+"."+domain);
1084    r.domain_id=id;
1085    r.content=(d_qname_iter)->content;
1086    r.qtype=(d_qname_iter)->qtype;
1087    r.ttl=(d_qname_iter)->ttl;
1088    r.priority=(d_qname_iter)->priority;
1089    r.auth = d_qname_iter->auth;
1090    d_qname_iter++;
1091    return true;
1092  }
1093  return false;
1094
1095}
1096
1097// this function really is too slow
1098bool Bind2Backend::isMaster(const string &name, const string &ip)
1099{
1100  shared_ptr<State> state = getState(); 
1101  for(id_zone_map_t::iterator j=state->id_zone_map.begin(); j!=state->id_zone_map.end();++j) {
1102    if(j->second.d_name==name) {
1103      for(vector<string>::const_iterator iter = j->second.d_masters.begin(); iter != j->second.d_masters.end(); ++iter)
1104        if(*iter==ip)
1105          return true;
1106    }
1107  }
1108  return false;
1109}
1110
1111bool Bind2Backend::superMasterBackend(const string &ip, const string &domain, const vector<DNSResourceRecord>&nsset, string *account, DNSBackend **db)
1112{
1113  // Check whether we have a configfile available.
1114  if (getArg("supermaster-config").empty())
1115    return false;
1116
1117  ifstream c_if(getArg("supermasters").c_str(), ios::in); // this was nocreate?
1118  if (!c_if) {
1119    L << Logger::Error << "Unable to open supermasters file for read: " << stringerror() << endl;
1120    return false;
1121  }
1122
1123  // Format:
1124  // <ip> <accountname>
1125  string line, sip, saccount;
1126  while (getline(c_if, line)) {
1127    istringstream ii(line);
1128    ii >> sip;
1129    if (sip == ip) {
1130      ii >> saccount;
1131      break;
1132    }
1133  } 
1134  c_if.close();
1135
1136  if (sip != ip)  // ip not found in authorization list - reject
1137    return false;
1138 
1139  // ip authorized as supermaster - accept
1140  *db = this;
1141  if (saccount.length() > 0)
1142      *account = saccount.c_str();
1143
1144  return true;
1145}
1146
1147bool Bind2Backend::createSlaveDomain(const string &ip, const string &domain, const string &account)
1148{
1149  // Interference with loadConfig(), use locking
1150  Lock l(&s_state_lock);
1151
1152  string filename = getArg("supermaster-destdir")+'/'+domain;
1153 
1154  L << Logger::Warning << d_logprefix
1155    << " Writing bind config zone statement for superslave zone '" << domain
1156    << "' from supermaster " << ip << endl;
1157       
1158  ofstream c_of(getArg("supermaster-config").c_str(),  ios::app);
1159  if (!c_of) {
1160    L << Logger::Error << "Unable to open supermaster configfile for append: " << stringerror() << endl;
1161    throw DBException("Unable to open supermaster configfile for append: "+stringerror());
1162  }
1163 
1164  c_of << endl;
1165  c_of << "# Superslave zone " << domain << " (added: " << nowTime() << ") (account: " << account << ')' << endl;
1166  c_of << "zone \"" << domain << "\" {" << endl;
1167  c_of << "\ttype slave;" << endl;
1168  c_of << "\tfile \"" << filename << "\";" << endl;
1169  c_of << "\tmasters { " << ip << "; };" << endl;
1170  c_of << "};" << endl;
1171  c_of.close();
1172
1173  int newid=0;
1174  // Find a free zone id nr. 
1175 
1176  if (!s_state->id_zone_map.empty()) {
1177    id_zone_map_t::reverse_iterator i = s_state->id_zone_map.rbegin();
1178    newid = i->second.d_id + 1;
1179  }
1180 
1181  BB2DomainInfo &bbd = s_state->id_zone_map[newid];
1182
1183  bbd.d_records = shared_ptr<recordstorage_t >(new recordstorage_t);
1184  bbd.d_name = domain;
1185  bbd.setCheckInterval(getArgAsNum("check-interval"));
1186  bbd.d_masters.push_back(ip);
1187  bbd.d_filename = filename;
1188
1189  s_state->name_id_map[domain] = bbd.d_id;
1190 
1191  return true;
1192}
1193
1194class Bind2Factory : public BackendFactory
1195{
1196   public:
1197      Bind2Factory() : BackendFactory("bind") {}
1198
1199      void declareArguments(const string &suffix="")
1200      {
1201         declare(suffix,"config","Location of named.conf","");
1202         //         declare(suffix,"example-zones","Install example zones","no");
1203         declare(suffix,"check-interval","Interval for zonefile changes","0");
1204         declare(suffix,"supermaster-config","Location of (part of) named.conf where pdns can write zone-statements to","");
1205         declare(suffix,"supermasters","List of IP-addresses of supermasters","");
1206         declare(suffix,"supermaster-destdir","Destination directory for newly added slave zones",::arg()["config-dir"]);
1207      }
1208
1209      DNSBackend *make(const string &suffix="")
1210      {
1211         return new Bind2Backend(suffix);
1212      }
1213};
1214
1215//! Magic class that is activated when the dynamic library is loaded
1216class Bind2Loader
1217{
1218public:
1219  Bind2Loader()
1220  {
1221    BackendMakers().report(new Bind2Factory);
1222    L<<Logger::Notice<<"[Bind2Backend] This is the bind backend version "VERSION" ("__DATE__", "__TIME__") reporting"<<endl;
1223  }
1224};
1225static Bind2Loader bind2loader;
Note: See TracBrowser for help on using the browser.