]>
Commit | Line | Data |
---|---|---|
343546e5 | 1 | // $Id: gmysqlbackend.cc,v 1.4 2002/12/09 18:34:45 ahu Exp $ |
2ffc06cb BH |
2 | #include <string> |
3 | #include <map> | |
4 | ||
5 | using namespace std; | |
6 | ||
7 | #include "dns.hh" | |
8 | #include "dnsbackend.hh" | |
9 | #include "gmysqlbackend.hh" | |
10 | #include "dnspacket.hh" | |
11 | #include "ueberbackend.hh" | |
12 | #include "ahuexception.hh" | |
13 | #include "logger.hh" | |
14 | #include "arguments.hh" | |
99c45e0a BH |
15 | |
16 | #ifdef PDNS_DOMYSQL | |
2ffc06cb | 17 | #include "smysql.hh" |
99c45e0a | 18 | #endif |
fc3b4b9d | 19 | |
99c45e0a BH |
20 | #ifdef PDNS_DOPGSQL |
21 | #include "spgsql.hh" | |
fc3b4b9d BH |
22 | #endif |
23 | ||
2ffc06cb BH |
24 | #include <sstream> |
25 | ||
26 | ||
27 | void gMySQLBackend::setNotified(u_int32_t domain_id, u_int32_t serial) | |
28 | { | |
29 | try { | |
30 | d_db->doQuery("update domains set notified_serial="+itoa(serial)+" where id="+itoa(domain_id)); | |
31 | } | |
32 | catch(SSqlException &e) { | |
33 | throw AhuException("gMySQLBackend unable to refresh domain_id "+itoa(domain_id)+": "+e.txtReason()); | |
34 | } | |
35 | } | |
36 | ||
37 | void gMySQLBackend::setFresh(u_int32_t domain_id) | |
38 | { | |
39 | try { | |
40 | d_db->doQuery("update domains set last_check="+itoa(time(0))+" where id="+itoa(domain_id)); | |
41 | } | |
42 | catch (SSqlException &e) { | |
43 | throw AhuException("gMySQLBackend unable to refresh domain_id "+itoa(domain_id)+": "+e.txtReason()); | |
44 | } | |
45 | } | |
46 | ||
47 | bool gMySQLBackend::isMaster(const string &domain, const string &ip) | |
48 | { | |
49 | try { | |
50 | d_db->doQuery("select master from domains where name='"+sqlEscape(domain)+"' and type='SLAVE'", d_result); | |
51 | } | |
52 | catch (SSqlException &e) { | |
53 | throw AhuException("gMySQLBackend unable to retrieve list of slave domains: "+e.txtReason()); | |
54 | } | |
55 | ||
56 | if(d_result.empty()) | |
57 | return 0; | |
58 | ||
59 | return !strcmp(ip.c_str(),d_result[0][0].c_str()); | |
60 | } | |
61 | ||
62 | bool gMySQLBackend::getDomainInfo(const string &domain, DomainInfo &di) | |
63 | { | |
64 | /* list all domains that need refreshing for which we are slave, and insert into SlaveDomain: | |
65 | id,name,master IP,serial */ | |
66 | ||
67 | try { | |
68 | d_db->doQuery("select id,name,master,last_check,notified_serial,type from domains where name='"+sqlEscape(domain)+"'",d_result); | |
69 | } | |
70 | catch(SSqlException &e) { | |
71 | throw AhuException("gMySQLBackend unable to retrieve information about a domain: "+e.txtReason()); | |
72 | } | |
73 | ||
74 | int numanswers=d_result.size(); | |
75 | if(!numanswers) | |
76 | return false; | |
77 | ||
78 | di.id=atol(d_result[0][0].c_str()); | |
79 | di.zone=d_result[0][1]; | |
80 | di.master=d_result[0][2]; | |
81 | di.last_check=atol(d_result[0][3].c_str()); | |
82 | di.backend=this; | |
83 | ||
84 | string type=d_result[0][4]; | |
85 | if(type=="SLAVE") | |
86 | di.kind=DomainInfo::Slave; | |
87 | else if(type=="MASTER") | |
88 | di.kind=DomainInfo::Slave; | |
89 | else | |
90 | di.kind=DomainInfo::Native; | |
91 | ||
92 | return true; | |
93 | } | |
94 | ||
95 | void gMySQLBackend::getUnfreshSlaveInfos(vector<DomainInfo> *unfreshDomains) | |
96 | { | |
97 | /* list all domains that need refreshing for which we are slave, and insert into SlaveDomain: | |
98 | id,name,master IP,serial */ | |
99 | ||
100 | try { | |
101 | d_db->doQuery("select id,name,master,last_check,type from domains where type='SLAVE'",d_result); | |
102 | } | |
103 | catch (SSqlException &e) { | |
104 | throw AhuException("gMySQLBackend unable to retrieve list of slave domains: "+e.txtReason()); | |
105 | } | |
106 | ||
107 | vector<DomainInfo>allSlaves; | |
108 | int numanswers=d_result.size(); | |
109 | for(int n=0;n<numanswers;++n) { // id,name,master,last_check | |
110 | DomainInfo sd; | |
111 | sd.id=atol(d_result[n][0].c_str()); | |
112 | sd.zone=d_result[n][1]; | |
113 | sd.master=d_result[n][2]; | |
114 | sd.last_check=atol(d_result[n][3].c_str()); | |
115 | sd.backend=this; | |
116 | sd.kind=DomainInfo::Slave; | |
117 | allSlaves.push_back(sd); | |
118 | } | |
119 | ||
120 | for(vector<DomainInfo>::iterator i=allSlaves.begin();i!=allSlaves.end();++i) { | |
121 | SOAData sdata; | |
122 | sdata.serial=0; | |
123 | sdata.refresh=0; | |
124 | getSOA(i->zone,sdata); | |
125 | if(i->last_check+sdata.refresh<time(0)) { | |
126 | i->serial=sdata.serial; | |
127 | unfreshDomains->push_back(*i); | |
128 | } | |
129 | } | |
130 | } | |
131 | ||
132 | void gMySQLBackend::getUpdatedMasters(vector<DomainInfo> *updatedDomains) | |
133 | { | |
134 | /* list all domains that need notifications for which we are master, and insert into updatedDomains | |
135 | id,name,master IP,serial */ | |
136 | ||
137 | try { | |
138 | d_db->doQuery("select id,name,master,last_check,notified_serial,type from domains where type='MASTER'",d_result); | |
139 | } | |
140 | catch(SSqlException &e) { | |
141 | throw AhuException("gMySQLBackend unable to retrieve list of master domains: "+e.txtReason()); | |
142 | } | |
143 | ||
144 | vector<DomainInfo>allMasters; | |
145 | int numanswers=d_result.size(); | |
146 | for(int n=0;n<numanswers;++n) { // id,name,master,last_check | |
147 | DomainInfo sd; | |
148 | sd.id=atol(d_result[n][0].c_str()); | |
149 | sd.zone=d_result[n][1]; | |
150 | sd.master=d_result[n][2]; | |
151 | sd.last_check=atol(d_result[n][3].c_str()); | |
152 | sd.notified_serial=atoi(d_result[n][4].c_str()); | |
153 | sd.backend=this; | |
154 | sd.kind=DomainInfo::Master; | |
155 | allMasters.push_back(sd); | |
156 | } | |
157 | ||
158 | for(vector<DomainInfo>::iterator i=allMasters.begin();i!=allMasters.end();++i) { | |
159 | SOAData sdata; | |
160 | sdata.serial=0; | |
161 | sdata.refresh=0; | |
162 | getSOA(i->zone,sdata); | |
163 | if(i->notified_serial!=sdata.serial) { | |
164 | i->serial=sdata.serial; | |
165 | updatedDomains->push_back(*i); | |
166 | } | |
167 | } | |
168 | } | |
169 | ||
170 | ||
171 | string gMySQLBackend::sqlEscape(const string &name) | |
172 | { | |
173 | string a; | |
174 | ||
175 | for(string::const_iterator i=name.begin();i!=name.end();++i) | |
176 | if(*i=='\'' || *i=='\\'){ | |
177 | a+='\\'; | |
178 | a+=*i; | |
179 | } | |
180 | else | |
181 | a+=*i; | |
182 | return a; | |
183 | } | |
184 | ||
185 | ||
186 | gMySQLBackend::gMySQLBackend(const string &mode, const string &suffix) | |
187 | { | |
188 | setArgPrefix(mode+suffix); | |
189 | ||
190 | d_logprefix="["+mode+"Backend"+suffix+"] "; | |
191 | d_db=0; | |
192 | try { | |
99c45e0a BH |
193 | if(0) {} |
194 | #ifdef PDNS_DOMYSQL | |
195 | else if(mode=="gmysql") | |
2ffc06cb BH |
196 | d_db=new SMySQL(getArg("dbname"), |
197 | getArg("host"), | |
198 | getArg("socket"), | |
199 | getArg("user"), | |
200 | getArg("password")); | |
99c45e0a BH |
201 | #endif |
202 | #ifdef PDNS_DOPGSQL | |
203 | else if(mode=="gpgsql") | |
2ffc06cb BH |
204 | d_db=new SPgSQL(getArg("dbname"), |
205 | getArg("host"), | |
206 | getArg("socket"), | |
207 | getArg("user"), | |
208 | getArg("password")); | |
fc3b4b9d | 209 | #endif |
2ffc06cb BH |
210 | else { |
211 | L<<Logger::Error<<d_logprefix<<"Generic backend does not support database '"<<mode<<"'"<<endl; | |
212 | exit(1); | |
213 | } | |
214 | ||
215 | } | |
216 | catch(SSqlException &e) { | |
217 | L<<Logger::Error<<d_logprefix<<"Connection failed: "<<e.txtReason()<<endl; | |
218 | throw AhuException("Unable to launch "+mode+" connection: "+e.txtReason()); | |
219 | } | |
220 | ||
221 | d_noWildCardNoIDQuery=getArg("basic-query"); | |
222 | d_noWildCardIDQuery=getArg("id-query"); | |
223 | d_wildCardNoIDQuery=getArg("wildcard-query"); | |
224 | d_wildCardIDQuery=getArg("wildcard-id-query"); | |
225 | ||
226 | d_noWildCardANYNoIDQuery=getArg("any-query"); | |
227 | d_noWildCardANYIDQuery=getArg("any-id-query"); | |
228 | d_wildCardANYNoIDQuery=getArg("wildcard-any-query"); | |
229 | d_wildCardANYIDQuery=getArg("wildcard-any-id-query"); | |
230 | ||
231 | d_listQuery=getArg("list-query"); | |
232 | L<<Logger::Warning<<d_logprefix<<"Connection succesful"<<endl; | |
233 | } | |
234 | ||
235 | gMySQLBackend::~gMySQLBackend() | |
236 | { | |
237 | if(d_db) | |
238 | delete d_db; | |
239 | L<<Logger::Error<<d_logprefix<<"Closing connection"<<endl; | |
240 | } | |
241 | ||
242 | void gMySQLBackend::lookup(const QType &qtype,const string &qname, DNSPacket *pkt_p, int domain_id) | |
243 | { | |
244 | string format; | |
245 | char output[1024]; | |
246 | ||
247 | d_db->setLog(arg().mustDo("query-logging")); | |
248 | ||
249 | string lcqname=toLower(qname); | |
250 | ||
251 | if(qtype.getCode()!=QType::ANY) { | |
252 | // qtype qname domain_id | |
253 | if(domain_id<0) { | |
254 | if(qname[0]=='%') | |
255 | format=d_wildCardNoIDQuery; | |
256 | else | |
257 | format=d_noWildCardNoIDQuery; | |
258 | ||
259 | snprintf(output,1023, format.c_str(),sqlEscape(qtype.getName()).c_str(), sqlEscape(lcqname).c_str()); | |
260 | } | |
261 | else { | |
262 | if(qname[0]!='%') | |
263 | format=d_noWildCardIDQuery; | |
264 | else | |
265 | format=d_wildCardIDQuery; | |
266 | snprintf(output,1023, format.c_str(),sqlEscape(qtype.getName()).c_str(),sqlEscape(lcqname).c_str(),domain_id); | |
267 | } | |
268 | } | |
269 | else { | |
270 | // qtype==ANY | |
271 | // qname domain_id | |
272 | if(domain_id<0) { | |
273 | if(qname[0]=='%') | |
274 | format=d_wildCardANYNoIDQuery; | |
275 | else | |
276 | format=d_noWildCardANYNoIDQuery; | |
277 | ||
278 | snprintf(output,1023, format.c_str(),sqlEscape(lcqname).c_str()); | |
279 | } | |
280 | else { | |
281 | if(qname[0]!='%') | |
282 | format=d_noWildCardANYIDQuery; | |
283 | else | |
284 | format=d_wildCardANYIDQuery; | |
285 | snprintf(output,1023, format.c_str(),sqlEscape(lcqname).c_str(),domain_id); | |
286 | } | |
287 | } | |
288 | DLOG(L<< "Query: '" << output << "'"<<endl); | |
289 | ||
290 | try { | |
291 | d_db->doQuery(output); | |
292 | } | |
293 | catch(SSqlException &e) { | |
294 | throw AhuException(e.txtReason()); | |
295 | } | |
296 | ||
297 | d_qname=qname; | |
298 | ||
299 | d_qtype=qtype; | |
300 | d_count=0; | |
301 | } | |
302 | bool gMySQLBackend::list(int domain_id ) | |
303 | { | |
304 | DLOG(L<<"gMySQLBackend constructing handle for list of domain id'"<<domain_id<<"'"<<endl); | |
305 | ||
306 | char output[1024]; | |
307 | snprintf(output,1023,d_listQuery.c_str(),domain_id); | |
308 | try { | |
309 | d_db->doQuery(output); | |
310 | } | |
311 | catch(SSqlException &e) { | |
312 | throw AhuException("gMySQLBackend list query: "+e.txtReason()); | |
313 | } | |
314 | ||
315 | d_qname=""; | |
316 | d_count=0; | |
317 | return true; | |
318 | } | |
319 | ||
320 | bool gMySQLBackend::superMasterBackend(const string &ip, const string &domain, const vector<DNSResourceRecord>&nsset, string *account, DNSBackend **ddb) | |
321 | { | |
322 | // check if we know the ip/ns couple in the database | |
323 | for(vector<DNSResourceRecord>::const_iterator i=nsset.begin();i!=nsset.end();++i) { | |
324 | try { | |
325 | d_db->doQuery(("select account from supermasters where ip='"+sqlEscape(ip)+"' and nameserver='"+sqlEscape(i->content)+"'"), | |
326 | d_result); | |
327 | } | |
328 | catch (SSqlException &e) { | |
329 | throw AhuException("gMySQLBackend unable to search for a domain: "+e.txtReason()); | |
330 | } | |
331 | ||
332 | if(!d_result.empty()) { | |
333 | *account=d_result[0][0]; | |
334 | *ddb=this; | |
335 | return true; | |
336 | } | |
337 | } | |
338 | return false; | |
339 | } | |
340 | ||
341 | bool gMySQLBackend::createSlaveDomain(const string &ip, const string &domain, const string &account) | |
342 | { | |
343 | try { | |
344 | d_db->doQuery(("insert into domains (type,name,master,account) values('SLAVE','"+ | |
345 | sqlEscape(domain)+"','"+ | |
346 | sqlEscape(ip)+"','"+sqlEscape(account)+"')")); | |
347 | } | |
348 | catch(SSqlException &e) { | |
349 | throw AhuException("Database error trying to insert new slave '"+domain+"': "+ e.txtReason()); | |
350 | } | |
351 | return true; | |
352 | } | |
353 | ||
354 | ||
355 | bool gMySQLBackend::get(DNSResourceRecord &r) | |
356 | { | |
357 | // L << "gMySQLBackend get() was called for "<<qtype.getName() << " record: "; | |
343546e5 | 358 | SSql::row_t row; |
2ffc06cb BH |
359 | if(d_db->getRow(row)) { |
360 | r.content=row[0]; | |
361 | r.ttl=atol(row[1].c_str()); | |
362 | r.priority=atol(row[2].c_str()); | |
363 | if(!d_qname.empty()) | |
364 | r.qname=d_qname; | |
365 | else | |
366 | r.qname=row[5]; | |
367 | r.qtype=row[3]; | |
368 | ||
369 | r.domain_id=atoi(row[4].c_str()); | |
370 | return true; | |
371 | } | |
372 | ||
373 | return false; | |
374 | } | |
375 | ||
376 | bool gMySQLBackend::feedRecord(const DNSResourceRecord &r) | |
377 | { | |
378 | ostringstream os; | |
379 | ||
380 | os<<"insert into records (content,ttl,prio,type,domain_id,name) values ('"<< | |
381 | sqlEscape(r.content)<<"', "<< | |
382 | r.ttl<<", "<< | |
383 | r.priority<<", '"<<sqlEscape(r.qtype.getName())<<"', "<< | |
384 | r.domain_id<< | |
385 | ", '"<<sqlEscape(r.qname)<<"')"; | |
386 | ||
387 | // L<<Logger::Error<<"Trying: '"<<os.str()<<"'"<<endl; | |
388 | ||
389 | try { | |
390 | d_db->doQuery(os.str()); | |
391 | } | |
392 | catch (SSqlException &e) { | |
393 | throw AhuException(e.txtReason()); | |
394 | } | |
395 | ||
396 | } | |
397 | ||
398 | bool gMySQLBackend::startTransaction(const string &domain, int domain_id) | |
399 | { | |
400 | try { | |
401 | d_db->doQuery("begin"); | |
402 | d_db->doQuery("delete from records where domain_id="+itoa(domain_id)); | |
403 | } | |
404 | catch (SSqlException &e) { | |
405 | throw AhuException("Database failed to start transaction: "+e.txtReason()); | |
406 | } | |
407 | ||
408 | return true; | |
409 | } | |
410 | ||
411 | bool gMySQLBackend::commitTransaction() | |
412 | { | |
413 | try { | |
414 | d_db->doQuery("commit"); | |
415 | } | |
416 | catch (SSqlException &e) { | |
417 | throw AhuException("Database failed to commit transaction: "+e.txtReason()); | |
418 | } | |
419 | return true; | |
420 | } | |
421 | ||
422 | bool gMySQLBackend::abortTransaction() | |
423 | { | |
424 | try { | |
425 | d_db->doQuery("rollback"); | |
426 | } | |
427 | catch(SSqlException &e) { | |
428 | throw AhuException("MySQL failed to abort transaction: "+string(e.txtReason())); | |
429 | } | |
430 | return true; | |
431 | } | |
432 | ||
433 | ||
434 | class gMySQLFactory : public BackendFactory | |
435 | { | |
436 | public: | |
437 | gMySQLFactory(const string &mode) : BackendFactory(mode),d_mode(mode) {} | |
438 | ||
439 | void declareArguments(const string &suffix="") | |
440 | { | |
441 | declare(suffix,"dbname","Pdns backend database name to connect to","powerdns"); | |
442 | declare(suffix,"user","Pdns backend user to connect as","powerdns"); | |
443 | declare(suffix,"host","Pdns backend host to connect to",""); | |
444 | declare(suffix,"socket","Pdns backend socket to connect to",""); | |
445 | declare(suffix,"password","Pdns backend password to connect with",""); | |
446 | ||
447 | declare(suffix,"basic-query","Basic query","select content,ttl,prio,type,domain_id,name from records where type='%s' and name='%s'"); | |
448 | declare(suffix,"id-query","Basic with ID query","select content,ttl,prio,type,domain_id,name from records where type='%s' and name='%s' and domain_id=%d"); | |
449 | declare(suffix,"wildcard-query","Wildcard query","select content,ttl,prio,type,domain_id,name from records where type='%s' and name like '%s'"); | |
450 | declare(suffix,"wildcard-id-query","Wildcard with ID query","select content,ttl,prio,type,domain_id,name from records where type='%s' and name like '%s' and domain_id='%d'"); | |
451 | ||
452 | declare(suffix,"any-query","Any query","select content,ttl,prio,type,domain_id,name from records where name='%s'"); | |
453 | declare(suffix,"any-id-query","Any with ID query","select content,ttl,prio,type,domain_id,name from records where name='%s' and domain_id=%d"); | |
454 | declare(suffix,"wildcard-any-query","Wildcard ANY query","select content,ttl,prio,type,domain_id,name from records where name like '%s'"); | |
455 | declare(suffix,"wildcard-any-id-query","Wildcard ANY with ID query","select content,ttl,prio,type,domain_id,name from records where like '%s' and domain_id='%d'"); | |
456 | ||
457 | declare(suffix,"list-query","AXFR query", "select content,ttl,prio,type,domain_id,name from records where domain_id='%d'"); | |
458 | ||
459 | } | |
460 | ||
461 | DNSBackend *make(const string &suffix="") | |
462 | { | |
463 | return new gMySQLBackend(d_mode,suffix); | |
464 | } | |
465 | private: | |
466 | const string d_mode; | |
467 | }; | |
468 | ||
469 | ||
470 | //! Magic class that is activated when the dynamic library is loaded | |
471 | class gMySQLLoader | |
472 | { | |
473 | public: | |
474 | //! This reports us to the main UeberBackend class | |
475 | gMySQLLoader() | |
476 | { | |
477 | BackendMakers().report(new gMySQLFactory("gmysql")); | |
478 | BackendMakers().report(new gMySQLFactory("gpgsql2")); | |
479 | L<<Logger::Warning<<"This is module gmysqlbackend.so reporting"<<endl; | |
480 | } | |
481 | }; | |
482 | static gMySQLLoader gmysqlloader; |