]>
Commit | Line | Data |
---|---|---|
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 | 38 | static string g_INstr("IN"); |
39 | ||
675fa24c | 40 | ZoneParserTNG::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 | 47 | ZoneParserTNG::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 |
57 | void 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 | ||
68 | ZoneParserTNG::~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 |
76 | static 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 |
81 | static 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 | ||
97 | unsigned 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 |
130 | bool 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 |
206 | void 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 |
224 | bool 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 |
243 | DNSName ZoneParserTNG::getZoneName() |
244 | { | |
245 | return d_zonename; | |
246 | } | |
247 | ||
d27ea394 BH |
248 | string 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 | 256 | pair<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 | 262 | bool 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 |
474 | bool 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 | } |