]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/zoneparser-tng.cc
Make the constructors taking a pthread_rwlock_t * private.
[thirdparty/pdns.git] / pdns / zoneparser-tng.cc
CommitLineData
f814d7c8 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 */
870a0fe4
AT
22#ifdef HAVE_CONFIG_H
23#include "config.h"
24#endif
a416d398 25#include "ascii.hh"
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>
e0e48a7a 37#include <system_error>
f814d7c8 38
3108d502 39static string g_INstr("IN");
40
675fa24c 41ZoneParserTNG::ZoneParserTNG(const string& fname, const DNSName& zname, const string& reldir) : d_reldir(reldir),
232f0877 42 d_zonename(zname), d_defaultttl(3600),
80ad238a
AT
43 d_templatecounter(0), d_templatestop(0),
44 d_templatestep(0), d_havedollarttl(false){
bf503cc0
BH
45 stackFile(fname);
46}
47
675fa24c 48ZoneParserTNG::ZoneParserTNG(const vector<string> zonedata, const DNSName& zname):
8a70e507
CHB
49 d_zonename(zname), d_zonedata(zonedata), d_defaultttl(3600),
50 d_templatecounter(0), d_templatestop(0), d_templatestep(0),
51 d_havedollarttl(false), d_fromfile(false)
0f0e73fe 52{
0f0e73fe 53 d_zonedataline = d_zonedata.begin();
0f0e73fe
MS
54}
55
bf503cc0
BH
56void ZoneParserTNG::stackFile(const std::string& fname)
57{
58 FILE *fp=fopen(fname.c_str(), "r");
e0e48a7a
PL
59 if(!fp) {
60 std::error_code ec (errno,std::generic_category());
61 throw std::system_error(ec, "Unable to open file '"+fname+"': "+stringerror());
62 }
cfe397d5
BH
63
64 filestate fs(fp, fname);
65 d_filestates.push(fs);
0f0e73fe 66 d_fromfile = true;
f814d7c8
BH
67}
68
69ZoneParserTNG::~ZoneParserTNG()
70{
cfe397d5
BH
71 while(!d_filestates.empty()) {
72 fclose(d_filestates.top().d_fp);
73 d_filestates.pop();
bf503cc0 74 }
f814d7c8
BH
75}
76
125e4840
BH
77static string makeString(const string& line, const pair<string::size_type, string::size_type>& range)
78{
79 return string(line.c_str() + range.first, range.second - range.first);
80}
81
cfe397d5
BH
82static bool isTimeSpec(const string& nextpart)
83{
84 if(nextpart.empty())
85 return false;
86 for(string::const_iterator iter = nextpart.begin(); iter != nextpart.end(); ++iter) {
87 if(isdigit(*iter))
88 continue;
89 if(iter+1 != nextpart.end())
90 return false;
91 char c=tolower(*iter);
2326ec3f 92 return (c=='s' || c=='m' || c=='h' || c=='d' || c=='w' || c=='y');
cfe397d5
BH
93 }
94 return true;
95}
96
97
98unsigned int ZoneParserTNG::makeTTLFromZone(const string& str)
125e4840
BH
99{
100 if(str.empty())
101 return 0;
102
97ce13be
RG
103 unsigned int val;
104 try {
105 val=pdns_stou(str);
106 }
107 catch (const std::out_of_range& oor) {
108 throw PDNSException("Unable to parse time specification '"+str+"' "+getLineOfFile());
109 }
110
3108d502 111 char lc=dns_tolower(str[str.length()-1]);
125e4840
BH
112 if(!isdigit(lc))
113 switch(lc) {
3108d502 114 case 's':
2326ec3f 115 break;
3108d502 116 case 'm':
38e655b6
BH
117 val*=60; // minutes, not months!
118 break;
3108d502 119 case 'h':
125e4840
BH
120 val*=3600;
121 break;
3108d502 122 case 'd':
125e4840
BH
123 val*=3600*24;
124 break;
3108d502 125 case 'w':
125e4840
BH
126 val*=3600*24*7;
127 break;
3108d502 128 case 'y': // ? :-)
125e4840
BH
129 val*=3600*24*365;
130 break;
cfe397d5 131
125e4840 132 default:
3fed7dbd 133 throw PDNSException("Unable to parse time specification '"+str+"' "+getLineOfFile());
125e4840
BH
134 }
135 return val;
136}
137
2e83ba09
BH
138bool ZoneParserTNG::getTemplateLine()
139{
b8c3ea84 140 if(d_templateparts.empty() || d_templatecounter > d_templatestop) // no template, or done with
2e83ba09
BH
141 return false;
142
143 string retline;
144 for(parts_t::const_iterator iter = d_templateparts.begin() ; iter != d_templateparts.end(); ++iter) {
145 if(iter != d_templateparts.begin())
146 retline+=" ";
147
148 string part=makeString(d_templateline, *iter);
149
150 /* a part can contain a 'naked' $, an escaped $ (\$), or ${offset,width,radix}, with width defaulting to 0,
f1f15cb2 151 and radix being 'd', 'o', 'x' or 'X', defaulting to 'd'.
2e83ba09
BH
152
153 The width is zero-padded, so if the counter is at 1, the offset is 15, with is 3, and the radix is 'x',
154 output will be '010', from the input of ${15,3,x}
155 */
156
157 string outpart;
158 outpart.reserve(part.size()+5);
159 bool inescape=false;
160
161 for(string::size_type pos = 0; pos < part.size() ; ++pos) {
162 char c=part[pos];
163 if(inescape) {
4957a608
BH
164 outpart.append(1, c);
165 inescape=false;
166 continue;
2e83ba09 167 }
4957a608 168
2e83ba09 169 if(part[pos]=='\\') {
4957a608
BH
170 inescape=true;
171 continue;
2e83ba09
BH
172 }
173 if(c=='$') {
4957a608 174 if(pos + 1 == part.size() || part[pos+1]!='{') { // a trailing $, or not followed by {
335da0ba 175 outpart.append(std::to_string(d_templatecounter));
4957a608
BH
176 continue;
177 }
178
179 // need to deal with { case
180
181 pos+=2;
182 string::size_type startPos=pos;
183 for(; pos < part.size() && part[pos]!='}' ; ++pos)
184 ;
185
186 if(pos == part.size()) // partial spec
187 break;
188
189 // we are on the '}'
190
191 string spec(part.c_str() + startPos, part.c_str() + pos);
192 int offset=0, width=0;
193 char radix='d';
194 sscanf(spec.c_str(), "%d,%d,%c", &offset, &width, &radix); // parse format specifier
195
4957a608 196 char tmp[80];
4338e69f
OM
197 switch (radix) {
198 case 'o':
199 snprintf(tmp, sizeof(tmp), "%0*o", width, d_templatecounter + offset);
200 break;
201 case 'x':
202 snprintf(tmp, sizeof(tmp), "%0*x", width, d_templatecounter + offset);
203 break;
204 case 'X':
205 snprintf(tmp, sizeof(tmp), "%0*X", width, d_templatecounter + offset);
206 break;
207 case 'd':
208 default:
209 snprintf(tmp, sizeof(tmp), "%0*d", width, d_templatecounter + offset);
210 break;
211 }
4957a608 212 outpart+=tmp;
2e83ba09
BH
213 }
214 else
4957a608 215 outpart.append(1, c);
2e83ba09
BH
216 }
217 retline+=outpart;
218 }
219 d_templatecounter+=d_templatestep;
220
221 d_line = retline;
222 return true;
223}
224
050e6877 225static void chopComment(string& line)
a09683af 226{
3108d502 227 if(line.find(';')==string::npos)
228 return;
a09683af
BH
229 string::size_type pos, len = line.length();
230 bool inQuote=false;
231 for(pos = 0 ; pos < len; ++pos) {
232 if(line[pos]=='\\')
233 pos++;
234 else if(line[pos]=='"')
235 inQuote=!inQuote;
236 else if(line[pos]==';' && !inQuote)
237 break;
238 }
239 if(pos != len)
240 line.resize(pos);
241}
242
050e6877 243static bool findAndElide(string& line, char c)
9f0076d7
BH
244{
245 string::size_type pos, len = line.length();
246 bool inQuote=false;
247 for(pos = 0 ; pos < len; ++pos) {
248 if(line[pos]=='\\')
249 pos++;
250 else if(line[pos]=='"')
251 inQuote=!inQuote;
252 else if(line[pos]==c && !inQuote)
253 break;
254 }
255 if(pos != len) {
256 line.erase(pos, 1);
257 return true;
258 }
259 return false;
260}
261
d16a2ccf
PL
262DNSName ZoneParserTNG::getZoneName()
263{
264 return d_zonename;
265}
266
d27ea394
BH
267string ZoneParserTNG::getLineOfFile()
268{
0f0e73fe 269 if (d_zonedata.size() > 0)
335da0ba 270 return "on line "+std::to_string(std::distance(d_zonedata.begin(), d_zonedataline))+" of given string";
0f0e73fe 271
a1239f65
RG
272 if (d_filestates.empty())
273 return "";
274
335da0ba 275 return "on line "+std::to_string(d_filestates.top().d_lineno)+" of file '"+d_filestates.top().d_filename+"'";
d27ea394 276}
cfe397d5 277
c91effc8 278pair<string,int> ZoneParserTNG::getLineNumAndFile()
279{
9923fc22
PD
280 if (d_filestates.empty())
281 return {"", 0};
282 else
283 return {d_filestates.top().d_filename, d_filestates.top().d_lineno};
c91effc8 284}
285
4950b140 286bool ZoneParserTNG::get(DNSResourceRecord& rr, std::string* comment)
f814d7c8
BH
287{
288 retry:;
2e83ba09 289 if(!getTemplateLine() && !getLine())
f814d7c8 290 return false;
125e4840 291
1e01f0f5 292 boost::trim_right_if(d_line, is_any_of(" \t\r\n\x1a"));
a5a1f447 293 if(comment)
294 comment->clear();
295 if(comment && d_line.find(';') != string::npos)
296 *comment = d_line.substr(d_line.find(';'));
125e4840 297
9bbcf03a
RG
298 d_parts.clear();
299 vstringtok(d_parts, d_line);
300
301 if(d_parts.empty())
125e4840
BH
302 goto retry;
303
9bbcf03a 304 if(d_parts[0].first != d_parts[0].second && d_line[d_parts[0].first]==';') // line consisting of nothing but comments
4d2c97aa
BH
305 goto retry;
306
125e4840 307 if(d_line[0]=='$') {
9bbcf03a
RG
308 string command=makeString(d_line, d_parts[0]);
309 if(pdns_iequals(command,"$TTL") && d_parts.size() > 1) {
310 d_defaultttl=makeTTLFromZone(trim_right_copy_if(makeString(d_line, d_parts[1]), is_any_of(";")));
df1d406a
BH
311 d_havedollarttl=true;
312 }
9bbcf03a
RG
313 else if(pdns_iequals(command,"$INCLUDE") && d_parts.size() > 1 && d_fromfile) {
314 string fname=unquotify(makeString(d_line, d_parts[1]));
da042e6e 315 if(!fname.empty() && fname[0]!='/' && !d_reldir.empty())
4957a608 316 fname=d_reldir+"/"+fname;
da042e6e 317 stackFile(fname);
bf503cc0 318 }
9bbcf03a
RG
319 else if(pdns_iequals(command, "$ORIGIN") && d_parts.size() > 1) {
320 d_zonename = DNSName(makeString(d_line, d_parts[1]));
2e83ba09 321 }
9bbcf03a 322 else if(pdns_iequals(command, "$GENERATE") && d_parts.size() > 2) {
91a862bf
RG
323 if (!d_generateEnabled) {
324 throw exception("$GENERATE is not allowed in this zone");
325 }
bf503cc0 326 // $GENERATE 1-127 $ CNAME $.0
9bbcf03a 327 string range=makeString(d_line, d_parts[1]);
2e83ba09
BH
328 d_templatestep=1;
329 d_templatestop=0;
bfce2f57 330 sscanf(range.c_str(),"%u-%u/%u", &d_templatecounter, &d_templatestop, &d_templatestep);
775a673a
OM
331 if (d_templatestep < 1 ||
332 d_templatestop < d_templatecounter) {
b4ab09cd 333 throw exception("Invalid $GENERATE parameters");
775a673a 334 }
ba3d53d1
RG
335 if (d_maxGenerateSteps != 0) {
336 size_t numberOfSteps = (d_templatestop - d_templatecounter) / d_templatestep;
337 if (numberOfSteps > d_maxGenerateSteps) {
338 throw exception("The number of $GENERATE steps (" + std::to_string(numberOfSteps) + ") is too high, the maximum is set to " + std::to_string(d_maxGenerateSteps));
339 }
340 }
2e83ba09 341 d_templateline=d_line;
9bbcf03a
RG
342 d_parts.pop_front();
343 d_parts.pop_front();
2e83ba09 344
9bbcf03a 345 d_templateparts=d_parts;
2e83ba09 346 goto retry;
bf503cc0 347 }
125e4840 348 else
d27ea394 349 throw exception("Can't parse zone line '"+d_line+"' "+getLineOfFile());
f814d7c8 350 goto retry;
f814d7c8 351 }
125e4840 352
e720f311 353 bool prevqname=false;
9bbcf03a 354 string qname = makeString(d_line, d_parts[0]); // Don't use DNSName here!
3108d502 355 if(dns_isspace(d_line[0])) {
125e4840 356 rr.qname=d_prevqname;
e720f311
KM
357 prevqname=true;
358 }else {
8171ab83 359 rr.qname=DNSName(qname);
9bbcf03a 360 d_parts.pop_front();
e720f311 361 if(qname.empty() || qname[0]==';')
125e4840
BH
362 goto retry;
363 }
d66269ef 364 if(qname=="@")
125e4840 365 rr.qname=d_zonename;
e720f311 366 else if(!prevqname && !isCanonical(qname))
675fa24c 367 rr.qname += d_zonename;
125e4840
BH
368 d_prevqname=rr.qname;
369
9bbcf03a 370 if(d_parts.empty())
d27ea394 371 throw exception("Line with too little parts "+getLineOfFile());
125e4840 372
125e4840 373 string nextpart;
f814d7c8 374
125e4840
BH
375 rr.ttl=d_defaultttl;
376 bool haveTTL=0, haveQTYPE=0;
377 pair<string::size_type, string::size_type> range;
378
9bbcf03a
RG
379 while(!d_parts.empty()) {
380 range=d_parts.front();
381 d_parts.pop_front();
125e4840
BH
382 nextpart=makeString(d_line, range);
383 if(nextpart.empty())
384 break;
385
a5a1f447 386 if(nextpart.find(';')!=string::npos) {
125e4840 387 break;
a5a1f447 388 }
125e4840
BH
389
390 // cout<<"Next part: '"<<nextpart<<"'"<<endl;
3108d502 391
392 if(pdns_iequals(nextpart, g_INstr)) {
125e4840
BH
393 // cout<<"Ignoring 'IN'\n";
394 continue;
395 }
cfe397d5 396 if(!haveTTL && !haveQTYPE && isTimeSpec(nextpart)) {
125e4840 397 rr.ttl=makeTTLFromZone(nextpart);
7fd6c67e 398 if(!d_havedollarttl)
399 d_defaultttl = rr.ttl;
125e4840
BH
400 haveTTL=true;
401 // cout<<"ttl is probably: "<<rr.ttl<<endl;
402 continue;
403 }
404 if(haveQTYPE)
405 break;
406
407 try {
408 rr.qtype=DNSRecordContent::TypeToNumber(nextpart);
409 // cout<<"Got qtype ("<<rr.qtype.getCode()<<")\n";
410 haveQTYPE=1;
411 continue;
412 }
413 catch(...) {
d27ea394 414 throw runtime_error("Parsing zone content "+getLineOfFile()+
232f0877
CH
415 ": '"+nextpart+
416 "' doesn't look like a qtype, stopping loop");
125e4840
BH
417 }
418 }
419 if(!haveQTYPE)
d27ea394 420 throw exception("Malformed line "+getLineOfFile()+": '"+d_line+"'");
125e4840 421
3108d502 422 // rr.content=d_line.substr(range.first);
423 rr.content.assign(d_line, range.first, string::npos);
a09683af 424 chopComment(rr.content);
1e01f0f5 425 trim_if(rr.content, is_any_of(" \r\n\t\x1a"));
849fde0b 426
3108d502 427 if(rr.content.size()==1 && rr.content[0]=='@')
90c3521b 428 rr.content=d_zonename.toString();
b33702d5 429
9f0076d7
BH
430 if(findAndElide(rr.content, '(')) { // have found a ( and elided it
431 if(!findAndElide(rr.content, ')')) {
432 while(getLine()) {
4957a608
BH
433 trim_right(d_line);
434 chopComment(d_line);
435 trim(d_line);
436
437 bool ended = findAndElide(d_line, ')');
438 rr.content+=" "+d_line;
439 if(ended)
440 break;
125e4840 441 }
125e4840
BH
442 }
443 }
1e01f0f5 444 trim_if(rr.content, is_any_of(" \r\n\t\x1a"));
9f0076d7 445
43f40013 446 vector<string> recparts;
125e4840
BH
447 switch(rr.qtype.getCode()) {
448 case QType::MX:
43f40013
BH
449 stringtok(recparts, rr.content);
450 if(recparts.size()==2) {
1293f91e
PL
451 if (recparts[1]!=".") {
452 try {
453 recparts[1] = toCanonic(d_zonename, recparts[1]).toStringRootDot();
454 } catch (std::exception &e) {
86f1af1c 455 throw PDNSException("Error in record '" + rr.qname.toLogString() + " " + rr.qtype.getName() + "': " + e.what());
1293f91e
PL
456 }
457 }
43f40013
BH
458 rr.content=recparts[0]+" "+recparts[1];
459 }
460 break;
25e0cd7f 461
6348d406
PD
462 case QType::RP:
463 stringtok(recparts, rr.content);
464 if(recparts.size()==2) {
0c0e717c
PD
465 recparts[0] = toCanonic(d_zonename, recparts[0]).toStringRootDot();
466 recparts[1] = toCanonic(d_zonename, recparts[1]).toStringRootDot();
6348d406
PD
467 rr.content=recparts[0]+" "+recparts[1];
468 }
469 break;
470
25e0cd7f
BH
471 case QType::SRV:
472 stringtok(recparts, rr.content);
473 if(recparts.size()==4) {
1293f91e
PL
474 if(recparts[3]!=".") {
475 try {
476 recparts[3] = toCanonic(d_zonename, recparts[3]).toStringRootDot();
477 } catch (std::exception &e) {
86f1af1c 478 throw PDNSException("Error in record '" + rr.qname.toLogString() + " " + rr.qtype.getName() + "': " + e.what());
1293f91e
PL
479 }
480 }
25e0cd7f
BH
481 rr.content=recparts[0]+" "+recparts[1]+" "+recparts[2]+" "+recparts[3];
482 }
483 break;
484
43f40013 485
125e4840
BH
486 case QType::NS:
487 case QType::CNAME:
8dee0750 488 case QType::DNAME:
125e4840 489 case QType::PTR:
1293f91e
PL
490 try {
491 rr.content = toCanonic(d_zonename, rr.content).toStringRootDot();
492 } catch (std::exception &e) {
86f1af1c 493 throw PDNSException("Error in record '" + rr.qname.toLogString() + " " + rr.qtype.getName() + "': " + e.what());
1293f91e 494 }
125e4840 495 break;
319d6e47 496 case QType::AFSDB:
9f067a15
JJ
497 stringtok(recparts, rr.content);
498 if(recparts.size() == 2) {
499 try {
319d6e47 500 recparts[1]=toCanonic(d_zonename, recparts[1]).toStringRootDot();
9f067a15 501 } catch (std::exception &e) {
86f1af1c 502 throw PDNSException("Error in record '" + rr.qname.toLogString() + " " + rr.qtype.getName() + "': " + e.what());
319d6e47 503 }
9f067a15 504 } else {
86f1af1c 505 throw PDNSException("AFSDB record for "+rr.qname.toLogString()+" invalid");
9f067a15
JJ
506 }
507 rr.content.clear();
508 for(string::size_type n = 0; n < recparts.size(); ++n) {
509 if(n)
510 rr.content.append(1,' ');
125e4840 511
9f067a15 512 rr.content+=recparts[n];
319d6e47
JJ
513 }
514 break;
125e4840 515 case QType::SOA:
43f40013 516 stringtok(recparts, rr.content);
0c306c7c 517 if(recparts.size() > 7)
86f1af1c 518 throw PDNSException("SOA record contents for "+rr.qname.toLogString()+" contains too many parts");
43f40013 519 if(recparts.size() > 1) {
562c0b13
PL
520 try {
521 recparts[0]=toCanonic(d_zonename, recparts[0]).toStringRootDot();
522 recparts[1]=toCanonic(d_zonename, recparts[1]).toStringRootDot();
1293f91e 523 } catch (std::exception &e) {
86f1af1c 524 throw PDNSException("Error in record '" + rr.qname.toLogString() + " " + rr.qtype.getName() + "': " + e.what());
562c0b13 525 }
125e4840
BH
526 }
527 rr.content.clear();
43f40013 528 for(string::size_type n = 0; n < recparts.size(); ++n) {
125e4840 529 if(n)
4957a608 530 rr.content.append(1,' ');
c9f935fa 531
82cc6877 532 if(n > 1)
335da0ba 533 rr.content+=std::to_string(makeTTLFromZone(recparts[n]));
82cc6877 534 else
43f40013 535 rr.content+=recparts[n];
125e4840 536 }
38e655b6 537 break;
125e4840
BH
538 default:;
539 }
f814d7c8
BH
540 return true;
541}
542
834942f1 543
f814d7c8
BH
544bool ZoneParserTNG::getLine()
545{
0f0e73fe
MS
546 if (d_zonedata.size() > 0) {
547 if (d_zonedataline != d_zonedata.end()) {
548 d_line = *d_zonedataline;
cb6655e7 549 ++d_zonedataline;
0f0e73fe
MS
550 return true;
551 }
552 return false;
553 }
cfe397d5 554 while(!d_filestates.empty()) {
834942f1 555 if(stringfgets(d_filestates.top().d_fp, d_line)) {
cfe397d5 556 d_filestates.top().d_lineno++;
bf503cc0
BH
557 return true;
558 }
cfe397d5
BH
559 fclose(d_filestates.top().d_fp);
560 d_filestates.pop();
f814d7c8
BH
561 }
562 return false;
563}