root/trunk/pdns/modules/pdnsbackend/pdnsbackend.cc @ 1976

Revision 1976, 11.8 KB (checked in by ahu, 2 years ago)

big batch of 'using namespace std;' removal

  • Property svn:eol-style set to native
  • Property svn:keywords set to author date id revision
Line 
1// $Id$
2
3#include <string>
4#include <map>
5#include <unistd.h>
6#include <stdlib.h>
7#include <sstream>
8
9#include "namespaces.hh"
10
11#include <pdns/dns.hh>
12#include <pdns/dnsbackend.hh>
13#include <pdns/dnspacket.hh>
14#include <pdns/ueberbackend.hh>
15#include <pdns/ahuexception.hh>
16#include <pdns/logger.hh>
17#include <pdns/arguments.hh>
18
19#include "pdnsbackend.hh"
20
21static string backendName="[PdnsBackend]";
22
23string PdnsBackend::sqlEscape(const string &name)
24{
25  string a;
26
27  for(string::const_iterator i=name.begin();i!=name.end();++i)
28    if(*i=='\'' || *i=='\\'){
29      a+='\\';
30      a+=*i;
31    }
32    else
33      a+=*i;
34  return a;
35     
36}
37
38PdnsBackend::PdnsBackend(const string &suffix)
39   : d_result(NULL)
40{
41   mysql_init(&d_database);
42   mysql_options(&d_database, MYSQL_READ_DEFAULT_GROUP, "client");
43   d_suffix=suffix;
44   MYSQL* theDatabase = mysql_real_connect
45      (
46         &d_database,
47         arg()["pdns-"+suffix+"host"].c_str(),
48         arg()["pdns-"+suffix+"user"].c_str(),
49         arg()["pdns-"+suffix+"password"].c_str(),
50         arg()["pdns-"+suffix+"dbname"].c_str(),
51         0,
52         arg()["pdns-"+suffix+"socket"].empty() ? NULL : arg()["pdns-"+suffix+"socket"].c_str(),
53         0
54      );
55
56   if (theDatabase == NULL) {
57      throw(AhuException("mysql_real_connect failed: "+string(mysql_error(&d_database))));
58   }
59   
60   L << Logger::Warning << backendName << " MySQL connection succeeded" << endl;
61}
62
63PdnsBackend::~PdnsBackend()
64{
65   mysql_close(&d_database);
66}
67
68void PdnsBackend::Query(const string& inQuery)
69{
70   //cout << "PdnsBackend::Query: " << inQuery << endl;
71
72   //
73   // Cleanup the previous result, if it exists.
74   //
75
76   if (d_result != NULL) {
77      mysql_free_result(d_result);
78      d_result = NULL;
79   }
80   
81   if (mysql_query(&d_database, inQuery.c_str()) != 0) { 
82      throw AhuException("mysql_query failed");
83   }
84 
85   d_result = mysql_use_result(&d_database);
86   if (d_result == NULL) {
87      throw AhuException("mysql_use_result failed");
88   }
89}
90
91void PdnsBackend::Execute(const string& inStatement)
92{
93   //
94   // Cleanup the previous result, if it exists.
95   //
96
97   if (d_result != NULL) {
98      mysql_free_result(d_result);
99      d_result = NULL;
100   }
101   
102   if (mysql_query(&d_database, inStatement.c_str()) != 0) {
103      throw AhuException(string("mysql_query failed")+string(mysql_error(&d_database)));
104   }
105}
106
107void PdnsBackend::lookup(const QType &qtype,const string &qname, DNSPacket *pkt_p, int zoneId )
108{
109  string query;
110
111  //cout << "PdnsBackend::lookup" << endl;
112 
113  // suport wildcard searches
114 
115  if (qname[0]!='%') {
116     query  ="select r.Content,r.TimeToLive,r.Priority,r.Type,r.ZoneId,r.Name,r.ChangeDate ";
117     query +="from Records r left join Zones z on r.ZoneId = z.Id where r.Name='";
118  } else {
119     query  ="select r.Content,r.TimeToLive,r.Priority,r.Type,r.ZoneId,r.Name,r.ChangeDate ";
120     query +="from Records r left join Zones z on r.ZoneId = z.Id where r.Name like '";
121  }
122
123  if (qname.find_first_of("'\\")!=string::npos)
124    query+=sqlEscape(qname);
125  else
126    query+=qname; 
127
128  query+="'";
129  if (qtype.getCode()!=255) {  // ANY
130    query+=" and r.Type='";
131    query+=qtype.getName();
132    query+="'";
133  }
134
135  if (zoneId>0) {
136    query+=" and r.ZoneId=";
137    ostringstream o;
138    o<<zoneId;
139    query+=o.str();
140  }
141 
142  // XXX Make this optional, because it adds an extra load to the db
143  query += " and r.Active <> 0 and z.Active <> 0";
144 
145  DLOG(L<< backendName<<" Query: '" << query << "'"<<endl);
146
147  this->Query(query);
148}
149
150bool PdnsBackend::list(const string &target, int inZoneId)
151{
152   //cout << "PdnsBackend::list" << endl;
153
154   ostringstream theQuery;
155   
156   theQuery << "select Content,TimeToLive,Priority,Type,ZoneId,Name,ChangeDate from Records where ZoneId = ";
157   theQuery << inZoneId;
158   
159   this->Query(theQuery.str());
160   return true;
161}
162
163bool PdnsBackend::getSOA(const string& inZoneName, SOAData& outSoaData, DNSPacket*)
164{
165   bool theResult = false;
166   MYSQL_ROW theRow = NULL;
167
168   //cout << "PdnsBackend::getSOA" << endl;
169
170   ostringstream o;
171   o << "select Id,Hostmaster,Serial from Zones where Active = 1 and Name = '" << sqlEscape(inZoneName) << "'";
172
173   this->Query(o.str());
174     
175   theRow = mysql_fetch_row(d_result);
176   if (theRow != NULL)
177   {
178      outSoaData.domain_id = atoi(theRow[0]);
179     
180      outSoaData.nameserver = arg()["default-soa-name"];
181      outSoaData.hostmaster = theRow[1];
182      outSoaData.serial = atoi(theRow[2]);
183     
184      outSoaData.refresh = arg()["pdns-"+d_suffix+"soa-refresh"].empty() ? 10800 : atoi(arg()["pdns-"+d_suffix+"soa-refresh"].c_str());
185      outSoaData.retry = 3600;
186      outSoaData.expire = 604800;
187      outSoaData.default_ttl = 40000;
188      outSoaData.db = this;
189     
190      theResult = true;
191   }
192   
193   return theResult;
194}
195
196bool PdnsBackend::isMaster(const string &name, const string &ip)
197{
198   bool theResult = false;
199   MYSQL_ROW theRow = NULL;
200   string master;
201   
202   ostringstream o;
203   o << "select Master from Zones where Master != '' and Name='"<<sqlEscape(name)<<"'";
204   
205   this->Query(o.str());
206   
207   theRow = mysql_fetch_row(d_result);
208   if (theRow != NULL)
209   {
210      master = theRow[0];
211   }
212   
213   if(master == ip)
214      theResult = true;
215   
216   return theResult;
217}
218
219void PdnsBackend::getUnfreshSlaveInfos(vector<DomainInfo>* unfreshDomains)
220{
221   MYSQL_ROW theRow = NULL;
222   
223   string o = "select Id,Name,Master,UNIX_TIMESTAMP(ChangeDate) from Zones where Master != ''";
224   
225   this->Query(o);
226   
227   vector<DomainInfo>allSlaves;
228   while((theRow = mysql_fetch_row(d_result)) != NULL) {
229      DomainInfo di;
230     
231      di.id         = atol(theRow[0]);
232      di.zone       = theRow[1];
233      stringtok(di.masters, theRow[2], ", \t");
234      di.last_check = atol(theRow[3]);
235      di.backend    = this;
236      di.kind       = DomainInfo::Slave;
237      allSlaves.push_back(di);
238   }
239   
240   for(vector<DomainInfo>::iterator i=allSlaves.begin(); i!=allSlaves.end();i++) {
241      SOAData sd;
242      sd.serial=0;
243      sd.refresh=0;
244      getSOA(i->zone,sd);
245      if((time_t)(i->last_check+sd.refresh) < time(0)) {
246         i->serial=sd.serial;
247         unfreshDomains->push_back(*i);
248      }
249   }
250}
251
252bool PdnsBackend::getDomainInfo(const string &domain, DomainInfo &di)
253{
254   bool theResult = false;
255   MYSQL_ROW theRow = NULL;
256   vector<string> masters;
257   
258   ostringstream o;
259   o << "select Id,Name,Master,UNIX_TIMESTAMP(ChangeDate) from Zones WHERE Name='" << sqlEscape(domain) << "'";
260   
261   this->Query(o.str());
262   
263   theRow = mysql_fetch_row(d_result);
264   if (theRow != NULL)
265   {
266      di.id         = atol(theRow[0]);
267      di.zone       = theRow[1];
268      di.last_check = atol(theRow[3]);
269      di.backend    = this;
270     
271      /* We have to store record in local variabel... theRow[2] == NULL makes it empty in di.master = theRow[2]???? */
272      if(theRow[2] != NULL)
273         stringtok(masters, theRow[2], " ,\t");
274     
275      if (masters.empty())
276      {
277         di.kind = DomainInfo::Native;
278      }
279      else
280      {
281         di.serial = 0;
282         try {
283            SOAData sd;
284            if(!getSOA(domain,sd))
285               L<<Logger::Notice<<"No serial for '"<<domain<<"' found - zone is missing?"<<endl;
286            di.serial = sd.serial;
287         }
288         catch (AhuException &ae) {
289            L<<Logger::Error<<"Error retrieving serial for '"<<domain<<"': "<<ae.reason<<endl;
290         }
291         
292         di.kind   = DomainInfo::Slave;
293         di.masters = masters;
294      }
295     
296      theResult = true;
297   }
298     
299   return theResult;
300}
301
302bool PdnsBackend::startTransaction(const string &qname, int domain_id)
303{
304   
305   ostringstream o;
306   o << "delete from Records where ZoneId=" << domain_id;
307
308   this->Execute("begin");
309   if(domain_id >= 0)
310     this->Execute(o.str());
311   
312   d_axfrcount = 0;
313   
314   return true;
315}
316
317bool PdnsBackend::feedRecord(const DNSResourceRecord &rr)
318{   
319   int qcode = rr.qtype.getCode();
320   
321   /* Check max records to transfer except for SOA and NS records */
322   if((qcode != QType::SOA) && (qcode != QType::NS))
323   {
324      if (d_axfrcount == atol(arg()["pdns-"+d_suffix+"max-slave-records"].c_str())  - 1)
325      {
326         L<<Logger::Warning<<backendName<<" Maximal AXFR records reached: "<<arg()["pdns-"+d_suffix+"max-slave-records"]
327                           <<". Skipping rest of records"<<endl;
328      }
329     
330      if (d_axfrcount >= atol(arg()["pdns-"+d_suffix+"max-slave-records"].c_str())) {
331         return true;
332      }
333     
334      d_axfrcount++; // increase AXFR count for pdns-max-slave-records
335   }
336   
337   /* SOA is not be feeded into Records.. update serial instead */
338   if(qcode == QType::SOA)
339   {
340      string::size_type emailpos = rr.content.find(" ", 0) + 1;
341      string::size_type serialpos = rr.content.find(" ", emailpos) + 1;
342      string::size_type other = rr.content.find(" ", serialpos);
343      string serial = rr.content.substr(serialpos, other - serialpos);
344     
345      ostringstream q;
346      q << "update Zones set Serial=" << serial << " where Id=" << rr.domain_id;
347     
348      this->Execute(q.str());
349     
350      return true;
351   }
352   
353   ostringstream o;
354   o << "insert into Records (ZoneId, Name, Type, Content, TimeToLive, Priority, Flags, Active) values ("
355     << rr.domain_id << ","
356     << "'" << toLower(sqlEscape(rr.qname)).c_str() << "',"
357     << "'" << sqlEscape(rr.qtype.getName()).c_str() << "',"
358     << "'" << sqlEscape(rr.content).c_str() << "',"
359     << rr.ttl << ","
360     << rr.priority << ","
361     << "4" << ","
362     << "1)";
363   
364   this->Execute(o.str());
365   
366   return true;
367}
368
369bool PdnsBackend::commitTransaction()
370{
371         this->Execute("commit");
372         
373         d_axfrcount = 0;
374         
375         return true;
376}
377
378bool PdnsBackend::abortTransaction()
379{
380         this->Execute("rollback");
381         
382         d_axfrcount = 0;
383         
384         return true;
385}
386
387void PdnsBackend::setFresh(u_int32_t domain_id)
388{
389   ostringstream o;
390   o << "update Zones set ChangeDate = NOW() where Id=" << domain_id;
391   
392   this->Execute(o.str());
393}
394
395//! For the dynamic loader
396DNSBackend *PdnsBackend::maker()
397{
398  DNSBackend *tmp;
399  try
400    {
401      tmp=new PdnsBackend;
402    }
403  catch(...)
404    {
405      return 0;
406    }
407  return tmp;
408}
409
410bool PdnsBackend::get(DNSResourceRecord& r)
411{
412   bool theResult = false;
413
414   //cout << "PdnsBackend::get" << endl;
415
416   MYSQL_ROW row;
417   
418   row = mysql_fetch_row(d_result);
419   if (row != NULL)
420   {
421      r.content=row[0];  // content
422 
423      if(!row[1])  // ttl
424         r.ttl=0;
425      else
426         r.ttl=atoi(row[1]);
427       
428      if(row[2])
429         r.priority=atoi(row[2]);;
430
431      r.qname=row[5];
432   
433      r.qtype=(const char *)row[3];
434     
435      r.domain_id=atoi(row[4]);
436      if(!row[6])
437         r.last_modified=0;
438      else
439         r.last_modified=atoi(row[6]);
440   
441      theResult = true;
442   }
443
444   return theResult;
445}
446
447class PDNSFactory : public BackendFactory
448{
449   public:
450
451      PDNSFactory() : BackendFactory("pdns") {}
452 
453      void declareArguments(const string &suffix="")
454      {
455         declare(suffix,"dbname","Pdns backend database name to connect to","powerdns");
456         declare(suffix,"user","Pdns backend user to connect as","powerdns");
457         declare(suffix,"host","Pdns backend host to connect to","");
458         declare(suffix,"password","Pdns backend password to connect with","");
459         declare(suffix,"socket","Pdns backend socket to connect to","");
460         declare(suffix,"soa-refresh","Pdns SOA refresh in seconds","");
461         declare(suffix,"max-slave-records","Pdns backend maximal records to transfer", "100");
462      }
463     
464      DNSBackend *make(const string &suffix="")
465      {
466         return new PdnsBackend(suffix);
467      }
468};
469
470
471//! Magic class that is activated when the dynamic library is loaded
472class PdnsBeLoader
473{
474   public:
475
476      PdnsBeLoader()
477      {
478         BackendMakers().report(new PDNSFactory);
479         L<<Logger::Notice<<backendName<<" This is the pdns module version "VERSION" ("__DATE__", "__TIME__") reporting"<<endl;
480      }
481};
482
483static PdnsBeLoader pdnsbeloader;
Note: See TracBrowser for help on using the browser.