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

Revision 1026, 30.1 KB (checked in by ahu, 6 years ago)

fix escaped dots! Plus close debian bug 406462, 'm' stands for minutes, not months. Plus improve bindbackend query-logging

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