]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/zoneparser-tng.cc
Merge pull request #3634 from rgacogne/dnsdist-dangling-tcp-fd
[thirdparty/pdns.git] / pdns / zoneparser-tng.cc
CommitLineData
f814d7c8
BH
1/*
2 PowerDNS Versatile Database Driven Nameserver
4d2c97aa 3 Copyright (C) 2005 - 2008 PowerDNS.COM BV
f814d7c8
BH
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
f782fe38
MH
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
f814d7c8
BH
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
06bd9ccf 20 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
f814d7c8
BH
21*/
22
870a0fe4
AT
23#ifdef HAVE_CONFIG_H
24#include "config.h"
25#endif
f814d7c8
BH
26#include "dnsparser.hh"
27#include "sstuff.hh"
28#include "misc.hh"
29#include "dnswriter.hh"
30#include "dnsrecords.hh"
f814d7c8
BH
31#include "misc.hh"
32#include <fstream>
33#include "dns.hh"
34#include "zoneparser-tng.hh"
125e4840
BH
35#include <deque>
36#include <boost/algorithm/string.hpp>
f814d7c8 37
3108d502 38static string g_INstr("IN");
39
675fa24c 40ZoneParserTNG::ZoneParserTNG(const string& fname, const DNSName& zname, const string& reldir) : d_reldir(reldir),
232f0877 41 d_zonename(zname), d_defaultttl(3600),
80ad238a
AT
42 d_templatecounter(0), d_templatestop(0),
43 d_templatestep(0), d_havedollarttl(false){
bf503cc0
BH
44 stackFile(fname);
45}
46
675fa24c 47ZoneParserTNG::ZoneParserTNG(const vector<string> zonedata, const DNSName& zname):
0f0e73fe 48 d_zonename(zname), d_defaultttl(3600),
80ad238a
AT
49 d_templatecounter(0), d_templatestop(0),
50 d_templatestep(0), d_havedollarttl(false)
0f0e73fe 51{
0f0e73fe
MS
52 d_zonedata = zonedata;
53 d_zonedataline = d_zonedata.begin();
54 d_fromfile = false;
55}
56
bf503cc0
BH
57void ZoneParserTNG::stackFile(const std::string& fname)
58{
59 FILE *fp=fopen(fname.c_str(), "r");
60 if(!fp)
f814d7c8 61 throw runtime_error("Unable to open file '"+fname+"': "+stringerror());
cfe397d5
BH
62
63 filestate fs(fp, fname);
64 d_filestates.push(fs);
0f0e73fe 65 d_fromfile = true;
f814d7c8
BH
66}
67
68ZoneParserTNG::~ZoneParserTNG()
69{
cfe397d5
BH
70 while(!d_filestates.empty()) {
71 fclose(d_filestates.top().d_fp);
72 d_filestates.pop();
bf503cc0 73 }
f814d7c8
BH
74}
75
125e4840
BH
76static string makeString(const string& line, const pair<string::size_type, string::size_type>& range)
77{
78 return string(line.c_str() + range.first, range.second - range.first);
79}
80
cfe397d5
BH
81static bool isTimeSpec(const string& nextpart)
82{
83 if(nextpart.empty())
84 return false;
85 for(string::const_iterator iter = nextpart.begin(); iter != nextpart.end(); ++iter) {
86 if(isdigit(*iter))
87 continue;
88 if(iter+1 != nextpart.end())
89 return false;
90 char c=tolower(*iter);
2326ec3f 91 return (c=='s' || c=='m' || c=='h' || c=='d' || c=='w' || c=='y');
cfe397d5
BH
92 }
93 return true;
94}
95
96
97unsigned int ZoneParserTNG::makeTTLFromZone(const string& str)
125e4840
BH
98{
99 if(str.empty())
100 return 0;
101
335da0ba 102 unsigned int val=pdns_stou(str);
3108d502 103 char lc=dns_tolower(str[str.length()-1]);
125e4840
BH
104 if(!isdigit(lc))
105 switch(lc) {
3108d502 106 case 's':
2326ec3f 107 break;
3108d502 108 case 'm':
38e655b6
BH
109 val*=60; // minutes, not months!
110 break;
3108d502 111 case 'h':
125e4840
BH
112 val*=3600;
113 break;
3108d502 114 case 'd':
125e4840
BH
115 val*=3600*24;
116 break;
3108d502 117 case 'w':
125e4840
BH
118 val*=3600*24*7;
119 break;
3108d502 120 case 'y': // ? :-)
125e4840
BH
121 val*=3600*24*365;
122 break;
cfe397d5 123
125e4840 124 default:
3fed7dbd 125 throw PDNSException("Unable to parse time specification '"+str+"' "+getLineOfFile());
125e4840
BH
126 }
127 return val;
128}
129
2e83ba09
BH
130bool ZoneParserTNG::getTemplateLine()
131{
b8c3ea84 132 if(d_templateparts.empty() || d_templatecounter > d_templatestop) // no template, or done with
2e83ba09
BH
133 return false;
134
135 string retline;
136 for(parts_t::const_iterator iter = d_templateparts.begin() ; iter != d_templateparts.end(); ++iter) {
137 if(iter != d_templateparts.begin())
138 retline+=" ";
139
140 string part=makeString(d_templateline, *iter);
141
142 /* a part can contain a 'naked' $, an escaped $ (\$), or ${offset,width,radix}, with width defaulting to 0,
143 and radix beging 'd', 'o', 'x' or 'X', defaulting to 'd'.
144
145 The width is zero-padded, so if the counter is at 1, the offset is 15, with is 3, and the radix is 'x',
146 output will be '010', from the input of ${15,3,x}
147 */
148
149 string outpart;
150 outpart.reserve(part.size()+5);
151 bool inescape=false;
152
153 for(string::size_type pos = 0; pos < part.size() ; ++pos) {
154 char c=part[pos];
155 if(inescape) {
4957a608
BH
156 outpart.append(1, c);
157 inescape=false;
158 continue;
2e83ba09 159 }
4957a608 160
2e83ba09 161 if(part[pos]=='\\') {
4957a608
BH
162 inescape=true;
163 continue;
2e83ba09
BH
164 }
165 if(c=='$') {
4957a608 166 if(pos + 1 == part.size() || part[pos+1]!='{') { // a trailing $, or not followed by {
335da0ba 167 outpart.append(std::to_string(d_templatecounter));
4957a608
BH
168 continue;
169 }
170
171 // need to deal with { case
172
173 pos+=2;
174 string::size_type startPos=pos;
175 for(; pos < part.size() && part[pos]!='}' ; ++pos)
176 ;
177
178 if(pos == part.size()) // partial spec
179 break;
180
181 // we are on the '}'
182
183 string spec(part.c_str() + startPos, part.c_str() + pos);
184 int offset=0, width=0;
185 char radix='d';
186 sscanf(spec.c_str(), "%d,%d,%c", &offset, &width, &radix); // parse format specifier
187
188 char format[12];
189 snprintf(format, sizeof(format) - 1, "%%0%d%c", width, radix); // make into printf-style format
190
191 char tmp[80];
192 snprintf(tmp, sizeof(tmp)-1, format, d_templatecounter + offset); // and do the actual printing
193 outpart+=tmp;
2e83ba09
BH
194 }
195 else
4957a608 196 outpart.append(1, c);
2e83ba09
BH
197 }
198 retline+=outpart;
199 }
200 d_templatecounter+=d_templatestep;
201
202 d_line = retline;
203 return true;
204}
205
a09683af
BH
206void chopComment(string& line)
207{
3108d502 208 if(line.find(';')==string::npos)
209 return;
a09683af
BH
210 string::size_type pos, len = line.length();
211 bool inQuote=false;
212 for(pos = 0 ; pos < len; ++pos) {
213 if(line[pos]=='\\')
214 pos++;
215 else if(line[pos]=='"')
216 inQuote=!inQuote;
217 else if(line[pos]==';' && !inQuote)
218 break;
219 }
220 if(pos != len)
221 line.resize(pos);
222}
223
9f0076d7
BH
224bool findAndElide(string& line, char c)
225{
226 string::size_type pos, len = line.length();
227 bool inQuote=false;
228 for(pos = 0 ; pos < len; ++pos) {
229 if(line[pos]=='\\')
230 pos++;
231 else if(line[pos]=='"')
232 inQuote=!inQuote;
233 else if(line[pos]==c && !inQuote)
234 break;
235 }
236 if(pos != len) {
237 line.erase(pos, 1);
238 return true;
239 }
240 return false;
241}
242
d16a2ccf
PL
243DNSName ZoneParserTNG::getZoneName()
244{
245 return d_zonename;
246}
247
d27ea394
BH
248string ZoneParserTNG::getLineOfFile()
249{
0f0e73fe 250 if (d_zonedata.size() > 0)
335da0ba 251 return "on line "+std::to_string(std::distance(d_zonedata.begin(), d_zonedataline))+" of given string";
0f0e73fe 252
335da0ba 253 return "on line "+std::to_string(d_filestates.top().d_lineno)+" of file '"+d_filestates.top().d_filename+"'";
d27ea394 254}
cfe397d5 255
c91effc8 256pair<string,int> ZoneParserTNG::getLineNumAndFile()
257{
258 return {d_filestates.top().d_filename, d_filestates.top().d_lineno};
259}
260
43f40013 261// ODD: this function never fills out the prio field! rest of pdns compensates though
a5a1f447 262bool ZoneParserTNG::get(DNSResourceRecord& rr, std::string* comment)
f814d7c8
BH
263{
264 retry:;
2e83ba09 265 if(!getTemplateLine() && !getLine())
f814d7c8 266 return false;
125e4840 267
df1d406a 268 boost::trim_right_if(d_line, is_any_of(" \r\n\x1a"));
a5a1f447 269 if(comment)
270 comment->clear();
271 if(comment && d_line.find(';') != string::npos)
272 *comment = d_line.substr(d_line.find(';'));
2e83ba09 273 parts_t parts;
125e4840
BH
274 vstringtok(parts, d_line);
275
276 if(parts.empty())
277 goto retry;
278
3108d502 279 if(parts[0].first != parts[0].second && d_line[parts[0].first]==';') // line consisting of nothing but comments
4d2c97aa
BH
280 goto retry;
281
125e4840 282 if(d_line[0]=='$') {
bf503cc0 283 string command=makeString(d_line, parts[0]);
ec6480f3 284 if(pdns_iequals(command,"$TTL") && parts.size() > 1) {
cfe397d5 285 d_defaultttl=makeTTLFromZone(trim_right_copy_if(makeString(d_line, parts[1]), is_any_of(";")));
df1d406a
BH
286 d_havedollarttl=true;
287 }
0f0e73fe 288 else if(pdns_iequals(command,"$INCLUDE") && parts.size() > 1 && d_fromfile) {
da042e6e
BH
289 string fname=unquotify(makeString(d_line, parts[1]));
290 if(!fname.empty() && fname[0]!='/' && !d_reldir.empty())
4957a608 291 fname=d_reldir+"/"+fname;
da042e6e 292 stackFile(fname);
bf503cc0 293 }
ec6480f3 294 else if(pdns_iequals(command, "$ORIGIN") && parts.size() > 1) {
e720f311 295 d_zonename = DNSName(makeString(d_line, parts[1]));
2e83ba09 296 }
ec6480f3 297 else if(pdns_iequals(command, "$GENERATE") && parts.size() > 2) {
bf503cc0
BH
298 // $GENERATE 1-127 $ CNAME $.0
299 string range=makeString(d_line, parts[1]);
2e83ba09
BH
300 d_templatestep=1;
301 d_templatestop=0;
302 sscanf(range.c_str(),"%d-%d/%d", &d_templatecounter, &d_templatestop, &d_templatestep);
303 d_templateline=d_line;
304 parts.pop_front();
305 parts.pop_front();
306
307 d_templateparts=parts;
308 goto retry;
bf503cc0 309 }
125e4840 310 else
d27ea394 311 throw exception("Can't parse zone line '"+d_line+"' "+getLineOfFile());
f814d7c8 312 goto retry;
f814d7c8 313 }
125e4840 314
e720f311 315 bool prevqname=false;
d66269ef 316 string qname = makeString(d_line, parts[0]); // Don't use DNSName here!
3108d502 317 if(dns_isspace(d_line[0])) {
125e4840 318 rr.qname=d_prevqname;
e720f311
KM
319 prevqname=true;
320 }else {
8171ab83 321 rr.qname=DNSName(qname);
125e4840 322 parts.pop_front();
e720f311 323 if(qname.empty() || qname[0]==';')
125e4840
BH
324 goto retry;
325 }
d66269ef 326 if(qname=="@")
125e4840 327 rr.qname=d_zonename;
e720f311 328 else if(!prevqname && !isCanonical(qname))
675fa24c 329 rr.qname += d_zonename;
125e4840
BH
330 d_prevqname=rr.qname;
331
332 if(parts.empty())
d27ea394 333 throw exception("Line with too little parts "+getLineOfFile());
125e4840 334
125e4840 335 string nextpart;
f814d7c8 336
125e4840
BH
337 rr.ttl=d_defaultttl;
338 bool haveTTL=0, haveQTYPE=0;
339 pair<string::size_type, string::size_type> range;
340
341 while(!parts.empty()) {
342 range=parts.front();
343 parts.pop_front();
344 nextpart=makeString(d_line, range);
345 if(nextpart.empty())
346 break;
347
a5a1f447 348 if(nextpart.find(';')!=string::npos) {
125e4840 349 break;
a5a1f447 350 }
125e4840
BH
351
352 // cout<<"Next part: '"<<nextpart<<"'"<<endl;
3108d502 353
354 if(pdns_iequals(nextpart, g_INstr)) {
125e4840
BH
355 // cout<<"Ignoring 'IN'\n";
356 continue;
357 }
cfe397d5 358 if(!haveTTL && !haveQTYPE && isTimeSpec(nextpart)) {
125e4840
BH
359 rr.ttl=makeTTLFromZone(nextpart);
360 haveTTL=true;
361 // cout<<"ttl is probably: "<<rr.ttl<<endl;
362 continue;
363 }
364 if(haveQTYPE)
365 break;
366
367 try {
368 rr.qtype=DNSRecordContent::TypeToNumber(nextpart);
369 // cout<<"Got qtype ("<<rr.qtype.getCode()<<")\n";
370 haveQTYPE=1;
371 continue;
372 }
373 catch(...) {
d27ea394 374 throw runtime_error("Parsing zone content "+getLineOfFile()+
232f0877
CH
375 ": '"+nextpart+
376 "' doesn't look like a qtype, stopping loop");
125e4840
BH
377 }
378 }
379 if(!haveQTYPE)
d27ea394 380 throw exception("Malformed line "+getLineOfFile()+": '"+d_line+"'");
125e4840 381
3108d502 382 // rr.content=d_line.substr(range.first);
383 rr.content.assign(d_line, range.first, string::npos);
a09683af 384 chopComment(rr.content);
3108d502 385 trim_if(rr.content, is_any_of(" \r\n\x1a"));
849fde0b 386
3108d502 387 if(rr.content.size()==1 && rr.content[0]=='@')
90c3521b 388 rr.content=d_zonename.toString();
b33702d5 389
9f0076d7
BH
390 if(findAndElide(rr.content, '(')) { // have found a ( and elided it
391 if(!findAndElide(rr.content, ')')) {
392 while(getLine()) {
4957a608
BH
393 trim_right(d_line);
394 chopComment(d_line);
395 trim(d_line);
396
397 bool ended = findAndElide(d_line, ')');
398 rr.content+=" "+d_line;
399 if(ended)
400 break;
125e4840 401 }
125e4840
BH
402 }
403 }
06918967 404 trim_if(rr.content, is_any_of(" \r\n\x1a"));
9f0076d7 405
43f40013 406 vector<string> recparts;
125e4840
BH
407 switch(rr.qtype.getCode()) {
408 case QType::MX:
43f40013
BH
409 stringtok(recparts, rr.content);
410 if(recparts.size()==2) {
4c22f7c5 411 if (recparts[1]!=".")
0c0e717c 412 recparts[1] = toCanonic(d_zonename, recparts[1]).toStringRootDot();
43f40013
BH
413 rr.content=recparts[0]+" "+recparts[1];
414 }
415 break;
25e0cd7f 416
6348d406
PD
417 case QType::RP:
418 stringtok(recparts, rr.content);
419 if(recparts.size()==2) {
0c0e717c
PD
420 recparts[0] = toCanonic(d_zonename, recparts[0]).toStringRootDot();
421 recparts[1] = toCanonic(d_zonename, recparts[1]).toStringRootDot();
6348d406
PD
422 rr.content=recparts[0]+" "+recparts[1];
423 }
424 break;
425
25e0cd7f
BH
426 case QType::SRV:
427 stringtok(recparts, rr.content);
428 if(recparts.size()==4) {
3ab6b614 429 if(recparts[3]!=".")
0c0e717c 430 recparts[3] = toCanonic(d_zonename, recparts[3]).toStringRootDot();
25e0cd7f
BH
431 rr.content=recparts[0]+" "+recparts[1]+" "+recparts[2]+" "+recparts[3];
432 }
433 break;
434
43f40013 435
125e4840
BH
436 case QType::NS:
437 case QType::CNAME:
8dee0750 438 case QType::DNAME:
125e4840 439 case QType::PTR:
37f47031 440 case QType::AFSDB:
0c0e717c 441 rr.content=toCanonic(d_zonename, rr.content).toStringRootDot();
125e4840
BH
442 break;
443
444 case QType::SOA:
43f40013 445 stringtok(recparts, rr.content);
0c306c7c
PL
446 if(recparts.size() > 7)
447 throw PDNSException("SOA record contents for "+rr.qname.toString()+" contains too many parts");
43f40013 448 if(recparts.size() > 1) {
0c0e717c
PD
449 recparts[0]=toCanonic(d_zonename, recparts[0]).toStringRootDot();
450 recparts[1]=toCanonic(d_zonename, recparts[1]).toStringRootDot();
125e4840
BH
451 }
452 rr.content.clear();
43f40013 453 for(string::size_type n = 0; n < recparts.size(); ++n) {
125e4840 454 if(n)
4957a608 455 rr.content.append(1,' ');
c9f935fa 456
82cc6877 457 if(n > 1)
335da0ba 458 rr.content+=std::to_string(makeTTLFromZone(recparts[n]));
82cc6877 459 else
43f40013 460 rr.content+=recparts[n];
c9f935fa 461
df1d406a 462 if(n==6 && !d_havedollarttl)
43f40013 463 d_defaultttl=makeTTLFromZone(recparts[n]);
125e4840 464 }
38e655b6 465 break;
125e4840
BH
466 default:;
467 }
468
469 rr.d_place=DNSResourceRecord::ANSWER;
f814d7c8
BH
470 return true;
471}
472
834942f1 473
f814d7c8
BH
474bool ZoneParserTNG::getLine()
475{
0f0e73fe
MS
476 if (d_zonedata.size() > 0) {
477 if (d_zonedataline != d_zonedata.end()) {
478 d_line = *d_zonedataline;
479 d_zonedataline++;
480 return true;
481 }
482 return false;
483 }
cfe397d5 484 while(!d_filestates.empty()) {
834942f1 485 if(stringfgets(d_filestates.top().d_fp, d_line)) {
cfe397d5 486 d_filestates.top().d_lineno++;
bf503cc0
BH
487 return true;
488 }
cfe397d5
BH
489 fclose(d_filestates.top().d_fp);
490 d_filestates.pop();
f814d7c8
BH
491 }
492 return false;
493}