]>
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");
295 ::arg().setCmd("version","Print the version");
297 S
.declare("logmessages");
302 ::arg().parse(argc
, argv
);
304 if(::arg().mustDo("version")) {
305 cerr
<<"zone2sql "<<VERSION
<<endl
;
309 if(::arg().mustDo("help")) {
310 cout
<<"syntax:"<<endl
<<endl
;
311 cout
<<::arg().helpstring()<<endl
;
316 cerr
<<"syntax:"<<endl
<<endl
;
317 cerr
<<::arg().helpstring()<<endl
;
321 bool filterDupSOA
= ::arg().mustDo("filter-duplicate-soa");
323 g_doJSONComments
=::arg().mustDo("json-comments");
325 if(::arg().mustDo("gmysql"))
327 else if(::arg().mustDo("gpgsql"))
329 else if(::arg().mustDo("gsqlite"))
331 else if(::arg().mustDo("goracle"))
333 else if(::arg().mustDo("oracle"))
335 else if(::arg().mustDo("mydns"))
338 cerr
<<"Unknown SQL mode!\n\n";
339 cerr
<<"syntax:"<<endl
<<endl
;
340 cerr
<<::arg().helpstring()<<endl
;
344 if((g_mode
==GORACLE
|| g_mode
==ORACLE
) && !::arg().mustDo("transactions"))
345 cout
<<"set autocommit on;"<<endl
;
347 namedfile
=::arg()["named-conf"];
348 zonefile
=::arg()["zone"];
350 int count
=0, num_domainsdone
=0;
352 if(zonefile
.empty()) {
354 BP
.setVerbose(::arg().mustDo("verbose"));
355 BP
.parse(namedfile
.empty() ? "./named.conf" : namedfile
);
357 vector
<BindDomainInfo
> domains
=BP
.getDomains();
359 for(vector
<BindDomainInfo
>::iterator i
=domains
.begin(); i
!=domains
.end(); ++i
) {
360 if(stat(i
->filename
.c_str(), &st
) == 0) {
361 i
->d_dev
= st
.st_dev
;
362 i
->d_ino
= st
.st_ino
;
366 sort(domains
.begin(), domains
.end()); // put stuff in inode order
368 int numdomains
=domains
.size();
369 int tick
=numdomains
/100;
371 for(vector
<BindDomainInfo
>::const_iterator i
=domains
.begin();
375 if(i
->type
!="master" && i
->type
!="slave") {
376 cerr
<<" Warning! Skipping '"<<i
->type
<<"' zone '"<<i
->name
.toString()<<"'"<<endl
;
380 startNewTransaction();
382 emitDomain(i
->name
, &(i
->masters
));
384 ZoneParserTNG
zpt(i
->filename
, i
->name
, BP
.getDirectory());
385 DNSResourceRecord rr
;
388 while(zpt
.get(rr
, &comment
)) {
389 if(filterDupSOA
&& seenSOA
&& rr
.qtype
.getCode() == QType::SOA
)
391 if(rr
.qtype
.getCode() == QType::SOA
)
394 emitRecord(i
->name
, rr
.qname
, rr
.qtype
.getName(), rr
.content
, rr
.ttl
, comment
);
398 catch(std::exception
&ae
) {
399 if(!::arg().mustDo("on-error-resume-next"))
402 cerr
<<endl
<<ae
.what()<<endl
;
404 catch(PDNSException
&ae
) {
405 if(!::arg().mustDo("on-error-resume-next"))
408 cerr
<<ae
.reason
<<endl
;
412 if(!tick
|| !((count
++)%tick
))
413 cerr
<<"\r"<<count
*100/numdomains
<<"% done ("<<i
->filename
<<")\033\133\113";
415 cerr
<<"\r100% done\033\133\113"<<endl
;
419 if(!::arg()["zone-name"].empty())
420 zonename
= DNSName(::arg()["zone-name"]);
422 ZoneParserTNG
zpt(zonefile
, zonename
);
423 DNSResourceRecord rr
;
424 startNewTransaction();
427 bool haveEmittedZone
= false;
428 while(zpt
.get(rr
, &comment
)) {
429 if(filterDupSOA
&& seenSOA
&& rr
.qtype
.getCode() == QType::SOA
)
431 if(rr
.qtype
.getCode() == QType::SOA
)
433 if(!haveEmittedZone
) {
434 if(!zpt
.getZoneName().empty()){
435 emitDomain(zpt
.getZoneName());
436 haveEmittedZone
= true;
438 // We have no zonename yet, don't emit
443 emitRecord(zpt
.getZoneName(), rr
.qname
, rr
.qtype
.getName(), rr
.content
, rr
.ttl
, comment
);
447 cerr
<<num_domainsdone
<<" domains were fully parsed, containing "<<g_numRecords
<<" records\n";
449 if(::arg().mustDo("transactions") && g_intransaction
) {
451 cout
<<"COMMIT WORK;"<<endl
;
453 cout
<<"COMMIT;"<<endl
;
457 catch(PDNSException
&ae
) {
458 cerr
<<"\nFatal error: "<<ae
.reason
<<endl
;
461 catch(std::exception
&e
) {
462 cerr
<<"\ndied because of STL error: "<<e
.what()<<endl
;
466 cerr
<<"\ndied because of unknown exception"<<endl
;