]>
git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/backends/bind/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 */
30 #include "namespaces.hh"
33 #include "arguments.hh"
34 #include "bindparserclasses.hh"
37 #include "dnspacket.hh"
38 #include "zoneparser-tng.hh"
39 #include "dnsrecords.hh"
40 #include <boost/algorithm/string.hpp>
41 #include <sys/types.h>
44 #include <boost/foreach.hpp>
48 static bool g_doDNSSEC
;
50 enum dbmode_t
{MYSQL
, ORACLE
, POSTGRES
, SQLITE
, MYDNS
};
51 static dbmode_t g_mode
;
52 static bool g_intransaction
;
53 static int g_numRecords
;
56 /* this is an official wart. We don't terminate domains on a . in PowerDNS,
57 which is fine as it goes, except for encoding the root, it would end up as '',
58 which leads to ambiguities in the content field. Therefore, if we encounter
59 the root as a . in a BIND zone, we leave it as a ., and don't replace it by
60 an empty string. Back in 1999 we made the wrong choice. */
62 static string
stripDotContent(const string
& content
)
64 if(boost::ends_with(content
, " .") || content
==".")
66 return stripDot(content
);
69 static string
sqlstr(const string
&name
)
72 return "'"+boost::replace_all_copy(name
, "'", "''")+"'";
76 for(string::const_iterator i
=name
.begin();i
!=name
.end();++i
) {
77 if(*i
=='\'' || *i
=='\\'){
84 if(g_mode
== POSTGRES
)
90 static void startNewTransaction()
92 if(!::arg().mustDo("transactions"))
96 if(g_mode
==POSTGRES
|| g_mode
==ORACLE
) {
97 cout
<<"COMMIT WORK;"<<endl
;
99 else if(g_mode
== MYSQL
|| g_mode
== SQLITE
|| g_mode
== MYDNS
) {
100 cout
<<"COMMIT;"<<endl
;
105 if(g_mode
== MYSQL
|| g_mode
== MYDNS
)
106 cout
<<"BEGIN;"<<endl
;
108 cout
<<"BEGIN TRANSACTION;"<<endl
;
111 static void emitDomain(const string
& domain
, const vector
<string
> *masters
= 0) {
112 if(!::arg().mustDo("slave")) {
113 if(g_mode
==POSTGRES
|| g_mode
==MYSQL
|| g_mode
==SQLITE
) {
114 cout
<<"insert into domains (name,type) values ("<<toLower(sqlstr(stripDot(domain
)))<<",'NATIVE');"<<endl
;
116 else if(g_mode
==ORACLE
) {
117 cout
<<"insert into domains (id,name,type) values (domains_id_sequence.nextval,"<<toLower(sqlstr(domain
))<<",'NATIVE');"<<endl
;
123 if(g_mode
==POSTGRES
|| g_mode
==MYSQL
|| g_mode
==SQLITE
) {
125 if (masters
!= 0 && ! masters
->empty()) {
126 BOOST_FOREACH(const string
& mstr
, *masters
) {
128 mstrs
.append(1, ' ');
132 cout
<<"insert into domains (name,type) values ("<<sqlstr(domain
)<<",'NATIVE');"<<endl
;
134 cout
<<"insert into domains (name,type,master) values ("<<sqlstr(domain
)<<",'SLAVE'"<<", '"<<mstrs
<<"');"<<endl
;
136 else if (g_mode
== ORACLE
) {
137 cerr
<<"Slave import mode not supported with oracle."<<endl
;
142 static void emitRecord(const string
& zoneName
, const string
&qname
, const string
&qtype
, const string
&ocontent
, int ttl
, int prio
)
145 string
content(ocontent
);
147 if(qtype
== "NSEC" || qtype
== "NSEC3")
148 return; // NSECs do not go in the database
150 if(qtype
== "MX" || qtype
== "SRV") {
151 prio
=atoi(content
.c_str());
153 string::size_type pos
= content
.find_first_not_of("0123456789");
154 if(pos
!= string::npos
)
155 boost::erase_head(content
, pos
);
160 if(qtype
== "NS" && !pdns_iequals(stripDot(qname
), zoneName
)) {
164 if(g_mode
==MYSQL
|| g_mode
==SQLITE
) {
166 cout
<<"insert into records (domain_id, name,type,content,ttl,prio) select id ,"<<
167 sqlstr(toLower(stripDot(qname
)))<<", "<<
168 sqlstr(qtype
)<<", "<<
169 sqlstr(stripDotContent(content
))<<", "<<ttl
<<", "<<prio
<<
170 " from domains where name="<<toLower(sqlstr(zoneName
))<<";\n";
173 cout
<<"insert into records (domain_id, name, ordername, auth, type,content,ttl,prio) select id ,"<<
174 sqlstr(toLower(stripDot(qname
)))<<", "<<
175 sqlstr(toLower(labelReverse(makeRelative(stripDot(qname
), zoneName
))))<<", "<<auth
<<", "<<
176 sqlstr(qtype
)<<", "<<
177 sqlstr(stripDotContent(content
))<<", "<<ttl
<<", "<<prio
<<
178 " from domains where name="<<toLower(sqlstr(zoneName
))<<";\n";
181 else if(g_mode
==POSTGRES
) {
183 cout
<<"insert into records (domain_id, name,type,content,ttl,prio) select id ,"<<
184 sqlstr(toLower(stripDot(qname
)))<<", "<<
185 sqlstr(qtype
)<<", "<<
186 sqlstr(stripDotContent(content
))<<", "<<ttl
<<", "<<prio
<<
187 " from domains where name="<<toLower(sqlstr(zoneName
))<<";\n";
190 cout
<<"insert into records (domain_id, name, ordername, auth, type,content,ttl,prio) select id ,"<<
191 sqlstr(toLower(stripDot(qname
)))<<", "<<
192 sqlstr(toLower(labelReverse(makeRelative(stripDot(qname
), zoneName
))))<<", '"<< (auth
? 't' : 'f') <<"', "<<
193 sqlstr(qtype
)<<", "<<
194 sqlstr(stripDotContent(content
))<<", "<<ttl
<<", "<<prio
<<
195 " from domains where name="<<toLower(sqlstr(zoneName
))<<";\n";
198 else if(g_mode
==ORACLE
) {
199 cout
<<"insert into Records (id,ZoneId, name,type,content,TimeToLive,Priority) select RECORDS_ID_SEQUENCE.nextval,id ,"<<
200 sqlstr(toLower(stripDot(qname
)))<<", "<<
201 sqlstr(qtype
)<<", "<<
202 sqlstr(stripDotContent(content
))<<", "<<ttl
<<", "<<prio
<<
203 " from Domains where name="<<toLower(sqlstr(zoneName
))<<";\n";
205 else if (g_mode
== MYDNS
) {
206 string zoneNameDot
= zoneName
+ ".";
207 if (qtype
== "A" || qtype
== "AAAA" || qtype
== "CNAME" || qtype
== "HINFO" || qtype
== "MX" || qtype
== "NAPTR" ||
208 qtype
== "NS" || qtype
== "PTR" || qtype
== "RP" || qtype
== "SRV" || qtype
== "TXT")
210 if ((qtype
== "MX" || qtype
== "NS" || qtype
== "SRV" || qtype
== "CNAME") && content
[content
.size()-1] != '.')
212 cout
<<"INSERT INTO rr(zone, name, type, data, aux, ttl) VALUES("<<
213 "(SELECT id FROM soa WHERE origin = "<<
214 sqlstr(toLower(zoneNameDot
))<<"), "<<
215 sqlstr(toLower(qname
))<<", "<<
216 sqlstr(qtype
)<<", "<<sqlstr(content
)<<", "<<prio
<<", "<<ttl
<<");\n";
218 else if (qtype
== "SOA") {
219 //pdns CONTENT = ns1.wtest.com. ahu.example.com. 2005092501 28800 7200 604800 86400
220 vector
<string
> parts
;
221 stringtok(parts
, content
);
223 cout
<<"INSERT INTO soa(origin, ns, mbox, serial, refresh, retry, expire, minimum, ttl) VALUES("<<
224 sqlstr(toLower(zoneNameDot
))<<", "<<sqlstr(parts
[0])<<", "<<sqlstr(parts
[1])<<", "<<atoi(parts
[2].c_str())<<", "<<
225 atoi(parts
[3].c_str())<<", "<<atoi(parts
[4].c_str())<<", "<<atoi(parts
[5].c_str())<<", "<<atoi(parts
[6].c_str())<<", "<<ttl
<<");\n";
229 cerr
<<"Record type "<<qtype
<<" is not supported."<<endl
;
235 /* 2 modes of operation, either --named or --zone (the latter needs $ORIGIN)
236 2 further modes: --mysql or --oracle
241 static ArgvMap theArg
;
246 int main(int argc
, char **argv
)
252 std::ios_base::sync_with_stdio(false);
255 ::arg().setSwitch("gpgsql","Output in format suitable for default gpgsqlbackend")="no";
256 ::arg().setSwitch("gmysql","Output in format suitable for default gmysqlbackend")="no";
257 ::arg().setSwitch("mydns","Output in format suitable for default mydnsbackend")="no";
258 ::arg().setSwitch("oracle","Output in format suitable for the oraclebackend")="no";
259 ::arg().setSwitch("gsqlite","Output in format suitable for default gsqlitebackend")="no";
260 ::arg().setSwitch("verbose","Verbose comments on operation")="no";
261 ::arg().setSwitch("dnssec","Add DNSSEC related data")="no";
262 ::arg().setSwitch("slave","Keep BIND slaves as slaves. Only works with named-conf.")="no";
263 ::arg().setSwitch("transactions","If target SQL supports it, use transactions")="no";
264 ::arg().setSwitch("on-error-resume-next","Continue after errors")="no";
265 ::arg().set("zone","Zonefile to parse")="";
266 ::arg().set("zone-name","Specify an $ORIGIN in case it is not present")="";
267 ::arg().set("named-conf","Bind 8/9 named.conf to parse")="";
269 ::arg().set("soa-minimum-ttl","Do not change")="0";
270 ::arg().set("soa-refresh-default","Do not change")="0";
271 ::arg().set("soa-retry-default","Do not change")="0";
272 ::arg().set("soa-expire-default","Do not change")="0";
274 ::arg().setCmd("help","Provide a helpful message");
276 S
.declare("logmessages");
281 ::arg().parse(argc
, argv
);
283 if(argc
<2 || ::arg().mustDo("help")) {
284 cerr
<<"syntax:"<<endl
<<endl
;
285 cerr
<<::arg().helpstring()<<endl
;
289 if(::arg().mustDo("gmysql"))
291 else if(::arg().mustDo("gpgsql"))
293 else if(::arg().mustDo("gsqlite"))
295 else if(::arg().mustDo("oracle")) {
297 if(!::arg().mustDo("transactions"))
298 cout
<<"set autocommit on;"<<endl
;
300 else if(::arg().mustDo("mydns"))
303 cerr
<<"Unknown SQL mode!\n\n";
304 cerr
<<"syntax:"<<endl
<<endl
;
305 cerr
<<::arg().helpstring()<<endl
;
309 g_doDNSSEC
=::arg().mustDo("dnssec");
311 namedfile
=::arg()["named-conf"];
312 zonefile
=::arg()["zone"];
314 int count
=0, num_domainsdone
=0;
316 if(zonefile
.empty()) {
318 BP
.setVerbose(::arg().mustDo("verbose"));
319 BP
.parse(namedfile
.empty() ? "./named.conf" : namedfile
);
321 vector
<BindDomainInfo
> domains
=BP
.getDomains();
323 for(vector
<BindDomainInfo
>::iterator i
=domains
.begin(); i
!=domains
.end(); ++i
) {
324 if(stat(i
->filename
.c_str(), &st
) == 0) {
325 i
->d_dev
= st
.st_dev
;
326 i
->d_ino
= st
.st_ino
;
330 sort(domains
.begin(), domains
.end()); // put stuff in inode order
332 int numdomains
=domains
.size();
333 int tick
=numdomains
/100;
335 for(vector
<BindDomainInfo
>::const_iterator i
=domains
.begin();
339 if(i
->type
!="master" && i
->type
!="slave") {
340 cerr
<<" Warning! Skipping '"<<i
->type
<<"' zone '"<<i
->name
<<"'"<<endl
;
344 startNewTransaction();
346 emitDomain(i
->name
, &(i
->masters
));
348 ZoneParserTNG
zpt(i
->filename
, i
->name
, BP
.getDirectory());
349 DNSResourceRecord rr
;
351 emitRecord(i
->name
, rr
.qname
, rr
.qtype
.getName(), rr
.content
, rr
.ttl
, rr
.priority
);
354 catch(std::exception
&ae
) {
355 if(!::arg().mustDo("on-error-resume-next"))
358 cerr
<<endl
<<ae
.what()<<endl
;
360 catch(PDNSException
&ae
) {
361 if(!::arg().mustDo("on-error-resume-next"))
364 cerr
<<ae
.reason
<<endl
;
368 if(!tick
|| !((count
++)%tick
))
369 cerr
<<"\r"<<count
*100/numdomains
<<"% done ("<<i
->filename
<<")\033\133\113";
371 cerr
<<"\r100% done\033\133\113"<<endl
;
374 string zonename
= ::arg()["zone-name"];
375 ZoneParserTNG
zpt(zonefile
, zonename
);
376 DNSResourceRecord rr
;
377 startNewTransaction();
378 emitDomain(zonename
);
380 emitRecord(zonename
, rr
.qname
, rr
.qtype
.getName(), rr
.content
, rr
.ttl
, rr
.priority
);
383 cerr
<<num_domainsdone
<<" domains were fully parsed, containing "<<g_numRecords
<<" records\n";
385 if(::arg().mustDo("transactions") && g_intransaction
) {
387 cout
<<"COMMIT WORK;"<<endl
;
389 cout
<<"COMMIT;"<<endl
;
393 catch(PDNSException
&ae
) {
394 cerr
<<"\nFatal error: "<<ae
.reason
<<endl
;
397 catch(std::exception
&e
) {
398 cerr
<<"\ndied because of STL error: "<<e
.what()<<endl
;
402 cerr
<<"\ndied because of unknown exception"<<endl
;