PageRenderTime 63ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/pdns-3.0.1/modules/pdnsbackend/pdnsbackend.cc

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