]>
git.ipfire.org Git - thirdparty/pdns.git/blob - modules/mydnsbackend/mydnsbackend.cc
ba583cb13bc1bd9c58946ffd5e64fc1240db8d71
2 * PowerDNS backend module for MyDNS style databases
3 * Author: Jonathan Oddy (Hostway UK) <jonathan@woaf.net>
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2
8 * as published by the Free Software Foundation.
10 * Additionally, the license of this program contains a special
11 * exception which allows to distribute the program in binary form when
12 * it is linked against OpenSSL.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
23 * The schema used by MyDNS isn't suitable for retrieving results with a single
24 * query. This means that existing PowerDNS backends are unable to make use of
25 * the schema without lame hackery (or awful performance.) This module does
26 * the nasty lookup logic required to make use of the schema, and should be as
27 * tolerant as MyDNS when it comes to things being fully qualified or not.
29 * A known "bug" is that AXFRs will fail if your rr table contains invalid
30 * junk. I'm not sure this is really a bug, if you've decided to put free-form
31 * text in your data for an A record you have bigger issues.
33 * I'd advise avoiding the MyDNS schema if at all possible as the query count
34 * for even simple lookups is daft. It's quite trivial to craft a request
35 * that'll require 128 database queries to answer with a servfail!
37 * If you do not know what mydns is: http://mydns.bboy.net/
49 #include "pdns/namespaces.hh"
51 #include "pdns/dns.hh"
52 #include "pdns/dnsbackend.hh"
53 #include "mydnsbackend.hh"
54 #include "pdns/dnspacket.hh"
55 #include "pdns/ueberbackend.hh"
56 #include "pdns/pdnsexception.hh"
57 #include "pdns/logger.hh"
58 #include "pdns/arguments.hh"
60 #include <modules/gmysqlbackend/smysql.hh>
62 static string backendName
= "[MyDNSbackend]" ;
64 MyDNSBackend :: MyDNSBackend ( const string
& suffix
) {
65 setArgPrefix ( "mydns" + suffix
);
67 d_domainIdQuery_stmt
= NULL
;
68 d_domainNoIdQuery_stmt
= NULL
;
69 d_listQuery_stmt
= NULL
;
70 d_soaQuery_stmt
= NULL
;
71 d_basicQuery_stmt
= NULL
;
72 d_anyQuery_stmt
= NULL
;
76 d_db
= new SMySQL ( getArg ( "dbname" ),
82 d_db
-> setLog (:: arg (). mustDo ( "query-logging" ));
84 catch ( SSqlException
& e
) {
85 L
<< Logger :: Error
<< backendName
<< " Connection failed: " << e
. txtReason ()<< endl
;
86 throw PDNSException ( backendName
+ "Unable to launch connection: " + e
. txtReason ());
89 string rrtable
= getArg ( "rr-table" );
90 string soatable
= getArg ( "soa-table" );
91 string rrwhere
=( mustDo ( "rr-active" )? "(active = '1' or active = 'Y') and " : "" )+ getArg ( "rr-where" );
92 string soawhere
=( mustDo ( "soa-active" )? "(active = '1' or active = 'Y') and " : "" )+ getArg ( "soa-where" );
94 if ( soatable
. empty ()) { throw PDNSException ( "SOA Table must not be empty" ); }
95 if ( rrtable
. empty ()) { throw PDNSException ( "Records table must not be empty" ); }
97 d_useminimalttl
= mustDo ( "use-minimal-ttl" );
100 L
<< Logger :: Warning
<< backendName
<< " Connection successful" << endl
;
104 string domainIdQuery
= "SELECT origin, minimum FROM `" + soatable
+ "` WHERE id = ?" ;
105 string domainNoIdQuery
= "SELECT id, origin, minimum FROM `" + soatable
+ "` WHERE origin = ?" ;
106 string soaQuery
= "SELECT id, mbox, serial, ns, refresh, retry, expire, minimum, ttl FROM `" + soatable
+ "` WHERE origin = ?" ;
108 if (! soawhere
. empty ()) {
109 domainIdQuery
+= " AND " + soawhere
;
110 domainNoIdQuery
+= " AND " + soawhere
;
111 soaQuery
+= " AND " + soawhere
;
114 d_domainIdQuery_stmt
= d_db
-> prepare ( domainIdQuery
, 1 );
115 d_domainNoIdQuery_stmt
= d_db
-> prepare ( domainNoIdQuery
, 1 );
116 d_soaQuery_stmt
= d_db
-> prepare ( soaQuery
, 1 );
118 string listQuery
= "SELECT type, data, aux, ttl, zone, name FROM `" + rrtable
+ "` WHERE zone = ?" ;
119 string basicQuery
= "SELECT type, data, aux, ttl, zone FROM `" + rrtable
+ "` WHERE zone = ? AND (name = ? OR name = ?) AND type = ?" ;
120 string anyQuery
= "(SELECT type, data, aux, ttl, zone FROM `" + rrtable
+ "` WHERE zone = ? AND (name = ? OR name = ?)" ;
122 if (! rrwhere
. empty ()) {
123 listQuery
+= " AND " + rrwhere
;
124 basicQuery
+= " AND " + rrwhere
;
125 anyQuery
+= " AND " + rrwhere
;
128 d_listQuery_stmt
= d_db
-> prepare ( listQuery
, 1 );
130 anyQuery
+= ") UNION (SELECT 'SOA' AS type, origin AS data, '0' AS aux, ttl, id AS zone FROM `" + soatable
+ "` WHERE id = ? AND origin = ?" ;
132 if (! soawhere
. empty ()) {
133 anyQuery
+= " AND " + soawhere
;
136 basicQuery
+= " ORDER BY type,aux,data" ;
137 anyQuery
+= ") ORDER BY type,aux,data" ;
139 d_basicQuery_stmt
= d_db
-> prepare ( basicQuery
, 4 );
140 d_anyQuery_stmt
= d_db
-> prepare ( anyQuery
, 5 );
141 } catch ( SSqlException
& e
) {
142 L
<< Logger :: Error
<< "Cannot prepare statements: " << e
. txtReason () << endl
;
143 throw PDNSException ( "Cannot prepare statements: " + e
. txtReason ());
147 MyDNSBackend ::~ MyDNSBackend () {
148 delete d_domainIdQuery_stmt
;
149 d_domainIdQuery_stmt
= NULL
;
150 delete d_domainNoIdQuery_stmt
;
151 d_domainNoIdQuery_stmt
= NULL
;
152 delete d_listQuery_stmt
;
153 d_listQuery_stmt
= NULL
;
154 delete d_soaQuery_stmt
;
155 d_soaQuery_stmt
= NULL
;
156 delete d_basicQuery_stmt
;
157 d_basicQuery_stmt
= NULL
;
158 delete d_anyQuery_stmt
;
159 d_anyQuery_stmt
= NULL
;
164 bool MyDNSBackend :: list ( const DNSName
& target
, int zoneId
, bool include_disabled
) {
167 SSqlStatement :: row_t rrow
;
170 d_domainIdQuery_stmt
->
171 bind ( "domain_id" , zoneId
)->
173 getResult ( d_result
)->
176 catch ( SSqlException
& e
) {
177 throw PDNSException ( "MyDNSBackend unable to list domain_id " + itoa ( zoneId
)+ ": " + e
. txtReason ());
180 if ( d_result
. empty ())
181 return false ; // No such zone
183 d_origin
= d_result
[ 0 ][ 0 ];
184 if ( d_origin
[ d_origin
. length ()- 1 ] == '.' )
185 d_origin
. erase ( d_origin
. length ()- 1 );
186 d_minimum
= pdns_stou ( d_result
[ 0 ][ 1 ]);
188 if ( d_result
. size ()> 1 ) {
189 L
<< Logger :: Warning
<< backendName
<< " Found more than one matching origin for zone ID: " << zoneId
<< endl
;
193 d_query_stmt
= d_listQuery_stmt
;
195 bind ( "domain_id" , zoneId
)->
198 catch ( SSqlException
& e
) {
199 throw PDNSException ( "MyDNSBackend unable to list domain_id " + itoa ( zoneId
)+ ": " + e
. txtReason ());
206 bool MyDNSBackend :: getSOA ( const DNSName
& name
, SOAData
& soadata
, DNSPacket
*) {
208 SSqlStatement :: row_t rrow
;
215 bind ( "origin" , name
. toString ())->
217 getResult ( d_result
)->
220 catch ( SSqlException
& e
) {
221 throw PDNSException ( "MyDNSBackend unable to get soa for domain " + name
. toString ()+ ": " + e
. txtReason ());
224 if ( d_result
. empty ()) {
230 soadata
. qname
= name
;
231 soadata
. domain_id
= pdns_stou ( rrow
[ 0 ]);
232 soadata
. hostmaster
= DNSName ( rrow
[ 1 ]);
233 soadata
. serial
= pdns_stou ( rrow
[ 2 ]);
234 soadata
. nameserver
= DNSName ( rrow
[ 3 ]);
235 soadata
. refresh
= pdns_stou ( rrow
[ 4 ]);
236 soadata
. retry
= pdns_stou ( rrow
[ 5 ]);
237 soadata
. expire
= pdns_stou ( rrow
[ 6 ]);
238 soadata
. default_ttl
= pdns_stou ( rrow
[ 7 ]);
239 soadata
. ttl
= pdns_stou ( rrow
[ 8 ]);
240 if ( d_useminimalttl
) {
241 soadata
. ttl
= std :: min ( soadata
. ttl
, soadata
. default_ttl
);
245 if ( d_result
. size ()> 1 ) {
246 L
<< Logger :: Warning
<< backendName
<< " Found more than one matching zone for: " << name
<< endl
;
252 void MyDNSBackend :: lookup ( const QType
& qtype
, const DNSName
& qname
, DNSPacket
* p
, int zoneId
) {
253 SSqlStatement :: row_t rrow
;
263 DLOG ( L
<< Logger :: Debug
<< "MyDNSBackend::lookup(" << qtype
. getName () << "," << qname
<< ",p," << zoneId
<< ")" << endl
);
266 // First off we need to work out what zone we're working with
267 // MyDNS records aren't always fully qualified, so we need to work out the zone ID.
272 d_domainNoIdQuery_stmt
->
273 bind ( "domain" , sdom
. toString ())->
275 getResult ( d_result
)->
278 catch ( SSqlException
& e
) {
279 throw PDNSException ( "MyDNSBackend unable to lookup " + qname
. toString ()+ ": " + e
. txtReason ());
282 if ( d_result
. empty () == false ) {
284 zoneId
= pdns_stou ( rrow
[ 0 ]);
285 d_origin
= stripDot ( rrow
[ 1 ]);
286 d_minimum
= pdns_stou ( rrow
[ 2 ]);
291 } while ( sdom
. chopOff ());
295 d_domainIdQuery_stmt
->
296 bind ( "domain_id" , zoneId
)->
298 getResult ( d_result
)->
301 catch ( SSqlException
& e
) {
302 throw PDNSException ( "MyDNSBackend unable to lookup " + qname
. toString ()+ ": " + e
. txtReason ());
305 if ( d_result
. empty ()) {
306 throw PDNSException ( "lookup() passed zoneId = " + itoa ( zoneId
)+ " but no such zone!" );
312 d_origin
= stripDot ( rrow
[ 0 ]);
313 d_minimum
= pdns_stou ( rrow
[ 1 ]);
318 while ( d_result
. size ()> 1 ) {
319 L
<< Logger :: Warning
<< backendName
<< " Found more than one matching zone for: " + d_origin
<< endl
;
321 // We found the zoneId, so we can work out how to find our rr
324 // The host part of the query is the name less the origin
325 DNSName
origin ( d_origin
);
326 host
= qname
. makeRelative ( origin
). toStringNoDot ();
330 if ( qtype
. getCode ()== QType :: ANY
) {
331 DLOG ( L
<< Logger :: Debug
<< "Running d_anyQuery_stmt with " << zoneId
<< ", " << host
<< ", " << sdom
<< ", " << zoneId
<< " , " << qname
<< ", " << qtype
. getName () << endl
);
332 d_query_stmt
= d_anyQuery_stmt
;
334 bind ( "domain_id" , zoneId
)->
336 bind ( "qname" , qname
. toString ())->
337 bind ( "domain_id" , zoneId
)-> // this is because positional arguments
338 bind ( "qname2" , sdom
. toString ())->
341 DLOG ( L
<< Logger :: Debug
<< "Running d_basicQuery_stmt with " << zoneId
<< ", " << host
<< ", " << qname
<< ", " << qtype
. getName () << endl
);
342 d_query_stmt
= d_basicQuery_stmt
;
344 bind ( "domain_id" , zoneId
)->
346 bind ( "qname" , qname
. toString ())->
347 bind ( "qtype" , qtype
. getName ())->
351 catch ( SSqlException
& e
) {
352 throw PDNSException ( "MyDNSBackend unable to lookup " + qname
. toString ()+ ": " + e
. txtReason ());
355 d_qname
= qname
. toString ();
360 bool MyDNSBackend :: get ( DNSResourceRecord
& rr
) {
361 if ( d_origin
. empty ()) {
364 d_query_stmt
-> reset ();
365 } catch ( SSqlException
& e
) {
366 throw PDNSException ( "MyDNSBackend unable to lookup " + d_qname
+ ": " + e
. txtReason ());
370 // This happens if lookup() couldn't find the zone
374 SSqlStatement :: row_t rrow
;
376 if ( d_query_stmt
-> hasNextRow ()) {
378 d_query_stmt
-> nextRow ( rrow
);
379 } catch ( SSqlException
& e
) {
380 throw PDNSException ( "MyDNSBackend unable to lookup " + d_qname
+ ": " + e
. txtReason ());
383 rr
. content
= rrow
[ 1 ];
385 if (! d_qname
. empty ()) {
386 // use this to distinguish between select with 'name' field (list()) and one without
387 rr
. qname
= DNSName ( d_qname
);
389 string tmpQname
= rrow
[ 5 ];
392 if (! tmpQname
. empty () && tmpQname
[ tmpQname
. length ()- 1 ] == '.' ) {
393 tmpQname
. erase ( tmpQname
. length ()- 1 ); // Fully qualified, nuke the last .
395 if (! tmpQname
. empty ()) {
398 tmpQname
+= d_origin
; // Not fully qualified
400 rr
. qname
= DNSName ( tmpQname
);
403 if ( rr
. qtype
. getCode () == QType :: NS
|| rr
. qtype
. getCode ()== QType :: MX
||
404 rr
. qtype
. getCode () == QType :: CNAME
|| rr
. qtype
. getCode () == QType :: PTR
) {
405 if (! rr
. content
. empty () && rr
. content
[ rr
. content
. length ()- 1 ] == '.' ) {
406 if ( rr
. content
. length () > 1 )
407 rr
. content
. erase ( rr
. content
. length ()- 1 ); // Fully qualified, nuke the last .
409 if ( rr
. content
!= "." )
411 rr
. content
+= d_origin
;
415 if ( rr
. qtype
. getCode () == QType :: MX
|| rr
. qtype
. getCode () == QType :: SRV
)
416 rr
. content
= rrow
[ 2 ]+ " " + rr
. content
;
418 rr
. ttl
= pdns_stou ( rrow
[ 3 ]);
420 rr
. ttl
= std :: min ( rr
. ttl
, d_minimum
);
421 rr
. domain_id
= pdns_stou ( rrow
[ 4 ]);
429 d_query_stmt
-> reset ();
430 } catch ( SSqlException
& e
) {
431 throw PDNSException ( "MyDNSBackend unable to lookup " + d_qname
+ ": " + e
. txtReason ());
439 class MyDNSFactory
: public BackendFactory
{
442 MyDNSFactory () : BackendFactory ( "mydns" ) {}
444 void declareArguments ( const string
& suffix
= "" ) {
445 declare ( suffix
, "dbname" , "Pdns backend database name to connect to" , "mydns" );
446 declare ( suffix
, "user" , "Pdns backend user to connect as" , "powerdns" );
447 declare ( suffix
, "host" , "Pdns backend host to connect to" , "" );
448 declare ( suffix
, "port" , "Pdns backend host to connect to" , "" );
449 declare ( suffix
, "password" , "Pdns backend password to connect with" , "" );
450 declare ( suffix
, "socket" , "Pdns backend socket to connect to" , "" );
451 declare ( suffix
, "rr-table" , "Name of RR table to use" , "rr" );
452 declare ( suffix
, "soa-table" , "Name of SOA table to use" , "soa" );
453 declare ( suffix
, "soa-where" , "Additional WHERE clause for SOA" , "1 = 1" );
454 declare ( suffix
, "rr-where" , "Additional WHERE clause for RR" , "1 = 1" );
455 declare ( suffix
, "soa-active" , "Use the active column in the SOA table" , "yes" );
456 declare ( suffix
, "rr-active" , "Use the active column in the RR table" , "yes" );
457 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" );
460 DNSBackend
* make ( const string
& suffix
= "" ) {
461 return new MyDNSBackend ( suffix
);
470 BackendMakers (). report ( new MyDNSFactory ());
471 L
<< Logger :: Info
<< "[mydnsbackend] This is the mydns backend version " VERSION
473 << " (" __DATE__
" " __TIME__
")"
475 << " reporting" << endl
;
479 static MyDNSLoader mydnsloader
;