]>
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
, POSTGRES
, SQLITE
};
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
)
75 return "'"+boost::replace_all_copy(name
, "'", "''")+"'";
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
) {
103 cout
<<"COMMIT;"<<endl
;
106 g_intransaction
=true;
109 cout
<<"BEGIN;"<<endl
;
111 cout
<<"BEGIN TRANSACTION;"<<endl
;
114 static void emitDomain(const DNSName
& domain
, const vector
<ComboAddress
>* primaries
= nullptr)
116 string iDomain
= domain
.toStringRootDot();
117 if (!::arg().mustDo("secondary")) {
118 cout
<<"insert into domains (name,type) values ("<<toLower(sqlstr(iDomain
))<<",'NATIVE');"<<endl
;
123 if (primaries
!= nullptr && !primaries
->empty()) {
124 for (const auto& mstr
: *primaries
) {
125 mstrs
.append(mstr
.toStringWithPortExcept(53));
126 mstrs
.append(1, ' ');
130 cout
<<"insert into domains (name,type) values ("<<sqlstr(iDomain
)<<",'NATIVE');"<<endl
;
132 cout
<<"insert into domains (name,type,master) values ("<<sqlstr(iDomain
)<<",'SLAVE'"<<", '"<<mstrs
<<"');"<<endl
;
136 bool g_doJSONComments
;
137 static void emitRecord(const DNSName
& zoneName
, const DNSName
&DNSqname
, const string
&qtype
, const string
&ocontent
, int ttl
, const string
& comment
="")
139 string qname
= DNSqname
.toStringRootDot();
140 string zname
= zoneName
.toStringRootDot();
143 string recordcomment
;
145 if(g_doJSONComments
&& !comment
.empty()) {
146 string::size_type pos
= comment
.find("json={");
147 if(pos
!=string::npos
) {
148 string json
= comment
.substr(pos
+5);
150 auto document
= json11::Json::parse(json
, err
);
151 if(document
.is_null())
152 throw runtime_error("Could not parse JSON '"+json
+"': " + err
);
154 disabled
=document
["disabled"].bool_value();
155 recordcomment
=document
["comment"].string_value();
160 string
content(ocontent
);
162 if(qtype
== "NSEC" || qtype
== "NSEC3")
163 return; // NSECs do not go in the database
165 if((qtype
== "MX" || qtype
== "SRV")) {
166 pdns::checked_stoi_into(prio
, content
);
168 string::size_type pos
= content
.find_first_not_of("0123456789");
169 if(pos
!= string::npos
)
170 boost::erase_head(content
, pos
);
171 boost::trim_left(content
);
174 cout
<<"insert into records (domain_id, name, type,content,ttl,prio,disabled) select id ,"<<
175 sqlstr(toLower(qname
))<<", "<<
176 sqlstr(qtype
)<<", "<<
177 sqlstr(stripDotContent(content
))<<", "<<ttl
<<", "<<prio
<<", "<<(g_mode
==POSTGRES
? (disabled
? "'t'" : "'f'") : std::to_string(disabled
))<<
178 " from domains where name="<<toLower(sqlstr(zname
))<<";\n";
180 if(!recordcomment
.empty()) {
181 cout
<<"insert into comments (domain_id,name,type,modified_at, comment) select id, "<<toLower(sqlstr(stripDot(qname
)))<<", "<<sqlstr(qtype
)<<", "<<time(nullptr)<<", "<<sqlstr(recordcomment
)<<" from domains where name="<<toLower(sqlstr(zname
))<<";\n";
186 /* 2 modes of operation, either --named or --zone (the latter needs $ORIGIN)
187 1 further mode: --mysql
192 static ArgvMap theArg
;
197 int main(int argc
, char **argv
) // NOLINT(readability-function-cognitive-complexity) 13379 https://github.com/PowerDNS/pdns/issues/13379 Habbie: zone2sql.cc, bindbackend2.cc: reduce complexity
201 std::ios_base::sync_with_stdio(false);
203 ::arg().setSwitch("gpgsql","Output in format suitable for default gpgsqlbackend")="no";
204 ::arg().setSwitch("gmysql","Output in format suitable for default gmysqlbackend")="no";
205 ::arg().setSwitch("gsqlite","Output in format suitable for default gsqlitebackend")="no";
206 ::arg().setSwitch("verbose","Verbose comments on operation")="no";
207 ::arg().setSwitch("secondary", "Keep BIND secondaries as secondaries. Only works with named-conf.") = "no";
208 ::arg().setSwitch("json-comments","Parse json={} field for disabled & comments")="no";
209 ::arg().setSwitch("transactions","If target SQL supports it, use transactions")="no";
210 ::arg().setSwitch("on-error-resume-next","Continue after errors")="no";
211 ::arg().setSwitch("filter-duplicate-soa","Filter second SOA in zone")="yes";
212 ::arg().set("zone","Zonefile to parse")="";
213 ::arg().set("zone-name","Specify an $ORIGIN in case it is not present")="";
214 ::arg().set("named-conf","Bind 8/9 named.conf to parse")="";
216 ::arg().set("max-generate-steps", "Maximum number of $GENERATE steps when loading a zone from a file")="0";
217 ::arg().set("max-include-depth", "Maximum nested $INCLUDE depth when loading a zone from a file")="20";
219 ::arg().setCmd("help","Provide a helpful message");
220 ::arg().setCmd("version","Print the version");
222 S
.declare("logmessages");
227 ::arg().parse(argc
, argv
);
229 if(::arg().mustDo("version")) {
230 cerr
<<"zone2sql "<<VERSION
<<endl
;
234 if(::arg().mustDo("help")) {
235 cout
<<"syntax:"<<endl
<<endl
;
236 cout
<<::arg().helpstring()<<endl
;
241 cerr
<<"syntax:"<<endl
<<endl
;
242 cerr
<<::arg().helpstring()<<endl
;
246 bool filterDupSOA
= ::arg().mustDo("filter-duplicate-soa");
248 g_doJSONComments
=::arg().mustDo("json-comments");
250 if(::arg().mustDo("gmysql"))
252 else if(::arg().mustDo("gpgsql"))
254 else if(::arg().mustDo("gsqlite"))
257 cerr
<<"Unknown SQL mode!\n\n";
258 cerr
<<"syntax:"<<endl
<<endl
;
259 cerr
<<::arg().helpstring()<<endl
;
263 namedfile
=::arg()["named-conf"];
264 zonefile
=::arg()["zone"];
266 int count
=0, num_domainsdone
=0;
268 if(zonefile
.empty()) {
270 BP
.setVerbose(::arg().mustDo("verbose"));
271 BP
.parse(namedfile
.empty() ? "./named.conf" : namedfile
);
273 vector
<BindDomainInfo
> domains
=BP
.getDomains();
275 for(auto & domain
: domains
) {
276 if(stat(domain
.filename
.c_str(), &st
) == 0) {
277 domain
.d_dev
= st
.st_dev
;
278 domain
.d_ino
= st
.st_ino
;
282 sort(domains
.begin(), domains
.end()); // put stuff in inode order
284 int numdomains
=domains
.size();
285 int tick
=numdomains
/100;
287 for(const auto & domain
: domains
)
289 if (domain
.type
!= "primary" && domain
.type
!= "secondary" && !domain
.type
.empty() && domain
.type
!= "master" && domain
.type
!= "slave") {
290 cerr
<< " Warning! Skipping '" << domain
.type
<< "' zone '" << domain
.name
<< "'" << endl
;
294 startNewTransaction();
296 emitDomain(domain
.name
, &(domain
.primaries
));
298 ZoneParserTNG
zpt(domain
.filename
, domain
.name
, BP
.getDirectory());
299 zpt
.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
300 zpt
.setMaxIncludes(::arg().asNum("max-include-depth"));
301 DNSResourceRecord rr
;
304 while(zpt
.get(rr
, &comment
)) {
305 if(filterDupSOA
&& seenSOA
&& rr
.qtype
.getCode() == QType::SOA
)
307 if(rr
.qtype
.getCode() == QType::SOA
)
310 emitRecord(domain
.name
, rr
.qname
, rr
.qtype
.toString(), rr
.content
, rr
.ttl
, comment
);
314 catch(std::exception
&ae
) {
315 if(!::arg().mustDo("on-error-resume-next"))
318 cerr
<<endl
<<ae
.what()<<endl
;
320 catch(PDNSException
&ae
) {
321 if(!::arg().mustDo("on-error-resume-next"))
324 cerr
<<ae
.reason
<<endl
;
328 if(!tick
|| !((count
++)%tick
))
329 cerr
<<"\r"<<count
*100/numdomains
<<"% done ("<<domain
.filename
<<")\033\133\113";
331 cerr
<<"\r100% done\033\133\113"<<endl
;
335 if(!::arg()["zone-name"].empty())
336 zonename
= DNSName(::arg()["zone-name"]);
338 ZoneParserTNG
zpt(zonefile
, zonename
);
339 zpt
.setMaxGenerateSteps(::arg().asNum("max-generate-steps"));
340 DNSResourceRecord rr
;
341 startNewTransaction();
344 bool haveEmittedZone
= false;
345 while(zpt
.get(rr
, &comment
)) {
346 if(filterDupSOA
&& seenSOA
&& rr
.qtype
.getCode() == QType::SOA
)
348 if(rr
.qtype
.getCode() == QType::SOA
)
350 if(!haveEmittedZone
) {
351 if(!zpt
.getZoneName().empty()){
352 emitDomain(zpt
.getZoneName());
353 haveEmittedZone
= true;
355 // We have no zonename yet, don't emit
360 emitRecord(zpt
.getZoneName(), rr
.qname
, rr
.qtype
.toString(), rr
.content
, rr
.ttl
, comment
);
364 cerr
<<num_domainsdone
<<" domains were fully parsed, containing "<<g_numRecords
<<" records\n";
366 if(::arg().mustDo("transactions") && g_intransaction
) {
368 cout
<<"COMMIT WORK;"<<endl
;
370 cout
<<"COMMIT;"<<endl
;
374 catch(PDNSException
&ae
) {
375 cerr
<<"\nFatal error: "<<ae
.reason
<<endl
;
378 catch(std::exception
&e
) {
379 cerr
<<"\ndied because of STL error: "<<e
.what()<<endl
;
383 cerr
<<"\ndied because of unknown exception"<<endl
;