]> git.ipfire.org Git - thirdparty/pdns.git/blob - modules/mydnsbackend/mydnsbackend.cc
Merge pull request #1795 from rubenk/travis-compile-luabackend
[thirdparty/pdns.git] / modules / mydnsbackend / mydnsbackend.cc
1 /*
2 * PowerDNS backend module for MyDNS style databases
3 * Author: Jonathan Oddy (Hostway UK) <jonathan@woaf.net>
4 *
5 * The schema used by MyDNS isn't suitable for retrieving results with a single
6 * query. This means that existing PowerDNS backends are unable to make use of
7 * the schema without lame hackery (or awful performance.) This module does
8 * the nasty lookup logic required to make use of the schema, and should be as
9 * tolerant as MyDNS when it comes to things being fully qualified or not.
10 *
11 * A known "bug" is that AXFRs will fail if your rr table contains invalid
12 * junk. I'm not sure this is really a bug, if you've decided to put free-form
13 * text in your data for an A record you have bigger issues.
14 *
15 * I'd advise avoiding the MyDNS schema if at all possible as the query count
16 * for even simple lookups is daft. It's quite trivial to craft a request
17 * that'll require 128 database queries to answer with a servfail!
18 *
19 * If you do not know what mydns is: http://mydns.bboy.net/
20 */
21
22 #include <string>
23 #include <map>
24 #include <unistd.h>
25 #include <stdlib.h>
26 #include <sstream>
27
28 #include "pdns/namespaces.hh"
29
30 #include "pdns/dns.hh"
31 #include "pdns/dnsbackend.hh"
32 #include "mydnsbackend.hh"
33 #include "pdns/dnspacket.hh"
34 #include "pdns/ueberbackend.hh"
35 #include "pdns/pdnsexception.hh"
36 #include "pdns/logger.hh"
37 #include "pdns/arguments.hh"
38
39 #include <modules/gmysqlbackend/smysql.hh>
40
41 static string backendName="[MyDNSbackend]";
42
43 MyDNSBackend::MyDNSBackend(const string &suffix) {
44 setArgPrefix("mydns"+suffix);
45
46 try {
47 d_db = new SMySQL(getArg("dbname"),
48 getArg("host"),
49 getArgAsNum("port"),
50 getArg("socket"),
51 getArg("user"),
52 getArg("password"));
53
54 }
55 catch(SSqlException &e) {
56 L<<Logger::Error<<backendName<<" Connection failed: "<<e.txtReason()<<endl;
57 throw PDNSException(backendName+"Unable to launch connection: "+e.txtReason());
58 }
59
60 d_rrtable=getArg("rr-table");
61 d_soatable=getArg("soa-table");
62 d_rrwhere=(mustDo("rr-active")?"active = 1 and ":"")+getArg("rr-where");
63 d_soawhere=(mustDo("soa-active")?"active = 1 and ":"")+getArg("soa-where");
64 d_useminimalttl=mustDo("use-minimal-ttl");
65 d_minimum=0;
66
67 L<<Logger::Warning<<backendName<<" Connection successful"<<endl;
68 }
69
70 MyDNSBackend::~MyDNSBackend() {
71 if (d_db)
72 delete(d_db);
73 }
74
75
76 void MyDNSBackend::Query(const string &query) {
77 try {
78 d_db->doQuery(query);
79 } catch (SSqlException &e) {
80 throw PDNSException("Query failed: "+e.txtReason());
81 }
82 }
83
84 bool MyDNSBackend::list(const string &target, int zoneId, bool include_disabled) {
85 string query;
86 string sname;
87 SSql::row_t rrow;
88
89 d_db->setLog(::arg().mustDo("query-logging"));
90
91 query = "select origin, minimum from "+d_soatable+" where id = ";
92 query+=itoa(zoneId);
93 if (!d_soawhere.empty())
94 query+= " and "+d_soawhere;
95
96 this->Query(query);
97
98 if(!d_db->getRow(rrow))
99 return false; // No such zone
100
101 d_origin = rrow[0];
102 if (d_origin[d_origin.length()-1] == '.')
103 d_origin.erase(d_origin.length()-1);
104 d_minimum = atol(rrow[1].c_str());
105
106 while (d_db->getRow(rrow)) {
107 L<<Logger::Warning<<backendName<<" Found more than one matching origin for zone ID: "<<zoneId<<endl;
108 };
109
110 query = "select type, data, aux, ttl, zone, name from "+d_rrtable+" where zone = ";
111 query+=itoa(zoneId);
112 if (!d_rrwhere.empty())
113 query += " and "+d_rrwhere;
114
115
116 this->Query(query);
117
118 d_qname = "";
119 return true;
120
121 }
122
123 bool MyDNSBackend::getSOA(const string& name, SOAData& soadata, DNSPacket*) {
124 string query;
125 SSql::row_t rrow;
126
127 d_db->setLog(::arg().mustDo("query-logging"));
128
129 if (name.empty())
130 return false;
131
132 query = "select id, mbox, serial, ns, refresh, retry, expire, minimum, ttl from "+d_soatable+" where origin = '";
133
134 if (name.find_first_of("'\\")!=string::npos)
135 query+=d_db->escape(name);
136 else
137 query+=name;
138
139 query+=".'";
140 if (! d_soawhere.empty())
141 query += " and "+d_soawhere;
142
143 this->Query(query);
144
145 if(!(d_db->getRow(rrow))) {
146 return false;
147 }
148
149 soadata.qname = name;
150 soadata.domain_id = atol(rrow[0].c_str());
151 soadata.hostmaster = rrow[1];
152 soadata.serial = atol(rrow[2].c_str());
153 soadata.nameserver = rrow[3];
154 soadata.refresh = atol(rrow[4].c_str());
155 soadata.retry = atol(rrow[5].c_str());
156 soadata.expire = atol(rrow[6].c_str());
157 soadata.default_ttl = atol(rrow[7].c_str());
158 soadata.ttl = atol(rrow[8].c_str());
159 if (d_useminimalttl && soadata.ttl < soadata.default_ttl) {
160 soadata.ttl = soadata.default_ttl;
161 }
162 soadata.db = this;
163
164 while (d_db->getRow(rrow)) {
165 L<<Logger::Warning<<backendName<<" Found more than one matching zone for: "+name<<endl;
166 };
167
168 return true;
169 }
170
171 void MyDNSBackend::lookup(const QType &qtype, const string &qname, DNSPacket *p, int zoneId) {
172 string query;
173 string sname;
174 string zoneIdStr = itoa(zoneId);
175 SSql::row_t rrow;
176 bool found = false;
177
178 d_origin = "";
179
180 d_db->setLog(::arg().mustDo("query-logging"));
181
182 if (qname.empty())
183 return;
184
185 // Escape the name, after this point we only want to use it in queries
186 if (qname.find_first_of("'\\")!=string::npos)
187 sname=d_db->escape(qname);
188 else
189 sname = qname;
190 sname += ".";
191
192 if (zoneId < 0) {
193 // First off we need to work out what zone we're working with
194 // MyDNS records aren't always fully qualified, so we need to work out the zone ID.
195
196 size_t pos;
197 string sdom;
198
199 pos = 0;
200 sdom = sname;
201 while (!sdom.empty() && pos != string::npos) {
202 query = "select id, origin, minimum from "+d_soatable+" where origin = '"+sdom+"'";
203 if (!d_soawhere.empty())
204 query += " and "+d_soawhere;
205
206 this->Query(query);
207 if(d_db->getRow(rrow)) {
208 zoneIdStr=rrow[0];
209 d_origin = rrow[1];
210 if (d_origin[d_origin.length()-1] == '.')
211 d_origin.erase(d_origin.length()-1);
212 d_minimum = atol(rrow[2].c_str());
213 found = true;
214 break;
215 }
216
217 pos = sname.find_first_of(".",pos+1);
218 sdom = sname.substr(pos+1);
219 }
220
221 } else {
222 query = "select origin, minimum from "+d_soatable+" where id = ";
223 query+=zoneIdStr;
224 if (!d_soawhere.empty())
225 query+= " and "+d_soawhere;
226
227 this->Query(query);
228
229 if(!d_db->getRow(rrow)) {
230 throw PDNSException("lookup() passed zoneId = "+zoneIdStr+" but no such zone!");
231 }
232
233 found = true;
234 d_origin = rrow[0];
235 if (d_origin[d_origin.length()-1] == '.')
236 d_origin.erase(d_origin.length()-1);
237 d_minimum = atol(rrow[1].c_str());
238 }
239
240
241 if (found) {
242
243 while (d_db->getRow(rrow)) {
244 L<<Logger::Warning<<backendName<<" Found more than one matching zone for: "+d_origin<<endl;
245 };
246 // We found the zoneId, so we can work out how to find our rr
247 string host;
248
249 // The host part of the query is the name less the origin
250 if (qname.length() == d_origin.length())
251 host = "";
252 else
253 host = qname.substr(0, (qname.length() - d_origin.length())-1);
254
255 if (host.find_first_of("'\\")!=string::npos)
256 host=d_db->escape(host);
257
258 query = "select type, data, aux, ttl, zone from "+d_rrtable+" where zone = ";
259 query+= zoneIdStr;
260 query += " and (name = '"+host+"' or name = '"+sname+"')";
261
262 if(qtype.getCode()!=255) { // ANY
263 query+=" and type='";
264 query+=qtype.getName();
265 query+="'";
266
267 }
268 if (!d_rrwhere.empty())
269 query += " and "+d_rrwhere;
270
271
272 if (qtype.getCode() == 255) {
273 query += " union select 'SOA' as type, origin as data, '0' as aux, ttl, id as zone from "+d_soatable+" where id= " + zoneIdStr + " and origin = '"+qname+".'";
274 if (!d_soawhere.empty())
275 query += " and " + d_soawhere;
276 }
277 query += " order by type,aux,data";
278
279 this->Query(query);
280
281 d_qname = qname;
282 }
283
284 }
285
286 bool MyDNSBackend::get(DNSResourceRecord &rr) {
287 if (d_origin.empty()) {
288 // This happens if lookup() couldn't find the zone
289 return false;
290 }
291
292 SSql::row_t rrow;
293
294 if(!d_db->getRow(rrow)) {
295 return false;
296 }
297
298 rr.qtype=rrow[0];
299 rr.content = rrow[1];
300
301 if(!d_qname.empty()) {
302 // use this to distinguish between select with 'name' field (list()) and one without
303 rr.qname=d_qname;
304 } else {
305 rr.qname=rrow[5];
306 if (rr.qname[rr.qname.length()-1] == '.') {
307 rr.qname.erase(rr.qname.length()-1); // Fully qualified, nuke the last .
308 } else {
309 if (!rr.qname.empty())
310 rr.qname += ".";
311 rr.qname += d_origin; // Not fully qualified
312 }
313
314 }
315
316 if (rr.qtype.getCode() == QType::NS || rr.qtype.getCode()==QType::MX ||
317 rr.qtype.getCode() == QType::CNAME || rr.qtype.getCode() == QType::PTR) {
318 if (rr.content[rr.content.length()-1] == '.') {
319 rr.content.erase(rr.content.length()-1); // Fully qualified, nuke the last .
320 } else {
321 if (!rr.content.empty())
322 rr.content += ".";
323 rr.content += d_origin;
324 }
325 }
326
327 rr.priority = atol(rrow[2].c_str());
328 rr.ttl = atol(rrow[3].c_str());
329 if (d_useminimalttl && rr.ttl < d_minimum)
330 rr.ttl = d_minimum;
331 rr.domain_id=atol(rrow[4].c_str());
332
333
334 rr.last_modified=0;
335
336 return true;
337
338 }
339
340 class MyDNSFactory : public BackendFactory {
341
342 public:
343 MyDNSFactory() : BackendFactory("mydns") {}
344
345 void declareArguments(const string &suffix = "") {
346 declare(suffix,"dbname","Pdns backend database name to connect to","mydns");
347 declare(suffix,"user","Pdns backend user to connect as","powerdns");
348 declare(suffix,"host","Pdns backend host to connect to","");
349 declare(suffix,"port","Pdns backend host to connect to","");
350 declare(suffix,"password","Pdns backend password to connect with","");
351 declare(suffix,"socket","Pdns backend socket to connect to","");
352 declare(suffix,"rr-table","Name of RR table to use","rr");
353 declare(suffix,"soa-table","Name of SOA table to use","soa");
354 declare(suffix,"soa-where","Additional WHERE clause for SOA","1 = 1");
355 declare(suffix,"rr-where","Additional WHERE clause for RR","1 = 1");
356 declare(suffix,"soa-active","Use the active column in the SOA table","yes");
357 declare(suffix,"rr-active","Use the active column in the RR table","yes");
358 declare(suffix,"use-minimal-ttl","Setting this to 'yes' will make the backend behave like MyDNS on the TTL values. Setting it to 'no' will make it ignore the minimal-ttl of the zone.","yes");
359 }
360
361 MyDNSBackend *make(const string &suffix = "") {
362 return new MyDNSBackend(suffix);
363 }
364
365 };
366
367 class MyDNSLoader {
368
369 public:
370 MyDNSLoader() {
371 BackendMakers().report(new MyDNSFactory());
372 L << Logger::Info << "[mydnsbackend] This is the mydns backend version " VERSION " (" __DATE__ ", " __TIME__ ") reporting" << endl;
373 }
374 };
375
376 static MyDNSLoader mydnsloader;