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

Revision 1400, 31.0 KB (checked in by ahu, 4 years ago)

Aki Tuomi fixed up the primordial 'alsoNotify' infrastructure, and implemented also-notify (both global and per-zone) for the BIND backend.

  • 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 - 2008  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>
31using namespace std;
32
33#include "dns.hh"
34#include "dnsbackend.hh"
35#include "bindbackend2.hh"
36#include "dnspacket.hh"
37#include "zoneparser-tng.hh"
38#include "bindparser.hh"
39#include "logger.hh"
40#include "arguments.hh"
41#include "qtype.hh"
42#include "misc.hh"
43#include "dynlistener.hh"
44#include "lock.hh"
45using namespace std;
46
47/** new scheme of things:
48    we have zone-id map
49    a zone-id has a vector of DNSResourceRecords
50    on start of query, we find the best zone to answer from
51*/
52
53// this map contains BB2DomainInfo structs, each of which contains a *pointer* to domain data
54shared_ptr<Bind2Backend::State> Bind2Backend::s_state;
55
56/* 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.
57   Then there is the name_id_map, which allows us to map a query to a zone id.
58
59   The s_state is never written to, and it is a reference counted shared_ptr. Any function which needs to access the state
60   should do so by making a shared_ptr copy of it, and do all its work on that copy.
61
62   When I said s_state is never written to, I lied. No elements are ever added to it, or removed from it.
63   Its values however may be changed, but not the keys.
64
65   When it is necessary to change the State, a deep copy is made, which is changed. Afterwards,
66   the s_state pointer is made to point to the new State.
67
68   Anybody who is currently accessing the original holds a reference counted handle (shared_ptr) to it, which means it will stay around
69   To save memory, we hold the records as a shared_ptr as well.
70
71   Changes made to s_state directly should take the s_state_lock, so as to prevent writing to a stale copy.
72*/
73
74int Bind2Backend::s_first=1;
75
76pthread_mutex_t Bind2Backend::s_startup_lock=PTHREAD_MUTEX_INITIALIZER;
77pthread_mutex_t Bind2Backend::s_state_lock=PTHREAD_MUTEX_INITIALIZER;
78string Bind2Backend::s_binddirectory; 
79/* when a query comes in, we find the most appropriate zone and answer from that */
80
81BB2DomainInfo::BB2DomainInfo()
82{
83  d_loaded=false;
84  d_lastcheck=0;
85  d_checknow=false;
86  d_status="Unknown";
87}
88
89void BB2DomainInfo::setCheckInterval(time_t seconds)
90{
91  d_checkinterval=seconds;
92}
93
94bool BB2DomainInfo::current()
95{
96  if(d_checknow)
97    return false;
98
99  if(!d_checkinterval) 
100    return true;
101
102  if(time(0) - d_lastcheck < d_checkinterval)
103    return true;
104 
105  if(d_filename.empty())
106    return true;
107
108  return (getCtime()==d_ctime);
109}
110
111time_t BB2DomainInfo::getCtime()
112{
113  struct stat buf;
114 
115  if(d_filename.empty() || stat(d_filename.c_str(),&buf)<0)
116    return 0; 
117  d_lastcheck=time(0);
118  return buf.st_ctime;
119}
120
121void BB2DomainInfo::setCtime()
122{
123  struct stat buf;
124  if(stat(d_filename.c_str(),&buf)<0)
125    return; 
126  d_ctime=buf.st_ctime;
127}
128
129void Bind2Backend::setNotified(uint32_t id, uint32_t serial)
130{
131  Lock l(&s_state_lock);
132  s_state->id_zone_map[id].d_lastnotified=serial;
133}
134
135void Bind2Backend::setFresh(uint32_t domain_id)
136{
137  Lock l(&s_state_lock);
138  s_state->id_zone_map[domain_id].d_lastcheck=time(0);
139}
140
141bool Bind2Backend::startTransaction(const string &qname, int id)
142{
143  shared_ptr<State> state = s_state; // is only read from
144
145  const BB2DomainInfo &bbd=state->id_zone_map[d_transaction_id=id];
146
147  d_transaction_tmpname=bbd.d_filename+"."+itoa(random());
148  d_of=new ofstream(d_transaction_tmpname.c_str());
149  if(!*d_of) {
150    throw DBException("Unable to open temporary zonefile '"+d_transaction_tmpname+"': "+stringerror());
151    unlink(d_transaction_tmpname.c_str());
152    delete d_of;
153    d_of=0;
154  }
155 
156  *d_of<<"; Written by PowerDNS, don't edit!"<<endl;
157  *d_of<<"; Zone '"+bbd.d_name+"' retrieved from master "<<endl<<"; at "<<nowTime()<<endl; // insert master info here again
158
159  return true;
160}
161
162bool Bind2Backend::commitTransaction()
163{
164  delete d_of;
165  d_of=0;
166  shared_ptr<State> state = s_state;
167
168  // this might fail if s_state was cycled during the AXFR
169  if(rename(d_transaction_tmpname.c_str(), state->id_zone_map[d_transaction_id].d_filename.c_str())<0)
170    throw DBException("Unable to commit (rename to: '" + state->id_zone_map[d_transaction_id].d_filename+"') AXFRed zone: "+stringerror());
171
172  queueReload(&state->id_zone_map[d_transaction_id]);
173
174  d_transaction_id=0;
175
176  return true;
177}
178
179bool Bind2Backend::abortTransaction()
180{
181  if(d_transaction_id) {
182    delete d_of;
183    d_of=0;
184    unlink(d_transaction_tmpname.c_str());
185    d_transaction_id=0;
186  }
187
188  return true;
189}
190
191bool Bind2Backend::feedRecord(const DNSResourceRecord &r)
192{
193  string qname=r.qname;
194
195  const shared_ptr<State> state = s_state;
196  string domain = state->id_zone_map[d_transaction_id].d_name;
197
198  if(!stripDomainSuffix(&qname,domain)) 
199    throw DBException("out-of-zone data '"+qname+"' during AXFR of zone '"+domain+"'");
200
201  string content=r.content;
202
203  // SOA needs stripping too! XXX FIXME - also, this should not be here I think
204  switch(r.qtype.getCode()) {
205  case QType::MX:
206    if(!stripDomainSuffix(&content, domain))
207      content+=".";
208  case QType::SRV:
209    *d_of<<qname<<"\t"<<r.ttl<<"\t"<<r.qtype.getName()<<"\t"<<r.priority<<"\t"<<content<<endl;
210    break;
211  case QType::CNAME:
212  case QType::NS:
213    if(!stripDomainSuffix(&content, domain))
214      content+=".";
215    *d_of<<qname<<"\t"<<r.ttl<<"\t"<<r.qtype.getName()<<"\t"<<content<<endl;
216    break;
217  default:
218    *d_of<<qname<<"\t"<<r.ttl<<"\t"<<r.qtype.getName()<<"\t"<<r.content<<endl;
219    break;
220  }
221
222  return true;
223}
224
225void Bind2Backend::getUpdatedMasters(vector<DomainInfo> *changedDomains)
226{
227  SOAData soadata;
228  shared_ptr<State> state = s_state;
229
230  for(id_zone_map_t::const_iterator i = state->id_zone_map.begin(); i != state->id_zone_map.end() ; ++i) {
231    if(!i->second.d_masters.empty() && this->alsoNotify.empty() && i->second.d_also_notify.empty())
232      continue;
233    soadata.serial=0;
234    try {
235      this->getSOA(i->second.d_name, soadata); // we might not *have* a SOA yet, but this might trigger a load of it
236    }
237    catch(...){}
238    DomainInfo di;
239    di.id=i->first;
240    di.serial=soadata.serial;
241    di.zone=i->second.d_name;
242    di.last_check=i->second.d_lastcheck;
243    di.backend=this;
244    di.kind=DomainInfo::Master;
245    if(!i->second.d_lastnotified)  {          // don't do notification storm on startup
246      Lock l(&s_state_lock);
247      s_state->id_zone_map[i->first].d_lastnotified=soadata.serial;
248    }
249    else
250      if(soadata.serial!=i->second.d_lastnotified)
251        changedDomains->push_back(di);
252  }
253}
254
255void Bind2Backend::getUnfreshSlaveInfos(vector<DomainInfo> *unfreshDomains)
256{
257  shared_ptr<State> state = s_state;
258  for(id_zone_map_t::const_iterator i = state->id_zone_map.begin(); i != state->id_zone_map.end() ; ++i) {
259    if(i->second.d_masters.empty())
260      continue;
261    DomainInfo sd;
262    sd.id=i->first;
263    sd.zone=i->second.d_name;
264    sd.masters=i->second.d_masters;
265    sd.last_check=i->second.d_lastcheck;
266    sd.backend=this;
267    sd.kind=DomainInfo::Slave;
268    SOAData soadata;
269    soadata.serial=0;
270    soadata.refresh=0;
271    soadata.serial=0;
272    soadata.db=(DNSBackend *)-1; // not sure if this is useful, inhibits any caches that might be around
273    try {
274      getSOA(i->second.d_name,soadata); // we might not *have* a SOA yet
275    }
276    catch(...){}
277    sd.serial=soadata.serial;
278    if(sd.last_check+soadata.refresh<(unsigned int)time(0))
279      unfreshDomains->push_back(sd);   
280  }
281}
282
283bool Bind2Backend::getDomainInfo(const string &domain, DomainInfo &di)
284{
285  shared_ptr<State> state = s_state;
286  for(id_zone_map_t::const_iterator i = state->id_zone_map.begin(); i != state->id_zone_map.end() ; ++i) {
287    if(i->second.d_name==domain) {
288      di.id=i->first;
289      di.zone=domain;
290      di.masters=i->second.d_masters;
291      di.last_check=i->second.d_lastcheck;
292      di.backend=this;
293      di.kind=i->second.d_masters.empty() ? DomainInfo::Master : DomainInfo::Slave;
294      di.serial=0;
295      try {
296        SOAData sd;
297        sd.serial=0;
298       
299        getSOA(i->second.d_name,sd); // we might not *have* a SOA yet
300        di.serial=sd.serial;
301      }
302      catch(...){}
303
304      return true;
305    }
306  }
307  return false;
308}
309
310void Bind2Backend::alsoNotifies(const string &domain, set<string> *ips)
311{
312  shared_ptr<State> state = s_state;
313  // combine global list with local list
314  for(set<string>::iterator i = this->alsoNotify.begin(); i != this->alsoNotify.end(); i++) {
315    (*ips).insert(*i);
316  }
317  for(id_zone_map_t::const_iterator i = state->id_zone_map.begin(); i != state->id_zone_map.end() ; ++i) {
318    if(i->second.d_name==domain) {
319      for(set<string>::iterator it = i->second.d_also_notify.begin(); it != i->second.d_also_notify.end(); it++) {
320        (*ips).insert(*it);
321      }
322      return;
323    }
324  }   
325}
326
327//! lowercase, strip trailing .
328static string canonic(string ret)
329{
330  string::iterator i;
331
332  for(i=ret.begin();
333      i!=ret.end();
334      ++i)
335    *i=tolower(*i);
336
337
338  if(*(i-1)=='.')
339    ret.resize(i-ret.begin()-1);
340  return ret;
341}
342
343set<string> contents;
344
345/** THIS IS AN INTERNAL FUNCTION! It does moadnsparser prio impedence matching
346    This function adds a record to a domain with a certain id.
347    Much of the complication is due to the efforts to benefit from std::string reference counting copy on write semantics */
348void Bind2Backend::insert(shared_ptr<State> stage, int id, const string &qnameu, const QType &qtype, const string &content, int ttl=300, int prio=25)
349{
350  BB2DomainInfo bb2 = stage->id_zone_map[id];
351  Bind2DNSRecord bdr;
352
353  vector<Bind2DNSRecord>& records=*bb2.d_records; 
354
355  bdr.qname=toLower(canonic(qnameu));
356  if(bb2.d_name.empty())
357    ;
358  else if(bdr.qname==toLower(bb2.d_name))
359    bdr.qname.clear();
360  else if(bdr.qname.length() > bb2.d_name.length())
361    bdr.qname.resize(bdr.qname.length() - (bb2.d_name.length() + 1));
362  else
363    throw AhuException("Trying to insert non-zone data, name='"+bdr.qname+"', qtype="+qtype.getName()+", zone='"+bb2.d_name+"'");
364
365  bdr.qname.swap(bdr.qname);
366
367  if(!records.empty() && bdr.qname==(records.end()-1)->qname)
368    bdr.qname=(records.end()-1)->qname;
369
370  bdr.qtype=qtype.getCode();
371  bdr.content=content; 
372
373  if(bdr.qtype == QType::MX || bdr.qtype == QType::SRV) { 
374    prio=atoi(bdr.content.c_str());
375   
376    string::size_type pos = bdr.content.find_first_not_of("0123456789");
377    if(pos != string::npos)
378      erase_head(bdr.content, pos);
379    trim_left(bdr.content);
380  }
381 
382  if(bdr.qtype==QType::CNAME || bdr.qtype==QType::MX || bdr.qtype==QType::NS || bdr.qtype==QType::AFSDB)
383    bdr.content=canonic(bdr.content); // I think this is wrong, the zoneparser should not come up with . terminated stuff XXX FIXME
384
385  set<string>::const_iterator i=contents.find(bdr.content);
386  if(i!=contents.end())
387   bdr.content=*i;
388  else {
389    contents.insert(bdr.content);
390  }
391
392  bdr.ttl=ttl;
393  bdr.priority=prio;
394 
395  records.push_back(bdr);
396}
397
398void Bind2Backend::reload()
399{
400  Lock l(&s_state_lock);
401  for(id_zone_map_t::iterator i = s_state->id_zone_map.begin(); i != s_state->id_zone_map.end(); ++i) 
402    i->second.d_checknow=true;
403}
404
405string Bind2Backend::DLReloadNowHandler(const vector<string>&parts, Utility::pid_t ppid)
406{
407  shared_ptr<State> state = s_state;
408  ostringstream ret;
409
410  for(vector<string>::const_iterator i=parts.begin()+1;i<parts.end();++i) {
411    if(state->name_id_map.count(*i)) {
412      BB2DomainInfo& bbd=state->id_zone_map[state->name_id_map[*i]];
413     
414      queueReload(&bbd);
415      ret<< *i << ": "<< (bbd.d_loaded ? "": "[rejected]") <<"\t"<<bbd.d_status<<"\n";     
416    }
417    else
418      ret<< *i << " no such domain\n";
419  }   
420  if(ret.str().empty())
421    ret<<"no domains reloaded";
422  return ret.str();
423}
424
425
426string Bind2Backend::DLDomStatusHandler(const vector<string>&parts, Utility::pid_t ppid)
427{
428  ostringstream ret;
429  shared_ptr<State> state = s_state;;
430     
431  if(parts.size() > 1) {
432    for(vector<string>::const_iterator i=parts.begin()+1;i<parts.end();++i) {
433      if(state->name_id_map.count(*i)) {
434        BB2DomainInfo& bbd=state->id_zone_map[state->name_id_map[*i]];  // XXX s_name_id_map needs trick as well
435        ret<< *i << ": "<< (bbd.d_loaded ? "": "[rejected]") <<"\t"<<bbd.d_status<<"\n";     
436    }
437      else
438        ret<< *i << " no such domain\n";
439    }   
440  }
441  else
442    for(id_zone_map_t::iterator i=state->id_zone_map.begin(); i!=state->id_zone_map.end(); ++i) 
443      ret<< i->second.d_name << ": "<< (i->second.d_loaded ? "": "[rejected]") <<"\t"<<i->second.d_status<<"\n";     
444
445  if(ret.str().empty())
446    ret<<"no domains passed";
447
448  return ret.str();
449}
450
451
452string Bind2Backend::DLListRejectsHandler(const vector<string>&parts, Utility::pid_t ppid)
453{
454  shared_ptr<State> state = s_state;
455
456  ostringstream ret;
457  for(id_zone_map_t::iterator j = state->id_zone_map.begin(); j != state->id_zone_map.end(); ++j) 
458    if(!j->second.d_loaded)
459      ret<<j->second.d_name<<"\t"<<j->second.d_status<<endl;
460       
461  return ret.str();
462}
463
464Bind2Backend::Bind2Backend(const string &suffix)
465{
466#if __GNUC__ >= 3
467    ios_base::sync_with_stdio(false);
468#endif
469  d_logprefix="[bind"+suffix+"backend]";
470  setArgPrefix("bind"+suffix);
471  Lock l(&s_startup_lock);
472
473  d_transaction_id=0;
474  if(!s_first) {
475    return;
476  }
477  s_first=0;
478  s_state = shared_ptr<State>(new State);
479  loadConfig();
480
481  extern DynListener *dl;
482  dl->registerFunc("BIND-RELOAD-NOW", &DLReloadNowHandler);
483  dl->registerFunc("BIND-DOMAIN-STATUS", &DLDomStatusHandler);
484  dl->registerFunc("BIND-LIST-REJECTS", &DLListRejectsHandler);
485}
486
487Bind2Backend::~Bind2Backend()
488{
489
490}
491
492void Bind2Backend::rediscover(string *status)
493{
494  loadConfig(status);
495}
496
497static void prefetchFile(const std::string& fname)
498{
499#if 0
500  static int fd;
501  if(fd > 0)
502    close(fd);
503  fd=open(fname.c_str(), O_RDONLY);
504  if(fd < 0)
505    return;
506
507  posix_fadvise(fd, 0, 0, POSIX_FADV_WILLNEED);
508#endif
509}
510
511void Bind2Backend::loadConfig(string* status)
512{
513  // Interference with createSlaveDomain()
514  Lock l(&s_state_lock);
515 
516  static int domain_id=1;
517
518  shared_ptr<State> staging = shared_ptr<State>(new State);
519
520  if(!getArg("config").empty()) {
521    BindParser BP;
522    try {
523      BP.parse(getArg("config"));
524    }
525    catch(AhuException &ae) {
526      L<<Logger::Error<<"Error parsing bind configuration: "<<ae.reason<<endl;
527      throw;
528    }
529     
530    vector<BindDomainInfo> domains=BP.getDomains();
531    this->alsoNotify = BP.getAlsoNotify();
532
533    s_binddirectory=BP.getDirectory();
534    //    ZP.setDirectory(d_binddirectory);
535
536    L<<Logger::Warning<<d_logprefix<<" Parsing "<<domains.size()<<" domain(s), will report when done"<<endl;
537   
538    int rejected=0;
539    int newdomains=0;
540
541    //    random_shuffle(domains.begin(), domains.end());
542    struct stat st;
543     
544    for(vector<BindDomainInfo>::iterator i=domains.begin(); i!=domains.end(); ++i) 
545    {
546      if(stat(i->filename.c_str(), &st) == 0) {
547        i->d_dev = st.st_dev;
548        i->d_ino = st.st_ino;
549      }
550    }
551
552    sort(domains.begin(), domains.end()); // put stuff in inode order
553
554    for(vector<BindDomainInfo>::const_iterator i=domains.begin();
555        i!=domains.end();
556        ++i) 
557      {
558        if(i->type!="master" && i->type!="slave") {
559          L<<Logger::Warning<<d_logprefix<<" Warning! Skipping '"<<i->type<<"' zone '"<<i->name<<"'"<<endl;
560          continue;
561        }
562
563        BB2DomainInfo* bbd=0;
564
565        if(!s_state->name_id_map.count(i->name)) { // is it fully new?
566          bbd=&staging->id_zone_map[domain_id];
567          bbd->d_id=domain_id++;
568       
569          // this isn't necessary, we do this on the actual load
570          //      bbd->d_records=shared_ptr<vector<Bind2DNSRecord> > (new vector<Bind2DNSRecord>);
571
572          bbd->setCheckInterval(getArgAsNum("check-interval"));
573          bbd->d_lastnotified=0;
574          bbd->d_loaded=false;
575        }
576        else {  // no, we knew about it already
577          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
578          bbd = &staging->id_zone_map[s_state->name_id_map[i->name]];
579        }
580       
581        staging->name_id_map[i->name]=bbd->d_id; // fill out name -> id map
582
583        // overwrite what we knew about the domain
584        bbd->d_name=i->name;
585
586        bool filenameChanged = (bbd->d_filename!=i->filename);
587        bbd->d_filename=i->filename;
588        bbd->d_masters=i->masters;
589        bbd->d_also_notify=i->alsoNotify;
590       
591        if(filenameChanged || !bbd->d_loaded || !bbd->current()) {
592          L<<Logger::Info<<d_logprefix<<" parsing '"<<i->name<<"' from file '"<<i->filename<<"'"<<endl;
593         
594          try {
595            // we need to allocate a new vector so we don't kill the original, which is still in use!
596            bbd->d_records=shared_ptr<vector<Bind2DNSRecord> > (new vector<Bind2DNSRecord>); 
597
598            ZoneParserTNG zpt(i->filename, i->name, BP.getDirectory());
599            DNSResourceRecord rr;
600            while(zpt.get(rr)) {
601              insert(staging, bbd->d_id, rr.qname, rr.qtype, rr.content, rr.ttl, rr.priority);
602            }
603
604            //      ZP.parse(i->filename, i->name, bbd->d_id); // calls callback for us
605            //      L<<Logger::Info<<d_logprefix<<" sorting '"<<i->name<<"'"<<endl;
606
607            sort(staging->id_zone_map[bbd->d_id].d_records->begin(), staging->id_zone_map[bbd->d_id].d_records->end());
608           
609            staging->id_zone_map[bbd->d_id].setCtime();
610            staging->id_zone_map[bbd->d_id].d_loaded=true; 
611            staging->id_zone_map[bbd->d_id].d_status="parsed into memory at "+nowTime();
612           
613            contents.clear();
614            //  s_stage->id_zone_map[bbd->d_id].d_records->swap(*s_staging_zone_map[bbd->d_id].d_records);
615          }
616          catch(AhuException &ae) {
617            ostringstream msg;
618            msg<<" error at "+nowTime()+" parsing '"<<i->name<<"' from file '"<<i->filename<<"': "<<ae.reason;
619
620            if(status)
621              *status+=msg.str();
622            staging->id_zone_map[bbd->d_id].d_status=msg.str();
623            L<<Logger::Warning<<d_logprefix<<msg.str()<<endl;
624            rejected++;
625          }
626          catch(std::exception &ae) {
627            ostringstream msg;
628            msg<<" error at "+nowTime()+" parsing '"<<i->name<<"' from file '"<<i->filename<<"': "<<ae.what();
629
630            if(status)
631              *status+=msg.str();
632            staging->id_zone_map[bbd->d_id].d_status=msg.str();
633            L<<Logger::Warning<<d_logprefix<<msg.str()<<endl;
634            rejected++;
635          }
636        }
637        /*
638        vector<vector<BBResourceRecord> *>&tmp=d_zone_id_map[bbd.d_id];  // shrink trick
639        vector<vector<BBResourceRecord> *>(tmp).swap(tmp);
640        */
641      }
642
643    // figure out which domains were new and which vanished
644    int remdomains=0;
645    set<string> oldnames, newnames;
646    for(id_zone_map_t::const_iterator j=s_state->id_zone_map.begin();j != s_state->id_zone_map.end();++j) {
647      oldnames.insert(j->second.d_name);
648    }
649    for(id_zone_map_t::const_iterator j=staging->id_zone_map.begin(); j!= staging->id_zone_map.end(); ++j) {
650      newnames.insert(j->second.d_name);
651    }
652
653    vector<string> diff;
654    set_difference(oldnames.begin(), oldnames.end(), newnames.begin(), newnames.end(), back_inserter(diff));
655    remdomains=diff.size();
656
657#if 0       
658    // remove domains from the *name* map, delete their pointer
659    for(vector<string>::const_iterator k=diff.begin();k!=diff.end(); ++k) {
660      L<<Logger::Error<<"Removing domain: "<<*k<<endl;
661      s_state->name_id_map.erase(*k);
662    }
663
664    // now remove from the s_state->id_zone_map
665    for(id_zone_map_t::iterator j=s_state->id_zone_map.begin();j!=s_state->id_zone_map.end();++j) { // O(N*M)
666      for(vector<string>::const_iterator k=diff.begin();k!=diff.end();++k)
667        if(j->second.d_name==*k) {
668          L<<Logger::Error<<"Removing records from zone '"<<j->second.d_name<<"' from memory"<<endl;
669
670          j->second.d_loaded=false;
671          nukeZoneRecords(&j->second);
672
673          break;
674        }
675    }
676#endif
677
678    // count number of entirely new domains
679    vector<string> diff2;
680    set_difference(newnames.begin(), newnames.end(), oldnames.begin(), oldnames.end(), back_inserter(diff2));
681    newdomains=diff2.size();
682   
683    s_state.swap(staging); // and boy do we hope this is a threadsafe operation!
684
685    // report
686    ostringstream msg;
687    msg<<" Done parsing domains, "<<rejected<<" rejected, "<<newdomains<<" new, "<<remdomains<<" removed"; 
688    if(status)
689      *status=msg.str();
690
691    L<<Logger::Error<<d_logprefix<<msg.str()<<endl;
692  }
693}
694
695/** nuke all records from memory, keep bbd intact though. */
696void Bind2Backend::nukeZoneRecords(BB2DomainInfo *bbd)
697{
698  bbd->d_loaded=0; // block further access
699  bbd->d_records = shared_ptr<vector<Bind2DNSRecord> > (new vector<Bind2DNSRecord>);
700}
701
702
703void Bind2Backend::queueReload(BB2DomainInfo *bbd)
704{
705  Lock l(&s_state_lock);
706
707  shared_ptr<State> staging(new State);
708
709  // we reload *now* for the time being
710
711  try {
712    nukeZoneRecords(bbd); // ? do we need this?
713    staging->id_zone_map[bbd->d_id]=s_state->id_zone_map[bbd->d_id];
714    staging->id_zone_map[bbd->d_id].d_records=shared_ptr<vector<Bind2DNSRecord> > (new vector<Bind2DNSRecord>);  // nuke it
715
716    ZoneParserTNG zpt(bbd->d_filename, bbd->d_name, s_binddirectory);
717    DNSResourceRecord rr;
718    while(zpt.get(rr)) {
719      insert(staging, bbd->d_id, rr.qname, rr.qtype, rr.content, rr.ttl, rr.priority);
720    }
721       
722    sort(staging->id_zone_map[bbd->d_id].d_records->begin(), staging->id_zone_map[bbd->d_id].d_records->end());
723    staging->id_zone_map[bbd->d_id].setCtime();
724   
725    contents.clear();
726
727    s_state->id_zone_map[bbd->d_id]=staging->id_zone_map[bbd->d_id]; // move over
728
729    bbd->setCtime();
730    // and raise d_loaded again!
731    bbd->d_loaded=1;
732    bbd->d_checknow=0;
733    bbd->d_status="parsed into memory at "+nowTime();
734    L<<Logger::Warning<<"Zone '"<<bbd->d_name<<"' ("<<bbd->d_filename<<") reloaded"<<endl;
735  }
736  catch(AhuException &ae) {
737    ostringstream msg;
738    msg<<" error at "+nowTime()+" parsing '"<<bbd->d_name<<"' from file '"<<bbd->d_filename<<"': "<<ae.reason;
739    bbd->d_status=msg.str();
740  }
741  catch(std::exception &ae) {
742    ostringstream msg;
743    msg<<" error at "+nowTime()+" parsing '"<<bbd->d_name<<"' from file '"<<bbd->d_filename<<"': "<<ae.what();
744    bbd->d_status=msg.str();
745  }
746}
747
748
749
750void Bind2Backend::lookup(const QType &qtype, const string &qname, DNSPacket *pkt_p, int zoneId )
751{
752  d_handle.reset();
753
754  string domain=toLower(qname);
755
756  static bool mustlog=::arg().mustDo("query-logging");
757  if(mustlog) 
758    L<<Logger::Warning<<"Lookup for '"<<qtype.getName()<<"' of '"<<domain<<"'"<<endl;
759
760  shared_ptr<State> state = s_state;
761
762  name_id_map_t::const_iterator iditer;
763  while((iditer=state->name_id_map.find(domain)) == state->name_id_map.end() && chopOff(domain))
764    ;
765
766
767  if(iditer==state->name_id_map.end()) {
768    if(mustlog)
769      L<<Logger::Warning<<"Found no authoritative zone for "<<qname<<endl;
770    d_handle.d_list=false;
771    return;
772  }
773  //  unsigned int id=*iditer;
774  if(mustlog)
775    L<<Logger::Warning<<"Found a zone '"<<domain<<"' (with id " << iditer->second<<") that might contain data "<<endl;
776   
777  d_handle.id=iditer->second;
778 
779  DLOG(L<<"Bind2Backend constructing handle for search for "<<qtype.getName()<<" for "<<
780       qname<<endl);
781 
782  if(domain.empty())
783    d_handle.qname=qname;
784  else if(strcasecmp(qname.c_str(),domain.c_str()))
785    d_handle.qname=qname.substr(0,qname.size()-domain.length()-1); // strip domain name
786
787  d_handle.qtype=qtype;
788  d_handle.domain=qname.substr(qname.size()-domain.length());
789
790  BB2DomainInfo& bbd = state->id_zone_map[iditer->second];
791  if(!bbd.d_loaded) {
792    d_handle.reset();
793    throw DBException("Zone for '"+bbd.d_name+"' in '"+bbd.d_filename+"' temporarily not available (file missing, or master dead)"); // fsck
794  }
795   
796  if(!bbd.current()) {
797    L<<Logger::Warning<<"Zone '"<<bbd.d_name<<"' ("<<bbd.d_filename<<") needs reloading"<<endl;
798    queueReload(&bbd);  // how can this be safe - ok, everybody should have their own reference counted copy of 'records'
799    state = s_state;
800  }
801
802  d_handle.d_records = bbd.d_records; // give it a reference counted copy
803 
804  if(d_handle.d_records->empty())
805    DLOG(L<<"Query with no results"<<endl);
806
807  pair<vector<Bind2DNSRecord>::const_iterator, vector<Bind2DNSRecord>::const_iterator> range;
808
809  //  cout<<"starting equal range for: '"<<d_handle.qname<<"'"<<endl;
810 
811  string lname=toLower(d_handle.qname);
812  range=equal_range(d_handle.d_records->begin(), d_handle.d_records->end(), lname);
813  d_handle.mustlog = mustlog;
814 
815  if(range.first==range.second) {
816    d_handle.d_list=false;
817    return;
818  }
819  else {
820    d_handle.d_iter=range.first;
821    d_handle.d_end_iter=range.second;
822  }
823
824  d_handle.d_list=false;
825}
826
827Bind2Backend::handle::handle()
828{
829  mustlog=false;
830}
831
832bool Bind2Backend::get(DNSResourceRecord &r)
833{
834  if(!d_handle.d_records) {
835    if(d_handle.mustlog)
836      L<<Logger::Warning<<"There were no answers"<<endl;
837    return false;
838  }
839
840  if(!d_handle.get(r)) {
841    if(d_handle.mustlog)
842      L<<Logger::Warning<<"End of answers"<<endl;
843
844    d_handle.reset();
845
846    return false;
847  }
848  if(d_handle.mustlog)
849    L<<Logger::Warning<<"Returning: '"<<r.qtype.getName()<<"' of '"<<r.qname<<"', content: '"<<r.content<<"', prio: "<<r.priority<<endl;
850  return true;
851}
852
853bool Bind2Backend::handle::get(DNSResourceRecord &r)
854{
855  if(d_list)
856    return get_list(r);
857  else
858    return get_normal(r);
859}
860
861bool Bind2Backend::handle::get_normal(DNSResourceRecord &r)
862{
863  DLOG(L << "Bind2Backend get() was called for "<<qtype.getName() << " record for '"<<
864       qname<<"' - "<<d_records->size()<<" available in total!"<<endl);
865 
866  if(d_iter==d_end_iter) {
867    return false;
868  }
869
870  while(d_iter!=d_end_iter && !(qtype.getCode()==QType::ANY || (d_iter)->qtype==qtype.getCode())) {
871    DLOG(L<<Logger::Warning<<"Skipped "<<qname<<"/"<<QType(d_iter->qtype).getName()<<": '"<<d_iter->content<<"'"<<endl);
872    d_iter++;
873  }
874  if(d_iter==d_end_iter) {
875    return false;
876  }
877  DLOG(L << "Bind2Backend get() returning a rr with a "<<QType(d_iter->qtype).getCode()<<endl);
878
879  r.qname=qname.empty() ? domain : (qname+"."+domain);
880  r.domain_id=id;
881  r.content=(d_iter)->content;
882  //  r.domain_id=(d_iter)->domain_id;
883  r.qtype=(d_iter)->qtype;
884  r.ttl=(d_iter)->ttl;
885  r.priority=(d_iter)->priority;
886  d_iter++;
887
888  return true;
889}
890
891bool Bind2Backend::list(const string &target, int id)
892{
893  shared_ptr<State> state = s_state;
894  if(!state->id_zone_map.count(id))
895    return false;
896
897  d_handle.reset(); 
898  DLOG(L<<"Bind2Backend constructing handle for list of "<<id<<endl);
899
900  d_handle.d_records=state->id_zone_map[id].d_records; // give it a copy, which will stay around
901  d_handle.d_qname_iter= d_handle.d_records->begin();
902  d_handle.d_qname_end=d_handle.d_records->end();   // iter now points to a vector of pointers to vector<BBResourceRecords>
903
904  d_handle.id=id;
905  d_handle.d_list=true;
906  return true;
907
908}
909
910bool Bind2Backend::handle::get_list(DNSResourceRecord &r)
911{
912  if(d_qname_iter!=d_qname_end) {
913    r.qname=d_qname_iter->qname.empty() ? domain : (d_qname_iter->qname+"."+domain);
914    r.domain_id=id;
915    r.content=(d_qname_iter)->content;
916    r.qtype=(d_qname_iter)->qtype;
917    r.ttl=(d_qname_iter)->ttl;
918    r.priority=(d_qname_iter)->priority;
919    d_qname_iter++;
920    return true;
921  }
922  return false;
923
924}
925
926bool Bind2Backend::isMaster(const string &name, const string &ip)
927{
928  for(id_zone_map_t::iterator j=s_state->id_zone_map.begin();j!=s_state->id_zone_map.end();++j) {
929    if(j->second.d_name==name) {
930      for(vector<string>::const_iterator iter = j->second.d_masters.begin(); iter != j->second.d_masters.end(); ++iter)
931        if(*iter==ip)
932          return true;
933    }
934  }
935  return false;
936}
937
938bool Bind2Backend::superMasterBackend(const string &ip, const string &domain, const vector<DNSResourceRecord>&nsset, string *account, DNSBackend **db)
939{
940  // Check whether we have a configfile available.
941  if (getArg("supermaster-config").empty())
942    return false;
943
944  ifstream c_if(getArg("supermasters").c_str(), ios::in); // this was nocreate?
945  if (!c_if) {
946    L << Logger::Error << "Unable to open supermasters file for read: " << stringerror() << endl;
947    return false;
948  }
949
950  // Format:
951  // <ip> <accountname>
952  string line, sip, saccount;
953  while (getline(c_if, line)) {
954    istringstream ii(line);
955    ii >> sip;
956    if (sip == ip) {
957      ii >> saccount;
958      break;
959    }
960  } 
961  c_if.close();
962
963  if (sip != ip)  // ip not found in authorization list - reject
964    return false;
965 
966  // ip authorized as supermaster - accept
967  *db = this;
968  if (saccount.length() > 0)
969      *account = saccount.c_str();
970
971  return true;
972}
973
974bool Bind2Backend::createSlaveDomain(const string &ip, const string &domain, const string &account)
975{
976  // Interference with loadConfig(), use locking
977  Lock l(&s_state_lock);
978
979  string filename = getArg("supermaster-destdir")+'/'+domain;
980 
981  L << Logger::Warning << d_logprefix
982    << " Writing bind config zone statement for superslave zone '" << domain
983    << "' from supermaster " << ip << endl;
984       
985  ofstream c_of(getArg("supermaster-config").c_str(),  ios::app);
986  if (!c_of) {
987    L << Logger::Error << "Unable to open supermaster configfile for append: " << stringerror() << endl;
988    throw DBException("Unable to open supermaster configfile for append: "+stringerror());
989  }
990 
991  c_of << endl;
992  c_of << "# Superslave zone " << domain << " (added: " << nowTime() << ") (account: " << account << ')' << endl;
993  c_of << "zone \"" << domain << "\" {" << endl;
994  c_of << "\ttype slave;" << endl;
995  c_of << "\tfile \"" << filename << "\";" << endl;
996  c_of << "\tmasters { " << ip << "; };" << endl;
997  c_of << "};" << endl;
998  c_of.close();
999
1000  int newid=0;
1001  // Find a free zone id nr. 
1002 
1003  if (!s_state->id_zone_map.empty()) {
1004    id_zone_map_t::reverse_iterator i = s_state->id_zone_map.rbegin();
1005    newid = i->second.d_id + 1;
1006  }
1007 
1008  BB2DomainInfo &bbd = s_state->id_zone_map[newid];
1009
1010  bbd.d_records = shared_ptr<vector<Bind2DNSRecord> >(new vector<Bind2DNSRecord>);
1011  bbd.d_name = domain;
1012  bbd.setCheckInterval(getArgAsNum("check-interval"));
1013  bbd.d_masters.push_back(ip);
1014  bbd.d_filename = filename;
1015
1016  s_state->name_id_map[domain] = bbd.d_id;
1017 
1018  return true;
1019}
1020
1021class Bind2Factory : public BackendFactory
1022{
1023   public:
1024      Bind2Factory() : BackendFactory("bind") {}
1025
1026      void declareArguments(const string &suffix="")
1027      {
1028         declare(suffix,"config","Location of named.conf","");
1029         //         declare(suffix,"example-zones","Install example zones","no");
1030         declare(suffix,"check-interval","Interval for zonefile changes","0");
1031         declare(suffix,"supermaster-config","Location of (part of) named.conf where pdns can write zone-statements to","");
1032         declare(suffix,"supermasters","List of IP-addresses of supermasters","");
1033         declare(suffix,"supermaster-destdir","Destination directory for newly added slave zones",::arg()["config-dir"]);
1034      }
1035
1036      DNSBackend *make(const string &suffix="")
1037      {
1038         return new Bind2Backend(suffix);
1039      }
1040};
1041
1042//! Magic class that is activated when the dynamic library is loaded
1043class Bind2Loader
1044{
1045public:
1046  Bind2Loader()
1047  {
1048    BackendMakers().report(new Bind2Factory);
1049    L<<Logger::Notice<<"[Bind2Backend] This is the bind backend version "VERSION" ("__DATE__", "__TIME__") reporting"<<endl;
1050  }
1051};
1052static Bind2Loader bind2loader;
Note: See TracBrowser for help on using the browser.