]>
git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/zone2sql.cc
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of version 2 of the GNU General Public License as
7 * published by the Free Software Foundation.
9 * In addition, for the avoidance of any doubt, permission is granted to
10 * link this program with OpenSSL and to (re)distribute the binaries
11 * produced as the result of such linking.
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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 /* accepts a named.conf or a zone as parameter and outputs heaps of sql */
35 #include "namespaces.hh"
37 #include "arguments.hh"
38 #include "bindparserclasses.hh"
41 #include "dnspacket.hh"
42 #include "zoneparser-tng.hh"
43 #include "dnsrecords.hh"
44 #include <boost/algorithm/string.hpp>
45 #include <sys/types.h>
53 enum dbmode_t
{MYSQL
, GORACLE
, POSTGRES
, SQLITE
, MYDNS
, ORACLE
};
54 static dbmode_t g_mode
;
55 static bool g_intransaction
;
56 static int g_numRecords
;
59 /* this is an official wart. We don't terminate domains on a . in PowerDNS,
60 which is fine as it goes, except for encoding the root, it would end up as '',
61 which leads to ambiguities in the content field. Therefore, if we encounter
62 the root as a . in a BIND zone, we leave it as a ., and don't replace it by
63 an empty string. Back in 1999 we made the wrong choice. */
65 static string
stripDotContent(const string
& content
)
67 if(boost::ends_with(content
, " .") || content
==".")
69 return stripDot(content
);
72 static string
sqlstr(const string
&name
)
74 if(g_mode
== SQLITE
|| g_mode
==GORACLE
|| g_mode
==ORACLE
)
75 return "'"+boost::replace_all_copy(name
, "'", "''")+"'";
79 for(string::const_iterator i
=name
.begin();i
!=name
.end();++i
) {
80 if(*i
=='\'' || *i
=='\\'){
87 if(g_mode
== POSTGRES
)
93 static void startNewTransaction()
95 if(!::arg().mustDo("transactions"))
99 if(g_mode
==POSTGRES
) {
100 cout
<<"COMMIT WORK;"<<endl
;
102 else if(g_mode
== MYSQL
|| g_mode
== SQLITE
|| g_mode
== MYDNS
) {
103 cout
<<"COMMIT;"<<endl
;
108 if(g_mode
== MYSQL
|| g_mode
== MYDNS
)
109 cout
<<"BEGIN;"<<endl
;
110 else if (g_mode
!=GORACLE
&& g_mode
!=ORACLE
)
111 cout
<<"BEGIN TRANSACTION;"<<endl
;
114 static void emitDomain(const DNSName
& domain
, const vector
<ComboAddress
> *masters
= 0) {
115 string iDomain
= domain
.toStringRootDot();
116 if(!::arg().mustDo("slave")) {
117 if(g_mode
==POSTGRES
|| g_mode
==MYSQL
|| g_mode
==SQLITE
) {
118 cout
<<"insert into domains (name,type) values ("<<toLower(sqlstr(iDomain
))<<",'NATIVE');"<<endl
;
120 else if(g_mode
==GORACLE
) {
121 cout
<<"insert into domains (id,name,type) values (domains_id_sequence.nextval,"<<toLower(sqlstr(iDomain
))<<",'NATIVE');"<<endl
;
123 else if(g_mode
==ORACLE
) {
124 cout
<<"INSERT INTO Zones (id, name, type) VALUES (zones_id_seq.nextval, "<<sqlstr(toLower(iDomain
))<<", 'NATIVE');"<<endl
;
130 if(g_mode
==POSTGRES
|| g_mode
==MYSQL
|| g_mode
==SQLITE
) {
132 if (masters
!= 0 && ! masters
->empty()) {
133 for(const auto& mstr
: *masters
) {
134 mstrs
.append(mstr
.toStringWithPortExcept(53));
135 mstrs
.append(1, ' ');
139 cout
<<"insert into domains (name,type) values ("<<sqlstr(iDomain
)<<",'NATIVE');"<<endl
;
141 cout
<<"insert into domains (name,type,master) values ("<<sqlstr(iDomain
)<<",'SLAVE'"<<", '"<<mstrs
<<"');"<<endl
;
143 else if (g_mode
== GORACLE
|| g_mode
==ORACLE
) {
144 cerr
<<"Slave import mode not supported with oracle."<<endl
;
149 bool g_doJSONComments
;
150 static void emitRecord(const DNSName
& zoneName
, const DNSName
&DNSqname
, const string
&qtype
, const string
&ocontent
, int ttl
, const string
& comment
="")
152 string qname
= DNSqname
.toStringRootDot();
153 string zname
= zoneName
.toStringRootDot();
156 string recordcomment
;
158 if(g_doJSONComments
& !comment
.empty()) {
159 string::size_type pos
= comment
.find("json={");
160 if(pos
!=string::npos
) {
161 string json
= comment
.substr(pos
+5);
163 auto document
= json11::Json::parse(json
, err
);
164 if(document
.is_null())
165 throw runtime_error("Could not parse JSON '"+json
+"': " + err
);
167 disabled
=document
["disabled"].bool_value();
168 recordcomment
=document
["comment"].string_value();
173 string
content(ocontent
);
175 if(qtype
== "NSEC" || qtype
== "NSEC3")
176 return; // NSECs do not go in the database
178 if((qtype
== "MX" || qtype
== "SRV") && g_mode
!=ORACLE
) {
179 prio
=pdns_stou(content
);
181 string::size_type pos
= content
.find_first_not_of("0123456789");
182 if(pos
!= string::npos
)
183 boost::erase_head(content
, pos
);
188 if(qtype
== "NS" && !pdns_iequals(qname
, zname
)) {
192 if(g_mode
==MYSQL
|| g_mode
==SQLITE
) {
193 cout
<<"insert into records (domain_id, name, type,content,ttl,prio,disabled) select id ,"<<
194 sqlstr(toLower(qname
))<<", "<<
195 sqlstr(qtype
)<<", "<<
196 sqlstr(stripDotContent(content
))<<", "<<ttl
<<", "<<prio
<<", "<<disabled
<<
197 " from domains where name="<<toLower(sqlstr(zname
))<<";\n";
199 if(!recordcomment
.empty()) {
200 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";
203 else if(g_mode
==POSTGRES
) {
204 cout
<<"insert into records (domain_id, name, ordername, auth, type,content,ttl,prio,disabled) select id ,"<<
205 sqlstr(toLower(qname
))<<", "<<
206 sqlstr(DNSName(qname
).makeRelative(DNSName(zname
)).makeLowerCase().labelReverse().toString(" ", false))<<", '"<< (auth
? 't' : 'f') <<"', "<<
207 sqlstr(qtype
)<<", "<<
208 sqlstr(stripDotContent(content
))<<", "<<ttl
<<", "<<prio
<<", '"<<(disabled
? 't': 'f') <<
209 "' from domains where name="<<toLower(sqlstr(zname
))<<";\n";
211 else if(g_mode
==GORACLE
) {
212 cout
<<"insert into Records (id, domain_id, name, type, content, ttl, prio, disabled) select RECORDS_ID_SEQUENCE.nextval,id ,"<<
213 sqlstr(toLower(qname
))<<", "<<
214 sqlstr(qtype
)<<", "<<
215 sqlstr(stripDotContent(content
))<<", "<<ttl
<<", "<<prio
<<", "<<disabled
<<
216 " from Domains where name="<<toLower(sqlstr(zname
))<<";\n";
218 else if(g_mode
==ORACLE
) {
219 cout
<<"INSERT INTO Records (id, zone_id, fqdn, ttl, type, content) SELECT records_id_seq.nextval, id, "<<
220 sqlstr(toLower(qname
))<<", "<<
221 ttl
<<", "<<sqlstr(qtype
)<<", "<<
222 sqlstr(stripDotContent(content
))<<
223 " FROM Zones WHERE name="<<toLower(sqlstr(zname
))<<";"<<endl
;
225 else if (g_mode
== MYDNS
) {
226 string zoneNameDot
= zname
+ ".";
227 if (qtype
== "A" || qtype
== "AAAA" || qtype
== "CNAME" || qtype
== "HINFO" || qtype
== "MX" || qtype
== "NAPTR" ||
228 qtype
== "NS" || qtype
== "PTR" || qtype
== "RP" || qtype
== "SRV" || qtype
== "TXT")
230 if ((qtype
== "MX" || qtype
== "NS" || qtype
== "SRV" || qtype
== "CNAME") && content
[content
.size()-1] != '.')
232 cout
<<"INSERT INTO rr(zone, name, type, data, aux, ttl) VALUES("<<
233 "(SELECT id FROM soa WHERE origin = "<<
234 sqlstr(toLower(zoneNameDot
))<<"), "<<
235 sqlstr(toLower(DNSqname
.toString()))<<", "<<
236 sqlstr(qtype
)<<", "<<sqlstr(content
)<<", "<<prio
<<", "<<ttl
<<");\n";
238 else if (qtype
== "SOA") {
239 //pdns CONTENT = ns1.wtest.com. ahu.example.com. 2005092501 28800 7200 604800 86400
240 vector
<string
> parts
;
241 stringtok(parts
, content
);
243 cout
<<"INSERT INTO soa(origin, ns, mbox, serial, refresh, retry, expire, minimum, ttl) VALUES("<<
244 sqlstr(toLower(zoneNameDot
))<<", "<<sqlstr(parts
[0])<<", "<<sqlstr(parts
[1])<<", "<<pdns_stou(parts
[2])<<", "<<
245 pdns_stou(parts
[3])<<", "<<pdns_stou(parts
[4])<<", "<<pdns_stou(parts
[5])<<", "<<pdns_stou(parts
[6])<<", "<<ttl
<<");\n";
249 cerr
<<"Record type "<<qtype
<<" is not supported."<<endl
;
255 /* 2 modes of operation, either --named or --zone (the latter needs $ORIGIN)
256 2 further modes: --mysql, --goracle or --oracle
261 static ArgvMap theArg
;
266 int main(int argc
, char **argv
)
270 std::ios_base::sync_with_stdio(false);
272 ::arg().setSwitch("gpgsql","Output in format suitable for default gpgsqlbackend")="no";
273 ::arg().setSwitch("gmysql","Output in format suitable for default gmysqlbackend")="no";
274 ::arg().setSwitch("mydns","Output in format suitable for default mydnsbackend")="no";
275 ::arg().setSwitch("goracle","Output in format suitable for the goraclebackend")="no";
276 ::arg().setSwitch("oracle","Output in format suitable for the oraclebackend")="no";
277 ::arg().setSwitch("gsqlite","Output in format suitable for default gsqlitebackend")="no";
278 ::arg().setSwitch("verbose","Verbose comments on operation")="no";
279 ::arg().setSwitch("slave","Keep BIND slaves as slaves. Only works with named-conf.")="no";
280 ::arg().setSwitch("json-comments","Parse json={} field for disabled & comments")="no";
281 ::arg().setSwitch("transactions","If target SQL supports it, use transactions")="no";
282 ::arg().setSwitch("on-error-resume-next","Continue after errors")="no";
283 ::arg().setSwitch("filter-duplicate-soa","Filter second SOA in zone")="yes";
284 ::arg().set("zone","Zonefile to parse")="";
285 ::arg().set("zone-name","Specify an $ORIGIN in case it is not present")="";
286 ::arg().set("named-conf","Bind 8/9 named.conf to parse")="";
288 ::arg().set("soa-minimum-ttl","Do not change")="0";
289 ::arg().set("soa-refresh-default","Do not change")="0";
290 ::arg().set("soa-retry-default","Do not change")="0";
291 ::arg().set("soa-expire-default","Do not change")="0";
293 ::arg().setCmd("help","Provide a helpful message");
294 ::arg().setCmd("version","Print the version");
296 S
.declare("logmessages");
301 ::arg().parse(argc
, argv
);
303 if(::arg().mustDo("version")) {
304 cerr
<<"zone2sql "<<VERSION
<<endl
;
308 if(::arg().mustDo("help")) {
309 cout
<<"syntax:"<<endl
<<endl
;
310 cout
<<::arg().helpstring()<<endl
;
315 cerr
<<"syntax:"<<endl
<<endl
;
316 cerr
<<::arg().helpstring()<<endl
;
320 bool filterDupSOA
= ::arg().mustDo("filter-duplicate-soa");
322 g_doJSONComments
=::arg().mustDo("json-comments");
324 if(::arg().mustDo("gmysql"))
326 else if(::arg().mustDo("gpgsql"))
328 else if(::arg().mustDo("gsqlite"))
330 else if(::arg().mustDo("goracle"))
332 else if(::arg().mustDo("oracle"))
334 else if(::arg().mustDo("mydns"))
337 cerr
<<"Unknown SQL mode!\n\n";
338 cerr
<<"syntax:"<<endl
<<endl
;
339 cerr
<<::arg().helpstring()<<endl
;
343 if((g_mode
==GORACLE
|| g_mode
==ORACLE
) && !::arg().mustDo("transactions"))
344 cout
<<"set autocommit on;"<<endl
;
346 namedfile
=::arg()["named-conf"];
347 zonefile
=::arg()["zone"];
349 int count
=0, num_domainsdone
=0;
351 if(zonefile
.empty()) {
353 BP
.setVerbose(::arg().mustDo("verbose"));
354 BP
.parse(namedfile
.empty() ? "./named.conf" : namedfile
);
356 vector
<BindDomainInfo
> domains
=BP
.getDomains();
358 for(vector
<BindDomainInfo
>::iterator i
=domains
.begin(); i
!=domains
.end(); ++i
) {
359 if(stat(i
->filename
.c_str(), &st
) == 0) {
360 i
->d_dev
= st
.st_dev
;
361 i
->d_ino
= st
.st_ino
;
365 sort(domains
.begin(), domains
.end()); // put stuff in inode order
367 int numdomains
=domains
.size();
368 int tick
=numdomains
/100;
370 for(vector
<BindDomainInfo
>::const_iterator i
=domains
.begin();
374 if(i
->type
!="master" && i
->type
!="slave") {
375 cerr
<<" Warning! Skipping '"<<i
->type
<<"' zone '"<<i
->name
<<"'"<<endl
;
379 startNewTransaction();
381 emitDomain(i
->name
, &(i
->masters
));
383 ZoneParserTNG
zpt(i
->filename
, i
->name
, BP
.getDirectory());
384 DNSResourceRecord rr
;
387 while(zpt
.get(rr
, &comment
)) {
388 if(filterDupSOA
&& seenSOA
&& rr
.qtype
.getCode() == QType::SOA
)
390 if(rr
.qtype
.getCode() == QType::SOA
)
393 emitRecord(i
->name
, rr
.qname
, rr
.qtype
.getName(), rr
.content
, rr
.ttl
, comment
);
397 catch(std::exception
&ae
) {
398 if(!::arg().mustDo("on-error-resume-next"))
401 cerr
<<endl
<<ae
.what()<<endl
;
403 catch(PDNSException
&ae
) {
404 if(!::arg().mustDo("on-error-resume-next"))
407 cerr
<<ae
.reason
<<endl
;
411 if(!tick
|| !((count
++)%tick
))
412 cerr
<<"\r"<<count
*100/numdomains
<<"% done ("<<i
->filename
<<")\033\133\113";
414 cerr
<<"\r100% done\033\133\113"<<endl
;
418 if(!::arg()["zone-name"].empty())
419 zonename
= DNSName(::arg()["zone-name"]);
421 ZoneParserTNG
zpt(zonefile
, zonename
);
422 DNSResourceRecord rr
;
423 startNewTransaction();
426 bool haveEmittedZone
= false;
427 while(zpt
.get(rr
, &comment
)) {
428 if(filterDupSOA
&& seenSOA
&& rr
.qtype
.getCode() == QType::SOA
)
430 if(rr
.qtype
.getCode() == QType::SOA
)
432 if(!haveEmittedZone
) {
433 if(!zpt
.getZoneName().empty()){
434 emitDomain(zpt
.getZoneName());
435 haveEmittedZone
= true;
437 // We have no zonename yet, don't emit
442 emitRecord(zpt
.getZoneName(), rr
.qname
, rr
.qtype
.getName(), rr
.content
, rr
.ttl
, comment
);
446 cerr
<<num_domainsdone
<<" domains were fully parsed, containing "<<g_numRecords
<<" records\n";
448 if(::arg().mustDo("transactions") && g_intransaction
) {
450 cout
<<"COMMIT WORK;"<<endl
;
452 cout
<<"COMMIT;"<<endl
;
456 catch(PDNSException
&ae
) {
457 cerr
<<"\nFatal error: "<<ae
.reason
<<endl
;
460 catch(std::exception
&e
) {
461 cerr
<<"\ndied because of STL error: "<<e
.what()<<endl
;
465 cerr
<<"\ndied because of unknown exception"<<endl
;