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