]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/backends/bind/zone2sql.cc
add OpenSSL exception to PowerDNS, Netherlabs, van Dijk and Hubert copyrights
[thirdparty/pdns.git] / pdns / backends / bind / zone2sql.cc
1 /*
2 PowerDNS Versatile Database Driven Nameserver
3 Copyright (C) 2002 - 2011 PowerDNS.COM BV
4
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
8
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.
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 St, Fifth Floor, Boston, MA 02110-1301 USA
21 */
22 /* accepts a named.conf or a zone as parameter and outputs heaps of sql */
23
24 #include <unistd.h>
25 #include <string>
26 #include <map>
27
28 #include <iostream>
29 #include <stdio.h>
30 #include "namespaces.hh"
31
32 #include "dns.hh"
33 #include "arguments.hh"
34 #include "bindparserclasses.hh"
35 #include "statbag.hh"
36 #include "misc.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>
42 #include <sys/stat.h>
43 #include <unistd.h>
44 #include <boost/foreach.hpp>
45
46
47 StatBag S;
48 static bool g_doDNSSEC;
49
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;
54
55
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. */
61
62 static string stripDotContent(const string& content)
63 {
64 if(boost::ends_with(content, " .") || content==".")
65 return content;
66 return stripDot(content);
67 }
68
69 static string sqlstr(const string &name)
70 {
71 if(g_mode == SQLITE)
72 return "'"+boost::replace_all_copy(name, "'", "''")+"'";
73
74 string a;
75
76 for(string::const_iterator i=name.begin();i!=name.end();++i) {
77 if(*i=='\'' || *i=='\\'){
78 a+='\\';
79 a+=*i;
80 }
81 else
82 a+=*i;
83 }
84 if(g_mode == POSTGRES)
85 return "E'"+a+"'";
86 else
87 return "'"+a+"'";
88 }
89
90 static void startNewTransaction()
91 {
92 if(!::arg().mustDo("transactions"))
93 return;
94
95 if(g_intransaction) {
96 if(g_mode==POSTGRES || g_mode==ORACLE) {
97 cout<<"COMMIT WORK;"<<endl;
98 }
99 else if(g_mode == MYSQL || g_mode == SQLITE || g_mode == MYDNS) {
100 cout<<"COMMIT;"<<endl;
101 }
102 }
103 g_intransaction=1;
104
105 if(g_mode == MYSQL || g_mode == MYDNS)
106 cout<<"BEGIN;"<<endl;
107 else
108 cout<<"BEGIN TRANSACTION;"<<endl;
109 }
110
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;
115 }
116 else if(g_mode==ORACLE) {
117 cout<<"insert into domains (id,name,type) values (domains_id_sequence.nextval,"<<toLower(sqlstr(domain))<<",'NATIVE');"<<endl;
118 }
119 }
120 else
121 {
122
123 if(g_mode==POSTGRES || g_mode==MYSQL || g_mode==SQLITE) {
124 string mstrs;
125 if (masters != 0 && ! masters->empty()) {
126 BOOST_FOREACH(const string& mstr, *masters) {
127 mstrs.append(mstr);
128 mstrs.append(1, ' ');
129 }
130 }
131 if (mstrs.empty())
132 cout<<"insert into domains (name,type) values ("<<sqlstr(domain)<<",'NATIVE');"<<endl;
133 else
134 cout<<"insert into domains (name,type,master) values ("<<sqlstr(domain)<<",'SLAVE'"<<", '"<<mstrs<<"');"<<endl;
135 }
136 else if (g_mode == ORACLE) {
137 cerr<<"Slave import mode not supported with oracle."<<endl;
138 }
139 }
140 }
141
142 static void emitRecord(const string& zoneName, const string &qname, const string &qtype, const string &ocontent, int ttl, int prio)
143 {
144 g_numRecords++;
145 string content(ocontent);
146
147 if(qtype == "NSEC" || qtype == "NSEC3")
148 return; // NSECs do not go in the database
149
150 if(qtype == "MX" || qtype == "SRV") {
151 prio=atoi(content.c_str());
152
153 string::size_type pos = content.find_first_not_of("0123456789");
154 if(pos != string::npos)
155 boost::erase_head(content, pos);
156 trim_left(content);
157 }
158
159 bool auth = true;
160 if(qtype == "NS" && !pdns_iequals(stripDot(qname), zoneName)) {
161 auth=false;
162 }
163
164 if(g_mode==MYSQL || g_mode==SQLITE) {
165 if(!g_doDNSSEC) {
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";
171 } else
172 {
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";
179 }
180 }
181 else if(g_mode==POSTGRES) {
182 if(!g_doDNSSEC) {
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";
188 } else
189 {
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";
196 }
197 }
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";
204 }
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")
209 {
210 if ((qtype == "MX" || qtype == "NS" || qtype == "SRV" || qtype == "CNAME") && content[content.size()-1] != '.')
211 content.append(".");
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";
217 }
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);
222
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";
226 }
227 else
228 {
229 cerr<<"Record type "<<qtype<<" is not supported."<<endl;
230 }
231 }
232 }
233
234
235 /* 2 modes of operation, either --named or --zone (the latter needs $ORIGIN)
236 2 further modes: --mysql or --oracle
237 */
238
239 ArgvMap &arg()
240 {
241 static ArgvMap theArg;
242 return theArg;
243 }
244
245
246 int main(int argc, char **argv)
247 try
248 {
249 reportAllTypes();
250 reportFancyTypes();
251 #if __GNUC__ >= 3
252 std::ios_base::sync_with_stdio(false);
253 #endif
254
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")="";
268
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";
273
274 ::arg().setCmd("help","Provide a helpful message");
275
276 S.declare("logmessages");
277
278 string namedfile="";
279 string zonefile="";
280
281 ::arg().parse(argc, argv);
282
283 if(argc<2 || ::arg().mustDo("help")) {
284 cerr<<"syntax:"<<endl<<endl;
285 cerr<<::arg().helpstring()<<endl;
286 exit(1);
287 }
288
289 if(::arg().mustDo("gmysql"))
290 g_mode=MYSQL;
291 else if(::arg().mustDo("gpgsql"))
292 g_mode=POSTGRES;
293 else if(::arg().mustDo("gsqlite"))
294 g_mode=SQLITE;
295 else if(::arg().mustDo("oracle")) {
296 g_mode=ORACLE;
297 if(!::arg().mustDo("transactions"))
298 cout<<"set autocommit on;"<<endl;
299 }
300 else if(::arg().mustDo("mydns"))
301 g_mode=MYDNS;
302 else {
303 cerr<<"Unknown SQL mode!\n\n";
304 cerr<<"syntax:"<<endl<<endl;
305 cerr<<::arg().helpstring()<<endl;
306 exit(1);
307 }
308
309 g_doDNSSEC=::arg().mustDo("dnssec");
310
311 namedfile=::arg()["named-conf"];
312 zonefile=::arg()["zone"];
313
314 int count=0, num_domainsdone=0;
315
316 if(zonefile.empty()) {
317 BindParser BP;
318 BP.setVerbose(::arg().mustDo("verbose"));
319 BP.parse(namedfile.empty() ? "./named.conf" : namedfile);
320
321 vector<BindDomainInfo> domains=BP.getDomains();
322 struct stat st;
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;
327 }
328 }
329
330 sort(domains.begin(), domains.end()); // put stuff in inode order
331
332 int numdomains=domains.size();
333 int tick=numdomains/100;
334
335 for(vector<BindDomainInfo>::const_iterator i=domains.begin();
336 i!=domains.end();
337 ++i)
338 {
339 if(i->type!="master" && i->type!="slave") {
340 cerr<<" Warning! Skipping '"<<i->type<<"' zone '"<<i->name<<"'"<<endl;
341 continue;
342 }
343 try {
344 startNewTransaction();
345
346 emitDomain(i->name, &(i->masters));
347
348 ZoneParserTNG zpt(i->filename, i->name, BP.getDirectory());
349 DNSResourceRecord rr;
350 while(zpt.get(rr))
351 emitRecord(i->name, rr.qname, rr.qtype.getName(), rr.content, rr.ttl, rr.priority);
352 num_domainsdone++;
353 }
354 catch(std::exception &ae) {
355 if(!::arg().mustDo("on-error-resume-next"))
356 throw;
357 else
358 cerr<<endl<<ae.what()<<endl;
359 }
360 catch(PDNSException &ae) {
361 if(!::arg().mustDo("on-error-resume-next"))
362 throw;
363 else
364 cerr<<ae.reason<<endl;
365 }
366
367
368 if(!tick || !((count++)%tick))
369 cerr<<"\r"<<count*100/numdomains<<"% done ("<<i->filename<<")\033\133\113";
370 }
371 cerr<<"\r100% done\033\133\113"<<endl;
372 }
373 else {
374 string zonename = ::arg()["zone-name"];
375 ZoneParserTNG zpt(zonefile, zonename);
376 DNSResourceRecord rr;
377 startNewTransaction();
378 emitDomain(zonename);
379 while(zpt.get(rr))
380 emitRecord(zonename, rr.qname, rr.qtype.getName(), rr.content, rr.ttl, rr.priority);
381 num_domainsdone=1;
382 }
383 cerr<<num_domainsdone<<" domains were fully parsed, containing "<<g_numRecords<<" records\n";
384
385 if(::arg().mustDo("transactions") && g_intransaction) {
386 if(g_mode != SQLITE)
387 cout<<"COMMIT WORK;"<<endl;
388 else
389 cout<<"COMMIT;"<<endl;
390 }
391 return 0;
392 }
393 catch(PDNSException &ae) {
394 cerr<<"\nFatal error: "<<ae.reason<<endl;
395 return 1;
396 }
397 catch(std::exception &e) {
398 cerr<<"\ndied because of STL error: "<<e.what()<<endl;
399 return 1;
400 }
401 catch(...) {
402 cerr<<"\ndied because of unknown exception"<<endl;
403 return 1;
404 }