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