]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/zone2sql.cc
auth 4.1 build: Switch to devtoolset 7 for el6
[thirdparty/pdns.git] / pdns / zone2sql.cc
CommitLineData
12c86877 1/*
6edbf68a
PL
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
4 *
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.
8 *
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.
12 *
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.
17 *
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.
21 */
12c86877 22
36d5b5eb 23/* accepts a named.conf or a zone as parameter and outputs heaps of sql */
12c86877 24
870a0fe4
AT
25#ifdef HAVE_CONFIG_H
26#include "config.h"
27#endif
a6d7640a 28#include <unistd.h>
12c86877
BH
29#include <string>
30#include <map>
2dfd6b65 31
12c86877
BH
32#include <iostream>
33#include <stdio.h>
7d365ab7 34#include "json11.hpp"
10f4eea8 35#include "namespaces.hh"
12c86877
BH
36#include "dns.hh"
37#include "arguments.hh"
7c0cb150 38#include "bindparserclasses.hh"
12c86877
BH
39#include "statbag.hh"
40#include "misc.hh"
a62c0fcf 41#include "dnspacket.hh"
874300a8 42#include "zoneparser-tng.hh"
57a2c446
BH
43#include "dnsrecords.hh"
44#include <boost/algorithm/string.hpp>
a19dacb9
BH
45#include <sys/types.h>
46#include <sys/stat.h>
47#include <unistd.h>
fa8fd4d2 48
12c86877 49
de43ec0f 50
12c86877
BH
51StatBag S;
52
d10c5985 53enum dbmode_t {MYSQL, GORACLE, POSTGRES, SQLITE, MYDNS, ORACLE};
36d5b5eb
BH
54static dbmode_t g_mode;
55static bool g_intransaction;
56static int g_numRecords;
57
9aa9781a 58
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. */
64
65static string stripDotContent(const string& content)
66{
67 if(boost::ends_with(content, " .") || content==".")
68 return content;
69 return stripDot(content);
70}
71
36d5b5eb 72static string sqlstr(const string &name)
12c86877 73{
d10c5985 74 if(g_mode == SQLITE || g_mode==GORACLE || g_mode==ORACLE)
36d5b5eb
BH
75 return "'"+boost::replace_all_copy(name, "'", "''")+"'";
76
77 string a;
12c86877 78
36d5b5eb 79 for(string::const_iterator i=name.begin();i!=name.end();++i) {
ce7f841f
BH
80 if(*i=='\'' || *i=='\\'){
81 a+='\\';
82 a+=*i;
83 }
84 else
85 a+=*i;
36d5b5eb 86 }
7168d218
PD
87 if(g_mode == POSTGRES)
88 return "E'"+a+"'";
89 else
90 return "'"+a+"'";
12c86877
BH
91}
92
36d5b5eb 93static void startNewTransaction()
12c86877 94{
36d5b5eb
BH
95 if(!::arg().mustDo("transactions"))
96 return;
97
98 if(g_intransaction) {
c87f6c06 99 if(g_mode==POSTGRES) {
36d5b5eb
BH
100 cout<<"COMMIT WORK;"<<endl;
101 }
267af0fb 102 else if(g_mode == MYSQL || g_mode == SQLITE || g_mode == MYDNS) {
36d5b5eb 103 cout<<"COMMIT;"<<endl;
12c86877 104 }
12c86877 105 }
36d5b5eb 106 g_intransaction=1;
12c86877 107
267af0fb 108 if(g_mode == MYSQL || g_mode == MYDNS)
36d5b5eb 109 cout<<"BEGIN;"<<endl;
d10c5985 110 else if (g_mode!=GORACLE && g_mode!=ORACLE)
36d5b5eb
BH
111 cout<<"BEGIN TRANSACTION;"<<endl;
112}
113
67c4425d
PL
114static void emitDomain(const DNSName& domain, const vector<string> *masters = 0) {
115 string iDomain = domain.toStringRootDot();
c0d55048
RA
116 if(!::arg().mustDo("slave")) {
117 if(g_mode==POSTGRES || g_mode==MYSQL || g_mode==SQLITE) {
67c4425d 118 cout<<"insert into domains (name,type) values ("<<toLower(sqlstr(iDomain))<<",'NATIVE');"<<endl;
c0d55048 119 }
d10c5985 120 else if(g_mode==GORACLE) {
67c4425d 121 cout<<"insert into domains (id,name,type) values (domains_id_sequence.nextval,"<<toLower(sqlstr(iDomain))<<",'NATIVE');"<<endl;
c0d55048 122 }
d10c5985 123 else if(g_mode==ORACLE) {
67c4425d 124 cout<<"INSERT INTO Zones (id, name, type) VALUES (zones_id_seq.nextval, "<<sqlstr(toLower(iDomain))<<", 'NATIVE');"<<endl;
d10c5985 125 }
c0d55048
RA
126 }
127 else
128 {
129
130 if(g_mode==POSTGRES || g_mode==MYSQL || g_mode==SQLITE) {
131 string mstrs;
132 if (masters != 0 && ! masters->empty()) {
ef7cd021 133 for(const string& mstr : *masters) {
c0d55048
RA
134 mstrs.append(mstr);
135 mstrs.append(1, ' ');
67c4425d 136 }
c0d55048
RA
137 }
138 if (mstrs.empty())
67c4425d 139 cout<<"insert into domains (name,type) values ("<<sqlstr(iDomain)<<",'NATIVE');"<<endl;
c0d55048 140 else
67c4425d 141 cout<<"insert into domains (name,type,master) values ("<<sqlstr(iDomain)<<",'SLAVE'"<<", '"<<mstrs<<"');"<<endl;
c0d55048 142 }
d10c5985 143 else if (g_mode == GORACLE || g_mode==ORACLE) {
c0d55048
RA
144 cerr<<"Slave import mode not supported with oracle."<<endl;
145 }
146 }
147}
148
2d709503 149bool g_doJSONComments;
67c4425d 150static void emitRecord(const DNSName& zoneName, const DNSName &DNSqname, const string &qtype, const string &ocontent, int ttl, const string& comment="")
36d5b5eb 151{
67c4425d
PL
152 string qname = DNSqname.toStringRootDot();
153 string zname = zoneName.toStringRootDot();
b9bafae0 154 int prio=0;
a2e9e5a5 155 int disabled=0;
156 string recordcomment;
157
2d709503 158 if(g_doJSONComments & !comment.empty()) {
a2e9e5a5 159 string::size_type pos = comment.find("json={");
160 if(pos!=string::npos) {
161 string json = comment.substr(pos+5);
7d365ab7
CH
162 string err;
163 auto document = json11::Json::parse(json, err);
164 if(document.is_null())
165 throw runtime_error("Could not parse JSON '"+json+"': " + err);
a2e9e5a5 166
7d365ab7
CH
167 disabled=document["disabled"].bool_value();
168 recordcomment=document["comment"].string_value();
a2e9e5a5 169 }
170 }
a5a1f447 171
36d5b5eb
BH
172 g_numRecords++;
173 string content(ocontent);
88a7d2c1
PD
174
175 if(qtype == "NSEC" || qtype == "NSEC3")
176 return; // NSECs do not go in the database
177
d10c5985 178 if((qtype == "MX" || qtype == "SRV") && g_mode!=ORACLE) {
335da0ba 179 prio=pdns_stou(content);
57a2c446
BH
180
181 string::size_type pos = content.find_first_not_of("0123456789");
182 if(pos != string::npos)
de43ec0f 183 boost::erase_head(content, pos);
57a2c446
BH
184 trim_left(content);
185 }
186
27a58682 187 bool auth = true;
67c4425d 188 if(qtype == "NS" && !pdns_iequals(qname, zname)) {
27a58682
BH
189 auth=false;
190 }
191
36d5b5eb 192 if(g_mode==MYSQL || g_mode==SQLITE) {
c4e1f2c1
PL
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<<
67c4425d 197 " from domains where name="<<toLower(sqlstr(zname))<<";\n";
8c228c32 198
c4e1f2c1 199 if(!recordcomment.empty()) {
67c4425d 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";
27a58682 201 }
12c86877 202 }
36d5b5eb 203 else if(g_mode==POSTGRES) {
c4e1f2c1
PL
204 cout<<"insert into records (domain_id, name, ordername, auth, type,content,ttl,prio,disabled) select id ,"<<
205 sqlstr(toLower(qname))<<", "<<
897e94f3 206 sqlstr(DNSName(qname).makeRelative(DNSName(zname)).makeLowerCase().labelReverse().toString(" ", false))<<", '"<< (auth ? 't' : 'f') <<"', "<<
c4e1f2c1
PL
207 sqlstr(qtype)<<", "<<
208 sqlstr(stripDotContent(content))<<", "<<ttl<<", "<<prio<<", '"<<(disabled ? 't': 'f') <<
67c4425d 209 "' from domains where name="<<toLower(sqlstr(zname))<<";\n";
12c86877 210 }
d10c5985 211 else if(g_mode==GORACLE) {
cea26350 212 cout<<"insert into Records (id, domain_id, name, type, content, ttl, prio, disabled) select RECORDS_ID_SEQUENCE.nextval,id ,"<<
0fae5c1e 213 sqlstr(toLower(qname))<<", "<<
12c86877 214 sqlstr(qtype)<<", "<<
86446d95 215 sqlstr(stripDotContent(content))<<", "<<ttl<<", "<<prio<<", "<<disabled<<
67c4425d 216 " from Domains where name="<<toLower(sqlstr(zname))<<";\n";
12c86877 217 }
d10c5985 218 else if(g_mode==ORACLE) {
e4fec670 219 cout<<"INSERT INTO Records (id, zone_id, fqdn, ttl, type, content) SELECT records_id_seq.nextval, id, "<<
0fae5c1e 220 sqlstr(toLower(qname))<<", "<<
d10c5985 221 ttl<<", "<<sqlstr(qtype)<<", "<<
e4fec670 222 sqlstr(stripDotContent(content))<<
67c4425d 223 " FROM Zones WHERE name="<<toLower(sqlstr(zname))<<";"<<endl;
d10c5985 224 }
267af0fb 225 else if (g_mode == MYDNS) {
67c4425d 226 string zoneNameDot = zname + ".";
267af0fb
PD
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")
229 {
230 if ((qtype == "MX" || qtype == "NS" || qtype == "SRV" || qtype == "CNAME") && content[content.size()-1] != '.')
231 content.append(".");
232 cout<<"INSERT INTO rr(zone, name, type, data, aux, ttl) VALUES("<<
233 "(SELECT id FROM soa WHERE origin = "<<
234 sqlstr(toLower(zoneNameDot))<<"), "<<
0fae5c1e 235 sqlstr(toLower(DNSqname.toString()))<<", "<<
267af0fb
PD
236 sqlstr(qtype)<<", "<<sqlstr(content)<<", "<<prio<<", "<<ttl<<");\n";
237 }
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);
242
243 cout<<"INSERT INTO soa(origin, ns, mbox, serial, refresh, retry, expire, minimum, ttl) VALUES("<<
335da0ba
AT
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";
267af0fb
PD
246 }
247 else
248 {
249 cerr<<"Record type "<<qtype<<" is not supported."<<endl;
250 }
251 }
12c86877
BH
252}
253
254
255/* 2 modes of operation, either --named or --zone (the latter needs $ORIGIN)
d10c5985 256 2 further modes: --mysql, --goracle or --oracle
12c86877
BH
257*/
258
259ArgvMap &arg()
260{
261 static ArgvMap theArg;
262 return theArg;
263}
264
265
266int main(int argc, char **argv)
5761d098 267try
12c86877 268{
57a2c446 269 reportAllTypes();
10f4eea8 270 std::ios_base::sync_with_stdio(false);
226bcf89 271
36d5b5eb
BH
272 ::arg().setSwitch("gpgsql","Output in format suitable for default gpgsqlbackend")="no";
273 ::arg().setSwitch("gmysql","Output in format suitable for default gmysqlbackend")="no";
267af0fb 274 ::arg().setSwitch("mydns","Output in format suitable for default mydnsbackend")="no";
a7aac01b 275 ::arg().setSwitch("goracle","Output in format suitable for the goraclebackend")="no";
d10c5985 276 ::arg().setSwitch("oracle","Output in format suitable for the oraclebackend")="no";
36d5b5eb 277 ::arg().setSwitch("gsqlite","Output in format suitable for default gsqlitebackend")="no";
6051fa72 278 ::arg().setSwitch("verbose","Verbose comments on operation")="no";
c0d55048 279 ::arg().setSwitch("slave","Keep BIND slaves as slaves. Only works with named-conf.")="no";
2d709503 280 ::arg().setSwitch("json-comments","Parse json={} field for disabled & comments")="no";
6051fa72
BH
281 ::arg().setSwitch("transactions","If target SQL supports it, use transactions")="no";
282 ::arg().setSwitch("on-error-resume-next","Continue after errors")="no";
8439c4e3 283 ::arg().setSwitch("filter-duplicate-soa","Filter second SOA in zone")="yes";
36d5b5eb 284 ::arg().set("zone","Zonefile to parse")="";
6051fa72
BH
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")="";
36d5b5eb 287
6051fa72
BH
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";
292
293 ::arg().setCmd("help","Provide a helpful message");
cd2ef423 294 ::arg().setCmd("version","Print the version");
12c86877
BH
295
296 S.declare("logmessages");
297
298 string namedfile="";
299 string zonefile="";
300
6051fa72 301 ::arg().parse(argc, argv);
cd2ef423
PL
302
303 if(::arg().mustDo("version")) {
304 cerr<<"zone2sql "<<VERSION<<endl;
305 exit(0);
306 }
12c86877 307
ff5ba4f9
WA
308 if(::arg().mustDo("help")) {
309 cout<<"syntax:"<<endl<<endl;
310 cout<<::arg().helpstring()<<endl;
311 exit(0);
312 }
313
314 if(argc<2) {
12c86877 315 cerr<<"syntax:"<<endl<<endl;
6051fa72 316 cerr<<::arg().helpstring()<<endl;
12c86877
BH
317 exit(1);
318 }
319
8439c4e3 320 bool filterDupSOA = ::arg().mustDo("filter-duplicate-soa");
8439c4e3 321
2d709503 322 g_doJSONComments=::arg().mustDo("json-comments");
323
36d5b5eb
BH
324 if(::arg().mustDo("gmysql"))
325 g_mode=MYSQL;
326 else if(::arg().mustDo("gpgsql"))
327 g_mode=POSTGRES;
328 else if(::arg().mustDo("gsqlite"))
329 g_mode=SQLITE;
d10c5985
KM
330 else if(::arg().mustDo("goracle"))
331 g_mode=GORACLE;
332 else if(::arg().mustDo("oracle"))
36d5b5eb 333 g_mode=ORACLE;
267af0fb
PD
334 else if(::arg().mustDo("mydns"))
335 g_mode=MYDNS;
36d5b5eb
BH
336 else {
337 cerr<<"Unknown SQL mode!\n\n";
338 cerr<<"syntax:"<<endl<<endl;
339 cerr<<::arg().helpstring()<<endl;
340 exit(1);
341 }
136efa2f 342
d10c5985
KM
343 if((g_mode==GORACLE || g_mode==ORACLE) && !::arg().mustDo("transactions"))
344 cout<<"set autocommit on;"<<endl;
345
6051fa72
BH
346 namedfile=::arg()["named-conf"];
347 zonefile=::arg()["zone"];
12c86877 348
a19dacb9 349 int count=0, num_domainsdone=0;
12c86877
BH
350
351 if(zonefile.empty()) {
352 BindParser BP;
6051fa72 353 BP.setVerbose(::arg().mustDo("verbose"));
12c86877
BH
354 BP.parse(namedfile.empty() ? "./named.conf" : namedfile);
355
a19dacb9
BH
356 vector<BindDomainInfo> domains=BP.getDomains();
357 struct stat st;
358 for(vector<BindDomainInfo>::iterator i=domains.begin(); i!=domains.end(); ++i) {
4957a608
BH
359 if(stat(i->filename.c_str(), &st) == 0) {
360 i->d_dev = st.st_dev;
361 i->d_ino = st.st_ino;
362 }
a19dacb9
BH
363 }
364
365 sort(domains.begin(), domains.end()); // put stuff in inode order
12c86877
BH
366
367 int numdomains=domains.size();
368 int tick=numdomains/100;
12c86877
BH
369
370 for(vector<BindDomainInfo>::const_iterator i=domains.begin();
4957a608
BH
371 i!=domains.end();
372 ++i)
373 {
36d5b5eb 374 if(i->type!="master" && i->type!="slave") {
e85a8718 375 cerr<<" Warning! Skipping '"<<i->type<<"' zone '"<<i->name<<"'"<<endl;
36d5b5eb 376 continue;
bc0fa09e 377 }
4957a608 378 try {
36d5b5eb
BH
379 startNewTransaction();
380
67c4425d 381 emitDomain(i->name, &(i->masters));
36d5b5eb 382
4957a608
BH
383 ZoneParserTNG zpt(i->filename, i->name, BP.getDirectory());
384 DNSResourceRecord rr;
0fae5c1e
RA
385 bool seenSOA=false;
386 string comment;
2d709503 387 while(zpt.get(rr, &comment)) {
0fae5c1e
RA
388 if(filterDupSOA && seenSOA && rr.qtype.getCode() == QType::SOA)
389 continue;
390 if(rr.qtype.getCode() == QType::SOA)
391 seenSOA=true;
392
67c4425d 393 emitRecord(i->name, rr.qname, rr.qtype.getName(), rr.content, rr.ttl, comment);
0fae5c1e 394 }
4957a608
BH
395 num_domainsdone++;
396 }
397 catch(std::exception &ae) {
398 if(!::arg().mustDo("on-error-resume-next"))
399 throw;
400 else
401 cerr<<endl<<ae.what()<<endl;
402 }
3f81d239 403 catch(PDNSException &ae) {
4957a608
BH
404 if(!::arg().mustDo("on-error-resume-next"))
405 throw;
406 else
407 cerr<<ae.reason<<endl;
408 }
409
36d5b5eb 410
4957a608
BH
411 if(!tick || !((count++)%tick))
412 cerr<<"\r"<<count*100/numdomains<<"% done ("<<i->filename<<")\033\133\113";
413 }
12c86877
BH
414 cerr<<"\r100% done\033\133\113"<<endl;
415 }
416 else {
d16a2ccf
PL
417 DNSName zonename;
418 if(!::arg()["zone-name"].empty())
419 zonename = DNSName(::arg()["zone-name"]);
420
c0d55048 421 ZoneParserTNG zpt(zonefile, zonename);
874300a8 422 DNSResourceRecord rr;
36d5b5eb 423 startNewTransaction();
a2e9e5a5 424 string comment;
8439c4e3 425 bool seenSOA=false;
d16a2ccf 426 bool haveEmittedZone = false;
a2e9e5a5 427 while(zpt.get(rr, &comment)) {
8439c4e3 428 if(filterDupSOA && seenSOA && rr.qtype.getCode() == QType::SOA)
429 continue;
430 if(rr.qtype.getCode() == QType::SOA)
431 seenSOA=true;
7baac7c6
PL
432 if(!haveEmittedZone) {
433 if(!zpt.getZoneName().empty()){
67c4425d 434 emitDomain(zpt.getZoneName());
7baac7c6
PL
435 haveEmittedZone = true;
436 } else {
437 // We have no zonename yet, don't emit
438 continue;
439 }
d16a2ccf 440 }
8439c4e3 441
67c4425d 442 emitRecord(zpt.getZoneName(), rr.qname, rr.qtype.getName(), rr.content, rr.ttl, comment);
a2e9e5a5 443 }
a19dacb9 444 num_domainsdone=1;
12c86877 445 }
36d5b5eb 446 cerr<<num_domainsdone<<" domains were fully parsed, containing "<<g_numRecords<<" records\n";
12c86877 447
2241f755
BH
448 if(::arg().mustDo("transactions") && g_intransaction) {
449 if(g_mode != SQLITE)
450 cout<<"COMMIT WORK;"<<endl;
451 else
452 cout<<"COMMIT;"<<endl;
453 }
5761d098
CH
454 return 0;
455}
456catch(PDNSException &ae) {
457 cerr<<"\nFatal error: "<<ae.reason<<endl;
458 return 1;
459}
460catch(std::exception &e) {
461 cerr<<"\ndied because of STL error: "<<e.what()<<endl;
462 return 1;
463}
464catch(...) {
465 cerr<<"\ndied because of unknown exception"<<endl;
12c86877 466 return 1;
12c86877 467}