]>
git.ipfire.org Git - thirdparty/pdns.git/blob - modules/mydnsbackend/mydnsbackend.cc
2 * PowerDNS backend module for MyDNS style databases
3 * Author: Jonathan Oddy (Hostway UK) <jonathan@woaf.net>
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.
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.
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!
19 * If you do not know what mydns is: http://mydns.bboy.net/
28 #include "pdns/namespaces.hh"
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"
39 #include <modules/gmysqlbackend/smysql.hh>
41 static string backendName
= "[MyDNSbackend]" ;
43 MyDNSBackend :: MyDNSBackend ( const string
& suffix
) {
44 setArgPrefix ( "mydns" + suffix
);
47 d_db
= new SMySQL ( getArg ( "dbname" ),
55 catch ( SSqlException
& e
) {
56 L
<< Logger :: Error
<< backendName
<< " Connection failed: " << e
. txtReason ()<< endl
;
57 throw PDNSException ( backendName
+ "Unable to launch connection: " + e
. txtReason ());
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" );
67 L
<< Logger :: Warning
<< backendName
<< " Connection successful" << endl
;
70 MyDNSBackend ::~ MyDNSBackend () {
76 void MyDNSBackend :: Query ( const string
& query
) {
79 } catch ( SSqlException
& e
) {
80 throw PDNSException ( "Query failed: " + e
. txtReason ());
84 bool MyDNSBackend :: list ( const string
& target
, int zoneId
, bool include_disabled
) {
89 d_db
-> setLog (:: arg (). mustDo ( "query-logging" ));
91 query
= "select origin, minimum from " + d_soatable
+ " where id = " ;
93 if (! d_soawhere
. empty ())
94 query
+= " and " + d_soawhere
;
98 if (! d_db
-> getRow ( rrow
))
99 return false ; // No such zone
102 if ( d_origin
[ d_origin
. length ()- 1 ] == '.' )
103 d_origin
. erase ( d_origin
. length ()- 1 );
104 d_minimum
= atol ( rrow
[ 1 ]. c_str ());
106 while ( d_db
-> getRow ( rrow
)) {
107 L
<< Logger :: Warning
<< backendName
<< " Found more than one matching origin for zone ID: " << zoneId
<< endl
;
110 query
= "select type, data, aux, ttl, zone, name from " + d_rrtable
+ " where zone = " ;
112 if (! d_rrwhere
. empty ())
113 query
+= " and " + d_rrwhere
;
123 bool MyDNSBackend :: getSOA ( const string
& name
, SOAData
& soadata
, DNSPacket
*) {
127 d_db
-> setLog (:: arg (). mustDo ( "query-logging" ));
132 query
= "select id, mbox, serial, ns, refresh, retry, expire, minimum, ttl from " + d_soatable
+ " where origin = '" ;
134 if ( name
. find_first_of ( "' \\ " )!= string :: npos
)
135 query
+= d_db
-> escape ( name
);
140 if (! d_soawhere
. empty ())
141 query
+= " and " + d_soawhere
;
145 if (!( d_db
-> getRow ( rrow
))) {
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
;
164 while ( d_db
-> getRow ( rrow
)) {
165 L
<< Logger :: Warning
<< backendName
<< " Found more than one matching zone for: " + name
<< endl
;
171 void MyDNSBackend :: lookup ( const QType
& qtype
, const string
& qname
, DNSPacket
* p
, int zoneId
) {
174 string zoneIdStr
= itoa ( zoneId
);
180 d_db
-> setLog (:: arg (). mustDo ( "query-logging" ));
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
);
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.
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
;
207 if ( d_db
-> getRow ( rrow
)) {
210 if ( d_origin
[ d_origin
. length ()- 1 ] == '.' )
211 d_origin
. erase ( d_origin
. length ()- 1 );
212 d_minimum
= atol ( rrow
[ 2 ]. c_str ());
217 pos
= sname
. find_first_of ( "." , pos
+ 1 );
218 sdom
= sname
. substr ( pos
+ 1 );
222 query
= "select origin, minimum from " + d_soatable
+ " where id = " ;
224 if (! d_soawhere
. empty ())
225 query
+= " and " + d_soawhere
;
229 if (! d_db
-> getRow ( rrow
)) {
230 throw PDNSException ( "lookup() passed zoneId = " + zoneIdStr
+ " but no such zone!" );
235 if ( d_origin
[ d_origin
. length ()- 1 ] == '.' )
236 d_origin
. erase ( d_origin
. length ()- 1 );
237 d_minimum
= atol ( rrow
[ 1 ]. c_str ());
243 while ( d_db
-> getRow ( rrow
)) {
244 L
<< Logger :: Warning
<< backendName
<< " Found more than one matching zone for: " + d_origin
<< endl
;
246 // We found the zoneId, so we can work out how to find our rr
249 // The host part of the query is the name less the origin
250 if ( qname
. length () == d_origin
. length ())
253 host
= qname
. substr ( 0 , ( qname
. length () - d_origin
. length ())- 1 );
255 if ( host
. find_first_of ( "' \\ " )!= string :: npos
)
256 host
= d_db
-> escape ( host
);
258 query
= "select type, data, aux, ttl, zone from " + d_rrtable
+ " where zone = " ;
260 query
+= " and (name = '" + host
+ "' or name = '" + sname
+ "')" ;
262 if ( qtype
. getCode ()!= 255 ) { // ANY
263 query
+= " and type='" ;
264 query
+= qtype
. getName ();
268 if (! d_rrwhere
. empty ())
269 query
+= " and " + d_rrwhere
;
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
;
277 query
+= " order by type,aux,data" ;
286 bool MyDNSBackend :: get ( DNSResourceRecord
& rr
) {
287 if ( d_origin
. empty ()) {
288 // This happens if lookup() couldn't find the zone
294 if (! d_db
-> getRow ( rrow
)) {
299 rr
. content
= rrow
[ 1 ];
301 if (! d_qname
. empty ()) {
302 // use this to distinguish between select with 'name' field (list()) and one without
306 if ( rr
. qname
[ rr
. qname
. length ()- 1 ] == '.' ) {
307 rr
. qname
. erase ( rr
. qname
. length ()- 1 ); // Fully qualified, nuke the last .
309 if (! rr
. qname
. empty ())
311 rr
. qname
+= d_origin
; // Not fully qualified
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 .
321 if (! rr
. content
. empty ())
323 rr
. content
+= d_origin
;
327 rr
. priority
= atol ( rrow
[ 2 ]. c_str ());
328 rr
. ttl
= atol ( rrow
[ 3 ]. c_str ());
329 if ( d_useminimalttl
&& rr
. ttl
< d_minimum
)
331 rr
. domain_id
= atol ( rrow
[ 4 ]. c_str ());
340 class MyDNSFactory
: public BackendFactory
{
343 MyDNSFactory () : BackendFactory ( "mydns" ) {}
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" );
361 MyDNSBackend
* make ( const string
& suffix
= "" ) {
362 return new MyDNSBackend ( suffix
);
371 BackendMakers (). report ( new MyDNSFactory ());
372 L
<< Logger :: Info
<< "[mydnsbackend] This is the mydns backend version " VERSION
" (" __DATE__
", " __TIME__
") reporting" << endl
;
376 static MyDNSLoader mydnsloader
;