]>
git.ipfire.org Git - thirdparty/pdns.git/blob - modules/mydnsbackend/mydnsbackend.cc
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
4 * originally authored by Jonathan Oddy
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of version 2 of the GNU General Public License as
8 * published by the Free Software Foundation.
10 * In addition, for the avoidance of any doubt, permission is granted to
11 * link this program with OpenSSL and to (re)distribute the binaries
12 * produced as the result of such linking.
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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 * The schema used by MyDNS isn't suitable for retrieving results with a single
26 * query. This means that existing PowerDNS backends are unable to make use of
27 * the schema without lame hackery (or awful performance.) This module does
28 * the nasty lookup logic required to make use of the schema, and should be as
29 * tolerant as MyDNS when it comes to things being fully qualified or not.
31 * A known "bug" is that AXFRs will fail if your rr table contains invalid
32 * junk. I'm not sure this is really a bug, if you've decided to put free-form
33 * text in your data for an A record you have bigger issues.
35 * I'd advise avoiding the MyDNS schema if at all possible as the query count
36 * for even simple lookups is daft. It's quite trivial to craft a request
37 * that'll require 128 database queries to answer with a servfail!
39 * If you do not know what mydns is: http://mydns.bboy.net/
51 #include "pdns/namespaces.hh"
53 #include "pdns/dns.hh"
54 #include "pdns/dnsbackend.hh"
55 #include "mydnsbackend.hh"
56 #include "pdns/dnspacket.hh"
57 #include "pdns/pdnsexception.hh"
58 #include "pdns/logger.hh"
59 #include "pdns/arguments.hh"
61 #include <modules/gmysqlbackend/smysql.hh>
63 static string backendName
= "[MyDNSbackend]" ;
65 MyDNSBackend :: MyDNSBackend ( const string
& suffix
) {
66 setArgPrefix ( "mydns" + suffix
);
69 d_db
= new SMySQL ( getArg ( "dbname" ),
75 d_db
-> setLog (:: arg (). mustDo ( "query-logging" ));
77 catch ( SSqlException
& e
) {
78 g_log
<< Logger :: Error
<< backendName
<< " Connection failed: " << e
. txtReason ()<< endl
;
79 throw PDNSException ( backendName
+ "Unable to launch connection: " + e
. txtReason ());
82 string rrtable
= getArg ( "rr-table" );
83 string soatable
= getArg ( "soa-table" );
84 string rrwhere
=( mustDo ( "rr-active" )? "(active = '1' or active = 'Y') and " : "" )+ getArg ( "rr-where" );
85 string soawhere
=( mustDo ( "soa-active" )? "(active = '1' or active = 'Y') and " : "" )+ getArg ( "soa-where" );
87 if ( soatable
. empty ()) { throw PDNSException ( "SOA Table must not be empty" ); }
88 if ( rrtable
. empty ()) { throw PDNSException ( "Records table must not be empty" ); }
90 d_useminimalttl
= mustDo ( "use-minimal-ttl" );
93 g_log
<< Logger :: Warning
<< backendName
<< " Connection successful" << endl
;
97 string domainIdQuery
= "SELECT origin, minimum FROM `" + soatable
+ "` WHERE id = ?" ;
98 string domainNoIdQuery
= "SELECT id, origin, minimum FROM `" + soatable
+ "` WHERE origin = ?" ;
99 string soaQuery
= "SELECT id, mbox, serial, ns, refresh, retry, expire, minimum, ttl FROM `" + soatable
+ "` WHERE origin = ?" ;
100 string allDomainsQuery
= "SELECT id, origin, serial FROM `" + soatable
+ "`" ;
102 if (! soawhere
. empty ()) {
103 domainIdQuery
+= " AND " + soawhere
;
104 domainNoIdQuery
+= " AND " + soawhere
;
105 soaQuery
+= " AND " + soawhere
;
106 allDomainsQuery
+= " WHERE " + soawhere
;
109 d_domainIdQuery_stmt
= d_db
-> prepare ( domainIdQuery
, 1 );
110 d_domainNoIdQuery_stmt
= d_db
-> prepare ( domainNoIdQuery
, 1 );
111 d_soaQuery_stmt
= d_db
-> prepare ( soaQuery
, 1 );
112 d_allDomainsQuery_stmt
= d_db
-> prepare ( allDomainsQuery
, 0 );
114 string listQuery
= "SELECT type, data, aux, ttl, zone, name FROM `" + rrtable
+ "` WHERE zone = ?" ;
115 string basicQuery
= "SELECT type, data, aux, ttl, zone FROM `" + rrtable
+ "` WHERE zone = ? AND (name = ? OR name = ?) AND type = ?" ;
116 string anyQuery
= "(SELECT type, data, aux, ttl, zone FROM `" + rrtable
+ "` WHERE zone = ? AND (name = ? OR name = ?)" ;
118 if (! rrwhere
. empty ()) {
119 listQuery
+= " AND " + rrwhere
;
120 basicQuery
+= " AND " + rrwhere
;
121 anyQuery
+= " AND " + rrwhere
;
124 d_listQuery_stmt
= d_db
-> prepare ( listQuery
, 1 );
126 anyQuery
+= ") UNION (SELECT 'SOA' AS type, CONCAT_WS(' ', ns, mbox,serial,refresh,retry,expire,minimum) AS data, '0' AS aux, ttl, id AS zone FROM `" + soatable
+ "` WHERE id = ? AND origin = ?" ;
128 if (! soawhere
. empty ()) {
129 anyQuery
+= " AND " + soawhere
;
132 basicQuery
+= " ORDER BY type,aux,data" ;
133 anyQuery
+= ") ORDER BY type,aux,data" ;
135 d_basicQuery_stmt
= d_db
-> prepare ( basicQuery
, 4 );
136 d_anyQuery_stmt
= d_db
-> prepare ( anyQuery
, 5 );
137 } catch ( SSqlException
& e
) {
138 g_log
<< Logger :: Error
<< "Cannot prepare statements: " << e
. txtReason () << endl
;
139 throw PDNSException ( "Cannot prepare statements: " + e
. txtReason ());
141 // keeps static analyzers happy
142 d_query_stmt
= nullptr ;
145 MyDNSBackend ::~ MyDNSBackend () {
146 d_domainIdQuery_stmt
. release ();
147 d_domainNoIdQuery_stmt
. release ();
148 d_listQuery_stmt
. release ();
149 d_soaQuery_stmt
. release ();
150 d_basicQuery_stmt
. release ();
151 d_anyQuery_stmt
. release ();
152 d_allDomainsQuery_stmt
. release ();
157 bool MyDNSBackend :: list ( const DNSName
& target
, int zoneId
, bool include_disabled
) {
160 SSqlStatement :: row_t rrow
;
163 d_domainIdQuery_stmt
->
164 bind ( "domain_id" , zoneId
)->
166 getResult ( d_result
)->
169 catch ( SSqlException
& e
) {
170 throw PDNSException ( "MyDNSBackend unable to list domain_id " + itoa ( zoneId
)+ ": " + e
. txtReason ());
173 if ( d_result
. empty ())
174 return false ; // No such zone
176 d_origin
= d_result
[ 0 ][ 0 ];
177 if ( d_origin
[ d_origin
. length ()- 1 ] == '.' )
178 d_origin
. erase ( d_origin
. length ()- 1 );
179 d_minimum
= pdns_stou ( d_result
[ 0 ][ 1 ]);
181 if ( d_result
. size ()> 1 ) {
182 g_log
<< Logger :: Warning
<< backendName
<< " Found more than one matching origin for zone ID: " << zoneId
<< endl
;
186 d_query_stmt
= & d_listQuery_stmt
;
188 bind ( "domain_id" , zoneId
)->
191 catch ( SSqlException
& e
) {
192 throw PDNSException ( "MyDNSBackend unable to list domain_id " + itoa ( zoneId
)+ ": " + e
. txtReason ());
199 bool MyDNSBackend :: getSOA ( const DNSName
& name
, SOAData
& soadata
, bool unmodifiedSerial
) {
201 SSqlStatement :: row_t rrow
;
208 bind ( "origin" , name
. toString ())->
210 getResult ( d_result
)->
213 catch ( SSqlException
& e
) {
214 throw PDNSException ( "MyDNSBackend unable to get soa for domain " + name
. toLogString ()+ ": " + e
. txtReason ());
217 if ( d_result
. empty ()) {
223 soadata
. qname
= name
;
224 soadata
. domain_id
= pdns_stou ( rrow
[ 0 ]);
225 soadata
. hostmaster
= DNSName ( rrow
[ 1 ]);
226 soadata
. serial
= pdns_stou ( rrow
[ 2 ]);
227 soadata
. nameserver
= DNSName ( rrow
[ 3 ]);
228 soadata
. refresh
= pdns_stou ( rrow
[ 4 ]);
229 soadata
. retry
= pdns_stou ( rrow
[ 5 ]);
230 soadata
. expire
= pdns_stou ( rrow
[ 6 ]);
231 soadata
. default_ttl
= pdns_stou ( rrow
[ 7 ]);
232 soadata
. ttl
= pdns_stou ( rrow
[ 8 ]);
233 if ( d_useminimalttl
) {
234 soadata
. ttl
= std :: min ( soadata
. ttl
, soadata
. default_ttl
);
238 if ( d_result
. size ()> 1 ) {
239 g_log
<< Logger :: Warning
<< backendName
<< " Found more than one matching zone for: " << name
<< endl
;
245 void MyDNSBackend :: lookup ( const QType
& qtype
, const DNSName
& qname
, DNSPacket
* p
, int zoneId
) {
246 SSqlStatement :: row_t rrow
;
256 DLOG ( g_log
<< Logger :: Debug
<< "MyDNSBackend::lookup(" << qtype
. getName () << "," << qname
<< ",p," << zoneId
<< ")" << endl
);
259 // First off we need to work out what zone we're working with
260 // MyDNS records aren't always fully qualified, so we need to work out the zone ID.
265 d_domainNoIdQuery_stmt
->
266 bind ( "domain" , sdom
. toString ())->
268 getResult ( d_result
)->
271 catch ( SSqlException
& e
) {
272 throw PDNSException ( "MyDNSBackend unable to lookup " + qname
. toLogString ()+ ": " + e
. txtReason ());
275 if ( d_result
. empty () == false ) {
277 zoneId
= pdns_stou ( rrow
[ 0 ]);
278 d_origin
= stripDot ( rrow
[ 1 ]);
279 d_minimum
= pdns_stou ( rrow
[ 2 ]);
284 } while ( sdom
. chopOff ());
288 d_domainIdQuery_stmt
->
289 bind ( "domain_id" , zoneId
)->
291 getResult ( d_result
)->
294 catch ( SSqlException
& e
) {
295 throw PDNSException ( "MyDNSBackend unable to lookup " + qname
. toLogString ()+ ": " + e
. txtReason ());
298 if ( d_result
. empty ()) {
299 return ; // just return if zone was not found instead of throwing an error
305 d_origin
= stripDot ( rrow
[ 0 ]);
306 d_minimum
= pdns_stou ( rrow
[ 1 ]);
311 while ( d_result
. size ()> 1 ) {
312 g_log
<< Logger :: Warning
<< backendName
<< " Found more than one matching zone for: " + d_origin
<< endl
;
314 // We found the zoneId, so we can work out how to find our rr
317 // The host part of the query is the name less the origin
318 DNSName
origin ( d_origin
);
319 host
= qname
. makeRelative ( origin
). toStringNoDot ();
323 if ( qtype
. getCode ()== QType :: ANY
) {
324 DLOG ( g_log
<< Logger :: Debug
<< "Running d_anyQuery_stmt with " << zoneId
<< ", " << host
<< ", " << sdom
<< ", " << zoneId
<< " , " << qname
<< ", " << qtype
. getName () << endl
);
325 d_query_stmt
= & d_anyQuery_stmt
;
327 bind ( "domain_id" , zoneId
)->
329 bind ( "qname" , qname
. toString ())->
330 bind ( "domain_id" , zoneId
)-> // this is because positional arguments
331 bind ( "qname2" , sdom
. toString ())->
334 DLOG ( g_log
<< Logger :: Debug
<< "Running d_basicQuery_stmt with " << zoneId
<< ", " << host
<< ", " << qname
<< ", " << qtype
. getName () << endl
);
335 d_query_stmt
= & d_basicQuery_stmt
;
337 bind ( "domain_id" , zoneId
)->
339 bind ( "qname" , qname
. toString ())->
340 bind ( "qtype" , qtype
. getName ())->
344 catch ( SSqlException
& e
) {
345 throw PDNSException ( "MyDNSBackend unable to lookup " + qname
. toLogString ()+ ": " + e
. txtReason ());
348 d_qname
= qname
. toString ();
353 bool MyDNSBackend :: get ( DNSResourceRecord
& rr
) {
354 if ( d_origin
. empty ()) {
357 (* d_query_stmt
)-> reset ();
358 } catch ( SSqlException
& e
) {
359 throw PDNSException ( "MyDNSBackend unable to lookup " + d_qname
+ ": " + e
. txtReason ());
363 // This happens if lookup() couldn't find the zone
367 SSqlStatement :: row_t rrow
;
369 if ((* d_query_stmt
)-> hasNextRow ()) {
371 (* d_query_stmt
)-> nextRow ( rrow
);
372 } catch ( SSqlException
& e
) {
373 throw PDNSException ( "MyDNSBackend unable to lookup " + d_qname
+ ": " + e
. txtReason ());
376 rr
. content
= rrow
[ 1 ];
378 if (! d_qname
. empty ()) {
379 // use this to distinguish between select with 'name' field (list()) and one without
380 rr
. qname
= DNSName ( d_qname
);
382 string tmpQname
= rrow
[ 5 ];
385 if (! tmpQname
. empty () && tmpQname
[ tmpQname
. length ()- 1 ] == '.' ) {
386 tmpQname
. erase ( tmpQname
. length ()- 1 ); // Fully qualified, nuke the last .
388 if (! tmpQname
. empty ()) {
391 tmpQname
+= d_origin
; // Not fully qualified
393 rr
. qname
= DNSName ( tmpQname
);
396 if ( rr
. qtype
. getCode () == QType :: NS
|| rr
. qtype
. getCode ()== QType :: MX
||
397 rr
. qtype
. getCode () == QType :: CNAME
|| rr
. qtype
. getCode () == QType :: PTR
) {
398 if (! rr
. content
. empty () && rr
. content
[ rr
. content
. length ()- 1 ] == '.' ) {
399 if ( rr
. content
. length () > 1 )
400 rr
. content
. erase ( rr
. content
. length ()- 1 ); // Fully qualified, nuke the last .
402 if ( rr
. content
!= "." )
404 rr
. content
+= d_origin
;
408 if ( rr
. qtype
. getCode () == QType :: MX
|| rr
. qtype
. getCode () == QType :: SRV
)
409 rr
. content
= rrow
[ 2 ]+ " " + rr
. content
;
411 rr
. ttl
= pdns_stou ( rrow
[ 3 ]);
413 rr
. ttl
= std :: min ( rr
. ttl
, d_minimum
);
414 rr
. domain_id
= pdns_stou ( rrow
[ 4 ]);
422 (* d_query_stmt
)-> reset ();
423 } catch ( SSqlException
& e
) {
424 throw PDNSException ( "MyDNSBackend unable to lookup " + d_qname
+ ": " + e
. txtReason ());
432 void MyDNSBackend :: getAllDomains ( vector
< DomainInfo
> * domains
, bool include_disabled
) {
433 /* include_disabled is unfortunately ignored here */
435 d_allDomainsQuery_stmt
->
438 while ( d_allDomainsQuery_stmt
-> hasNextRow ()) {
439 SSqlStatement :: row_t row
;
441 d_allDomainsQuery_stmt
-> nextRow ( row
);
443 di
. id
= pdns_stou ( row
[ 0 ]);
444 di
. zone
= DNSName ( row
[ 1 ]);
445 di
. serial
= pdns_stou ( row
[ 2 ]);
446 di
. kind
= DomainInfo :: Native
;
449 domains
-> push_back ( di
);
452 d_allDomainsQuery_stmt
->
455 catch ( SSqlException
& e
) {
456 throw PDNSException ( "MyDNSBackend unable to list all domains: " + e
. txtReason ());
460 class MyDNSFactory
: public BackendFactory
{
463 MyDNSFactory () : BackendFactory ( "mydns" ) {}
465 void declareArguments ( const string
& suffix
= "" ) {
466 declare ( suffix
, "dbname" , "Pdns backend database name to connect to" , "mydns" );
467 declare ( suffix
, "user" , "Pdns backend user to connect as" , "powerdns" );
468 declare ( suffix
, "host" , "Pdns backend host to connect to" , "" );
469 declare ( suffix
, "port" , "Pdns backend host to connect to" , "" );
470 declare ( suffix
, "password" , "Pdns backend password to connect with" , "" );
471 declare ( suffix
, "socket" , "Pdns backend socket to connect to" , "" );
472 declare ( suffix
, "rr-table" , "Name of RR table to use" , "rr" );
473 declare ( suffix
, "soa-table" , "Name of SOA table to use" , "soa" );
474 declare ( suffix
, "soa-where" , "Additional WHERE clause for SOA" , "1 = 1" );
475 declare ( suffix
, "rr-where" , "Additional WHERE clause for RR" , "1 = 1" );
476 declare ( suffix
, "soa-active" , "Use the active column in the SOA table" , "yes" );
477 declare ( suffix
, "rr-active" , "Use the active column in the RR table" , "yes" );
478 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" );
481 DNSBackend
* make ( const string
& suffix
= "" ) {
482 return new MyDNSBackend ( suffix
);
491 BackendMakers (). report ( new MyDNSFactory ());
492 g_log
<< Logger :: Info
<< "[mydnsbackend] This is the mydns backend version " VERSION
494 << " (" __DATE__
" " __TIME__
")"
496 << " reporting" << endl
;
500 static MyDNSLoader mydnsloader
;