]>
git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/zone2sql.cc
2 PowerDNS Versatile Database Driven Nameserver
3 Copyright (C) 2002 - 2011 PowerDNS.COM BV
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License version 2
7 as published by the Free Software Foundation
9 Additionally, the license of this program contains a special
10 exception which allows to distribute the program in binary form when
11 it is linked against OpenSSL.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22 /* accepts a named.conf or a zone as parameter and outputs heaps of sql */
34 #include "namespaces.hh"
36 #include "arguments.hh"
37 #include "bindparserclasses.hh"
40 #include "dnspacket.hh"
41 #include "zoneparser-tng.hh"
42 #include "dnsrecords.hh"
43 #include <boost/algorithm/string.hpp>
44 #include <sys/types.h>
52 enum dbmode_t
{MYSQL
, GORACLE
, POSTGRES
, SQLITE
, MYDNS
, ORACLE
};
53 static dbmode_t g_mode
;
54 static bool g_intransaction
;
55 static int g_numRecords
;
58 /* this is an official wart. We don't terminate domains on a . in PowerDNS,
59 which is fine as it goes, except for encoding the root, it would end up as '',
60 which leads to ambiguities in the content field. Therefore, if we encounter
61 the root as a . in a BIND zone, we leave it as a ., and don't replace it by
62 an empty string. Back in 1999 we made the wrong choice. */
64 static string
stripDotContent(const string
& content
)
66 if(boost::ends_with(content
, " .") || content
==".")
68 return stripDot(content
);
71 static string
sqlstr(const string
&name
)
73 if(g_mode
== SQLITE
|| g_mode
==GORACLE
|| g_mode
==ORACLE
)
74 return "'"+boost::replace_all_copy(name
, "'", "''")+"'";
78 for(string::const_iterator i
=name
.begin();i
!=name
.end();++i
) {
79 if(*i
=='\'' || *i
=='\\'){
86 if(g_mode
== POSTGRES
)
92 static void startNewTransaction()
94 if(!::arg().mustDo("transactions"))
98 if(g_mode
==POSTGRES
) {
99 cout
<<"COMMIT WORK;"<<endl
;
101 else if(g_mode
== MYSQL
|| g_mode
== SQLITE
|| g_mode
== MYDNS
) {
102 cout
<<"COMMIT;"<<endl
;
107 if(g_mode
== MYSQL
|| g_mode
== MYDNS
)
108 cout
<<"BEGIN;"<<endl
;
109 else if (g_mode
!=GORACLE
&& g_mode
!=ORACLE
)
110 cout
<<"BEGIN TRANSACTION;"<<endl
;
113 static void emitDomain(const DNSName
& domain
, const vector
<string
> *masters
= 0) {
114 string iDomain
= domain
.toStringRootDot();
115 if(!::arg().mustDo("slave")) {
116 if(g_mode
==POSTGRES
|| g_mode
==MYSQL
|| g_mode
==SQLITE
) {
117 cout
<<"insert into domains (name,type) values ("<<toLower(sqlstr(iDomain
))<<",'NATIVE');"<<endl
;
119 else if(g_mode
==GORACLE
) {
120 cout
<<"insert into domains (id,name,type) values (domains_id_sequence.nextval,"<<toLower(sqlstr(iDomain
))<<",'NATIVE');"<<endl
;
122 else if(g_mode
==ORACLE
) {
123 cout
<<"INSERT INTO Zones (id, name, type) VALUES (zones_id_seq.nextval, "<<sqlstr(toLower(iDomain
))<<", 'NATIVE');"<<endl
;
129 if(g_mode
==POSTGRES
|| g_mode
==MYSQL
|| g_mode
==SQLITE
) {
131 if (masters
!= 0 && ! masters
->empty()) {
132 for(const string
& mstr
: *masters
) {
134 mstrs
.append(1, ' ');
138 cout
<<"insert into domains (name,type) values ("<<sqlstr(iDomain
)<<",'NATIVE');"<<endl
;
140 cout
<<"insert into domains (name,type,master) values ("<<sqlstr(iDomain
)<<",'SLAVE'"<<", '"<<mstrs
<<"');"<<endl
;
142 else if (g_mode
== GORACLE
|| g_mode
==ORACLE
) {
143 cerr
<<"Slave import mode not supported with oracle."<<endl
;
148 bool g_doJSONComments
;
149 static void emitRecord(const DNSName
& zoneName
, const DNSName
&DNSqname
, const string
&qtype
, const string
&ocontent
, int ttl
, const string
& comment
="")
151 string qname
= DNSqname
.toStringRootDot();
152 string zname
= zoneName
.toStringRootDot();
155 string recordcomment
;
157 if(g_doJSONComments
& !comment
.empty()) {
158 string::size_type pos
= comment
.find("json={");
159 if(pos
!=string::npos
) {
160 string json
= comment
.substr(pos
+5);
162 auto document
= json11::Json::parse(json
, err
);
163 if(document
.is_null())
164 throw runtime_error("Could not parse JSON '"+json
+"': " + err
);
166 disabled
=document
["disabled"].bool_value();
167 recordcomment
=document
["comment"].string_value();
172 string
content(ocontent
);
174 if(qtype
== "NSEC" || qtype
== "NSEC3")
175 return; // NSECs do not go in the database
177 if((qtype
== "MX" || qtype
== "SRV") && g_mode
!=ORACLE
) {
178 prio
=pdns_stou(content
);
180 string::size_type pos
= content
.find_first_not_of("0123456789");
181 if(pos
!= string::npos
)
182 boost::erase_head(content
, pos
);
187 if(qtype
== "NS" && !pdns_iequals(qname
, zname
)) {
191 if(g_mode
==MYSQL
|| g_mode
==SQLITE
) {
192 cout
<<"insert into records (domain_id, name, type,content,ttl,prio,disabled) select id ,"<<
193 sqlstr(toLower(qname
))<<", "<<
194 sqlstr(qtype
)<<", "<<
195 sqlstr(stripDotContent(content
))<<", "<<ttl
<<", "<<prio
<<", "<<disabled
<<
196 " from domains where name="<<toLower(sqlstr(zname
))<<";\n";
198 if(!recordcomment
.empty()) {
199 cout
<<"insert into comments (domain_id,name,type,modified_at, comment) select id, "<<toLower(sqlstr(stripDot(qname
)))<<", "<<sqlstr(qtype
)<<", "<<time(0)<<", "<<sqlstr(recordcomment
)<<" from domains where name="<<toLower(sqlstr(zname
))<<";\n";
202 else if(g_mode
==POSTGRES
) {
203 cout
<<"insert into records (domain_id, name, ordername, auth, type,content,ttl,prio,disabled) select id ,"<<
204 sqlstr(toLower(qname
))<<", "<<
205 sqlstr(toLower(labelReverse(makeRelative(qname
, zname
))))<<", '"<< (auth
? 't' : 'f') <<"', "<<
206 sqlstr(qtype
)<<", "<<
207 sqlstr(stripDotContent(content
))<<", "<<ttl
<<", "<<prio
<<", '"<<(disabled
? 't': 'f') <<
208 "' from domains where name="<<toLower(sqlstr(zname
))<<";\n";
210 else if(g_mode
==GORACLE
) {
211 cout
<<"insert into Records (id, domain_id, name, type, content, ttl, prio, disabled) select RECORDS_ID_SEQUENCE.nextval,id ,"<<
212 sqlstr(toLower(qname
))<<", "<<
213 sqlstr(qtype
)<<", "<<
214 sqlstr(stripDotContent(content
))<<", "<<ttl
<<", "<<prio
<<", "<<disabled
<<
215 " from Domains where name="<<toLower(sqlstr(zname
))<<";\n";
217 else if(g_mode
==ORACLE
) {
218 cout
<<"INSERT INTO Records (id, zone_id, fqdn, ttl, type, content) SELECT records_id_seq.nextval, id, "<<
219 sqlstr(toLower(qname
))<<", "<<
220 ttl
<<", "<<sqlstr(qtype
)<<", "<<
221 sqlstr(stripDotContent(content
))<<
222 " FROM Zones WHERE name="<<toLower(sqlstr(zname
))<<";"<<endl
;
224 else if (g_mode
== MYDNS
) {
225 string zoneNameDot
= zname
+ ".";
226 if (qtype
== "A" || qtype
== "AAAA" || qtype
== "CNAME" || qtype
== "HINFO" || qtype
== "MX" || qtype
== "NAPTR" ||
227 qtype
== "NS" || qtype
== "PTR" || qtype
== "RP" || qtype
== "SRV" || qtype
== "TXT")
229 if ((qtype
== "MX" || qtype
== "NS" || qtype
== "SRV" || qtype
== "CNAME") && content
[content
.size()-1] != '.')
231 cout
<<"INSERT INTO rr(zone, name, type, data, aux, ttl) VALUES("<<
232 "(SELECT id FROM soa WHERE origin = "<<
233 sqlstr(toLower(zoneNameDot
))<<"), "<<
234 sqlstr(toLower(DNSqname
.toString()))<<", "<<
235 sqlstr(qtype
)<<", "<<sqlstr(content
)<<", "<<prio
<<", "<<ttl
<<");\n";
237 else if (qtype
== "SOA") {
238 //pdns CONTENT = ns1.wtest.com. ahu.example.com. 2005092501 28800 7200 604800 86400
239 vector
<string
> parts
;
240 stringtok(parts
, content
);
242 cout
<<"INSERT INTO soa(origin, ns, mbox, serial, refresh, retry, expire, minimum, ttl) VALUES("<<
243 sqlstr(toLower(zoneNameDot
))<<", "<<sqlstr(parts
[0])<<", "<<sqlstr(parts
[1])<<", "<<pdns_stou(parts
[2])<<", "<<
244 pdns_stou(parts
[3])<<", "<<pdns_stou(parts
[4])<<", "<<pdns_stou(parts
[5])<<", "<<pdns_stou(parts
[6])<<", "<<ttl
<<");\n";
248 cerr
<<"Record type "<<qtype
<<" is not supported."<<endl
;
254 /* 2 modes of operation, either --named or --zone (the latter needs $ORIGIN)
255 2 further modes: --mysql, --goracle or --oracle
260 static ArgvMap theArg
;
265 int main(int argc
, char **argv
)
270 std::ios_base::sync_with_stdio(false);
273 ::arg().setSwitch("gpgsql","Output in format suitable for default gpgsqlbackend")="no";
274 ::arg().setSwitch("gmysql","Output in format suitable for default gmysqlbackend")="no";
275 ::arg().setSwitch("mydns","Output in format suitable for default mydnsbackend")="no";
276 ::arg().setSwitch("goracle","Output in format suitable for the goraclebackend")="no";
277 ::arg().setSwitch("oracle","Output in format suitable for the oraclebackend")="no";
278 ::arg().setSwitch("gsqlite","Output in format suitable for default gsqlitebackend")="no";
279 ::arg().setSwitch("verbose","Verbose comments on operation")="no";
280 ::arg().setSwitch("slave","Keep BIND slaves as slaves. Only works with named-conf.")="no";
281 ::arg().setSwitch("json-comments","Parse json={} field for disabled & comments")="no";
282 ::arg().setSwitch("transactions","If target SQL supports it, use transactions")="no";
283 ::arg().setSwitch("on-error-resume-next","Continue after errors")="no";
284 ::arg().setSwitch("filter-duplicate-soa","Filter second SOA in zone")="yes";
285 ::arg().set("zone","Zonefile to parse")="";
286 ::arg().set("zone-name","Specify an $ORIGIN in case it is not present")="";
287 ::arg().set("named-conf","Bind 8/9 named.conf to parse")="";
289 ::arg().set("soa-minimum-ttl","Do not change")="0";
290 ::arg().set("soa-refresh-default","Do not change")="0";
291 ::arg().set("soa-retry-default","Do not change")="0";
292 ::arg().set("soa-expire-default","Do not change")="0";
294 ::arg().setCmd("help","Provide a helpful message");
296 S
.declare("logmessages");
301 ::arg().parse(argc
, argv
);
303 if(::arg().mustDo("help")) {
304 cout
<<"syntax:"<<endl
<<endl
;
305 cout
<<::arg().helpstring()<<endl
;
310 cerr
<<"syntax:"<<endl
<<endl
;
311 cerr
<<::arg().helpstring()<<endl
;
315 bool filterDupSOA
= ::arg().mustDo("filter-duplicate-soa");
317 g_doJSONComments
=::arg().mustDo("json-comments");
319 if(::arg().mustDo("gmysql"))
321 else if(::arg().mustDo("gpgsql"))
323 else if(::arg().mustDo("gsqlite"))
325 else if(::arg().mustDo("goracle"))
327 else if(::arg().mustDo("oracle"))
329 else if(::arg().mustDo("mydns"))
332 cerr
<<"Unknown SQL mode!\n\n";
333 cerr
<<"syntax:"<<endl
<<endl
;
334 cerr
<<::arg().helpstring()<<endl
;
338 if((g_mode
==GORACLE
|| g_mode
==ORACLE
) && !::arg().mustDo("transactions"))
339 cout
<<"set autocommit on;"<<endl
;
341 namedfile
=::arg()["named-conf"];
342 zonefile
=::arg()["zone"];
344 int count
=0, num_domainsdone
=0;
346 if(zonefile
.empty()) {
348 BP
.setVerbose(::arg().mustDo("verbose"));
349 BP
.parse(namedfile
.empty() ? "./named.conf" : namedfile
);
351 vector
<BindDomainInfo
> domains
=BP
.getDomains();
353 for(vector
<BindDomainInfo
>::iterator i
=domains
.begin(); i
!=domains
.end(); ++i
) {
354 if(stat(i
->filename
.c_str(), &st
) == 0) {
355 i
->d_dev
= st
.st_dev
;
356 i
->d_ino
= st
.st_ino
;
360 sort(domains
.begin(), domains
.end()); // put stuff in inode order
362 int numdomains
=domains
.size();
363 int tick
=numdomains
/100;
365 for(vector
<BindDomainInfo
>::const_iterator i
=domains
.begin();
369 if(i
->type
!="master" && i
->type
!="slave") {
370 cerr
<<" Warning! Skipping '"<<i
->type
<<"' zone '"<<i
->name
.toString()<<"'"<<endl
;
374 startNewTransaction();
376 emitDomain(i
->name
, &(i
->masters
));
378 ZoneParserTNG
zpt(i
->filename
, i
->name
, BP
.getDirectory());
379 DNSResourceRecord rr
;
382 while(zpt
.get(rr
, &comment
)) {
383 if(filterDupSOA
&& seenSOA
&& rr
.qtype
.getCode() == QType::SOA
)
385 if(rr
.qtype
.getCode() == QType::SOA
)
388 emitRecord(i
->name
, rr
.qname
, rr
.qtype
.getName(), rr
.content
, rr
.ttl
, comment
);
392 catch(std::exception
&ae
) {
393 if(!::arg().mustDo("on-error-resume-next"))
396 cerr
<<endl
<<ae
.what()<<endl
;
398 catch(PDNSException
&ae
) {
399 if(!::arg().mustDo("on-error-resume-next"))
402 cerr
<<ae
.reason
<<endl
;
406 if(!tick
|| !((count
++)%tick
))
407 cerr
<<"\r"<<count
*100/numdomains
<<"% done ("<<i
->filename
<<")\033\133\113";
409 cerr
<<"\r100% done\033\133\113"<<endl
;
413 if(!::arg()["zone-name"].empty())
414 zonename
= DNSName(::arg()["zone-name"]);
416 ZoneParserTNG
zpt(zonefile
, zonename
);
417 DNSResourceRecord rr
;
418 startNewTransaction();
421 bool haveEmittedZone
= false;
422 while(zpt
.get(rr
, &comment
)) {
423 if(filterDupSOA
&& seenSOA
&& rr
.qtype
.getCode() == QType::SOA
)
425 if(rr
.qtype
.getCode() == QType::SOA
)
427 if(!haveEmittedZone
) {
428 if(!zpt
.getZoneName().empty()){
429 emitDomain(zpt
.getZoneName());
430 haveEmittedZone
= true;
432 // We have no zonename yet, don't emit
437 emitRecord(zpt
.getZoneName(), rr
.qname
, rr
.qtype
.getName(), rr
.content
, rr
.ttl
, comment
);
441 cerr
<<num_domainsdone
<<" domains were fully parsed, containing "<<g_numRecords
<<" records\n";
443 if(::arg().mustDo("transactions") && g_intransaction
) {
445 cout
<<"COMMIT WORK;"<<endl
;
447 cout
<<"COMMIT;"<<endl
;
451 catch(PDNSException
&ae
) {
452 cerr
<<"\nFatal error: "<<ae
.reason
<<endl
;
455 catch(std::exception
&e
) {
456 cerr
<<"\ndied because of STL error: "<<e
.what()<<endl
;
460 cerr
<<"\ndied because of unknown exception"<<endl
;