]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/zoneparser-tng.cc
we consumed too much random (need only 128 bits), plus close ticket 147: we ignore...
[thirdparty/pdns.git] / pdns / zoneparser-tng.cc
CommitLineData
f814d7c8
BH
1/*
2 PowerDNS Versatile Database Driven Nameserver
2e83ba09 3 Copyright (C) 2005 - 2007 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
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
06bd9ccf 16 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
f814d7c8
BH
17*/
18
19#include "dnsparser.hh"
20#include "sstuff.hh"
21#include "misc.hh"
22#include "dnswriter.hh"
23#include "dnsrecords.hh"
f814d7c8
BH
24#include "misc.hh"
25#include <fstream>
26#include "dns.hh"
27#include "zoneparser-tng.hh"
125e4840
BH
28#include <deque>
29#include <boost/algorithm/string.hpp>
30#include <boost/lexical_cast.hpp>
f814d7c8 31
da042e6e 32ZoneParserTNG::ZoneParserTNG(const string& fname, const string& zname, const string& reldir) : d_reldir(reldir), d_zonename(zname), d_defaultttl(3600)
f814d7c8 33{
98544e13 34 d_zonename = toCanonic("", d_zonename);
bf503cc0
BH
35 stackFile(fname);
36}
37
38void ZoneParserTNG::stackFile(const std::string& fname)
39{
40 FILE *fp=fopen(fname.c_str(), "r");
41 if(!fp)
f814d7c8 42 throw runtime_error("Unable to open file '"+fname+"': "+stringerror());
cfe397d5
BH
43
44 filestate fs(fp, fname);
45 d_filestates.push(fs);
f814d7c8
BH
46}
47
48ZoneParserTNG::~ZoneParserTNG()
49{
cfe397d5
BH
50 while(!d_filestates.empty()) {
51 fclose(d_filestates.top().d_fp);
52 d_filestates.pop();
bf503cc0 53 }
f814d7c8
BH
54}
55
125e4840
BH
56static string makeString(const string& line, const pair<string::size_type, string::size_type>& range)
57{
58 return string(line.c_str() + range.first, range.second - range.first);
59}
60
cfe397d5
BH
61static bool isTimeSpec(const string& nextpart)
62{
63 if(nextpart.empty())
64 return false;
65 for(string::const_iterator iter = nextpart.begin(); iter != nextpart.end(); ++iter) {
66 if(isdigit(*iter))
67 continue;
68 if(iter+1 != nextpart.end())
69 return false;
70 char c=tolower(*iter);
71 return (c=='m' || c=='h' || c=='d' || c=='w' || c=='y');
72 }
73 return true;
74}
75
76
77unsigned int ZoneParserTNG::makeTTLFromZone(const string& str)
125e4840
BH
78{
79 if(str.empty())
80 return 0;
81
82 unsigned int val=atoi(str.c_str());
83 char lc=toupper(str[str.length()-1]);
84 if(!isdigit(lc))
85 switch(lc) {
38e655b6
BH
86 case 'M':
87 val*=60; // minutes, not months!
88 break;
125e4840
BH
89 case 'H':
90 val*=3600;
91 break;
92 case 'D':
93 val*=3600*24;
94 break;
95 case 'W':
96 val*=3600*24*7;
97 break;
125e4840
BH
98 case 'Y': // ? :-)
99 val*=3600*24*365;
100 break;
cfe397d5 101
125e4840 102 default:
cfe397d5
BH
103 throw ZoneParserTNG::exception("Unable to parse time specification '"+str+"' on line "+
104 lexical_cast<string>(d_filestates.top().d_lineno)+" of file '"+
105 d_filestates.top().d_filename+"'");
125e4840
BH
106 }
107 return val;
108}
109
2e83ba09
BH
110bool ZoneParserTNG::getTemplateLine()
111{
b8c3ea84 112 if(d_templateparts.empty() || d_templatecounter > d_templatestop) // no template, or done with
2e83ba09
BH
113 return false;
114
115 string retline;
116 for(parts_t::const_iterator iter = d_templateparts.begin() ; iter != d_templateparts.end(); ++iter) {
117 if(iter != d_templateparts.begin())
118 retline+=" ";
119
120 string part=makeString(d_templateline, *iter);
121
122 /* a part can contain a 'naked' $, an escaped $ (\$), or ${offset,width,radix}, with width defaulting to 0,
123 and radix beging 'd', 'o', 'x' or 'X', defaulting to 'd'.
124
125 The width is zero-padded, so if the counter is at 1, the offset is 15, with is 3, and the radix is 'x',
126 output will be '010', from the input of ${15,3,x}
127 */
128
129 string outpart;
130 outpart.reserve(part.size()+5);
131 bool inescape=false;
132
133 for(string::size_type pos = 0; pos < part.size() ; ++pos) {
134 char c=part[pos];
135 if(inescape) {
136 outpart.append(1, c);
137 inescape=false;
138 continue;
139 }
140
141 if(part[pos]=='\\') {
142 inescape=true;
143 continue;
144 }
145 if(c=='$') {
146 if(pos + 1 == part.size() || part[pos+1]!='{') { // a trailing $, or not followed by {
147 outpart.append(lexical_cast<string>(d_templatecounter));
148 continue;
149 }
150
151 // need to deal with { case
152
153 pos+=2;
154 string::size_type startPos=pos;
155 for(; pos < part.size() && part[pos]!='}' ; ++pos)
156 ;
157
158 if(pos == part.size()) // partial spec
159 break;
160
161 // we are on the '}'
162
163 string spec(part.c_str() + startPos, part.c_str() + pos);
164 int offset=0, width=0;
165 char radix='d';
166 sscanf(spec.c_str(), "%d,%d,%c", &offset, &width, &radix); // parse format specifier
167
168 char format[12];
169 snprintf(format, sizeof(format) - 1, "%%0%d%c", width, radix); // make into printf-style format
170
171 char tmp[80];
172 snprintf(tmp, sizeof(tmp)-1, format, d_templatecounter + offset); // and do the actual printing
173 outpart+=tmp;
174 }
175 else
176 outpart.append(1, c);
177 }
178 retline+=outpart;
179 }
180 d_templatecounter+=d_templatestep;
181
182 d_line = retline;
183 return true;
184}
185
a09683af
BH
186void chopComment(string& line)
187{
188 string::size_type pos, len = line.length();
189 bool inQuote=false;
190 for(pos = 0 ; pos < len; ++pos) {
191 if(line[pos]=='\\')
192 pos++;
193 else if(line[pos]=='"')
194 inQuote=!inQuote;
195 else if(line[pos]==';' && !inQuote)
196 break;
197 }
198 if(pos != len)
199 line.resize(pos);
200}
201
9f0076d7
BH
202bool findAndElide(string& line, char c)
203{
204 string::size_type pos, len = line.length();
205 bool inQuote=false;
206 for(pos = 0 ; pos < len; ++pos) {
207 if(line[pos]=='\\')
208 pos++;
209 else if(line[pos]=='"')
210 inQuote=!inQuote;
211 else if(line[pos]==c && !inQuote)
212 break;
213 }
214 if(pos != len) {
215 line.erase(pos, 1);
216 return true;
217 }
218 return false;
219}
220
221
cfe397d5 222
f814d7c8
BH
223bool ZoneParserTNG::get(DNSResourceRecord& rr)
224{
225 retry:;
2e83ba09 226 if(!getTemplateLine() && !getLine())
f814d7c8 227 return false;
125e4840
BH
228
229 chomp(d_line, " \r\n\x1a");
2e83ba09
BH
230
231 parts_t parts;
125e4840
BH
232 vstringtok(parts, d_line);
233
234 if(parts.empty())
235 goto retry;
236
237 if(d_line[0]=='$') {
bf503cc0 238 string command=makeString(d_line, parts[0]);
cfe397d5
BH
239 if(iequals(command,"$TTL") && parts.size() > 1)
240 d_defaultttl=makeTTLFromZone(trim_right_copy_if(makeString(d_line, parts[1]), is_any_of(";")));
c098c6ac 241 else if(iequals(command,"$INCLUDE") && parts.size() > 1) {
da042e6e
BH
242 string fname=unquotify(makeString(d_line, parts[1]));
243 if(!fname.empty() && fname[0]!='/' && !d_reldir.empty())
244 fname=d_reldir+"/"+fname;
245 stackFile(fname);
bf503cc0 246 }
c098c6ac 247 else if(iequals(command, "$ORIGIN") && parts.size() > 1) {
2e83ba09
BH
248 d_zonename = toCanonic("", makeString(d_line, parts[1]));
249 }
c098c6ac 250 else if(iequals(command, "$GENERATE") && parts.size() > 2) {
bf503cc0
BH
251 // $GENERATE 1-127 $ CNAME $.0
252 string range=makeString(d_line, parts[1]);
2e83ba09
BH
253 d_templatestep=1;
254 d_templatestop=0;
255 sscanf(range.c_str(),"%d-%d/%d", &d_templatecounter, &d_templatestop, &d_templatestep);
256 d_templateline=d_line;
257 parts.pop_front();
258 parts.pop_front();
259
260 d_templateparts=parts;
261 goto retry;
bf503cc0 262 }
125e4840 263 else
cfe397d5
BH
264 throw exception("Can't parse zone line '"+d_line+"' on line "+lexical_cast<string>(d_filestates.top().d_lineno)+
265 " of file '"+d_filestates.top().d_filename);
f814d7c8 266 goto retry;
f814d7c8 267 }
125e4840 268
125e4840
BH
269 if(isspace(d_line[0]))
270 rr.qname=d_prevqname;
271 else {
272 rr.qname=makeString(d_line, parts[0]);
273 parts.pop_front();
274 if(rr.qname.empty() || rr.qname[0]==';')
275 goto retry;
276 }
277 if(rr.qname=="@")
278 rr.qname=d_zonename;
279 else if(!isCanonical(rr.qname)) {
280 rr.qname.append(1,'.');
281 rr.qname.append(d_zonename);
282 }
283 d_prevqname=rr.qname;
284
285 if(parts.empty())
286 throw exception("Line with too little parts");
287
125e4840 288 string nextpart;
f814d7c8 289
125e4840
BH
290 rr.ttl=d_defaultttl;
291 bool haveTTL=0, haveQTYPE=0;
292 pair<string::size_type, string::size_type> range;
293
294 while(!parts.empty()) {
295 range=parts.front();
296 parts.pop_front();
297 nextpart=makeString(d_line, range);
298 if(nextpart.empty())
299 break;
300
301 if(nextpart.find(';')!=string::npos)
302 break;
303
304 // cout<<"Next part: '"<<nextpart<<"'"<<endl;
305
705f31ae 306 if(!Utility::strcasecmp(nextpart.c_str(), "IN")) {
125e4840
BH
307 // cout<<"Ignoring 'IN'\n";
308 continue;
309 }
cfe397d5 310 if(!haveTTL && !haveQTYPE && isTimeSpec(nextpart)) {
125e4840
BH
311 rr.ttl=makeTTLFromZone(nextpart);
312 haveTTL=true;
313 // cout<<"ttl is probably: "<<rr.ttl<<endl;
314 continue;
315 }
316 if(haveQTYPE)
317 break;
318
319 try {
320 rr.qtype=DNSRecordContent::TypeToNumber(nextpart);
321 // cout<<"Got qtype ("<<rr.qtype.getCode()<<")\n";
322 haveQTYPE=1;
323 continue;
324 }
325 catch(...) {
cfe397d5
BH
326 throw runtime_error("Parsing zone content on line "+
327 lexical_cast<string>(d_filestates.top().d_lineno)+
328 " of file '"+d_filestates.top().d_filename+"': '"+nextpart+
329 "' doesn't look like a qtype, stopping loop");
125e4840
BH
330 }
331 }
332 if(!haveQTYPE)
333 throw exception("Malformed line '"+d_line+"'");
334
335 rr.content=d_line.substr(range.first);
336
a09683af 337 chopComment(rr.content);
62c821cd 338 trim(rr.content);
849fde0b 339
b33702d5
BH
340 if(equals(rr.content, "@"))
341 rr.content=d_zonename;
342
9f0076d7
BH
343 if(findAndElide(rr.content, '(')) { // have found a ( and elided it
344 if(!findAndElide(rr.content, ')')) {
345 while(getLine()) {
346 chomp(d_line,"\r\n ");
347 chopComment(d_line);
125e4840 348 trim(d_line);
9f0076d7
BH
349
350 bool ended = findAndElide(d_line, ')');
125e4840 351 rr.content+=" "+d_line;
9f0076d7
BH
352 if(ended)
353 break;
125e4840 354 }
125e4840
BH
355 }
356 }
9f0076d7 357
125e4840
BH
358 vector<string> soaparts;
359 switch(rr.qtype.getCode()) {
360 case QType::MX:
361 case QType::NS:
362 case QType::CNAME:
363 case QType::PTR:
364 case QType::SRV:
37f47031 365 case QType::AFSDB:
125e4840
BH
366 rr.content=toCanonic(d_zonename, rr.content);
367 break;
368
369 case QType::SOA:
370 stringtok(soaparts, rr.content);
371 if(soaparts.size() > 1) {
372 soaparts[0]=toCanonic(d_zonename, soaparts[0]);
373 soaparts[1]=toCanonic(d_zonename, soaparts[1]);
374 }
375 rr.content.clear();
376 for(string::size_type n = 0; n < soaparts.size(); ++n) {
377 if(n)
378 rr.content.append(1,' ');
82cc6877
BH
379 if(n > 1)
380 rr.content+=lexical_cast<string>(makeTTLFromZone(soaparts[n]));
381 else
382 rr.content+=soaparts[n];
125e4840 383 }
38e655b6 384 break;
125e4840
BH
385 default:;
386 }
387
388 rr.d_place=DNSResourceRecord::ANSWER;
f814d7c8
BH
389 return true;
390}
391
392bool ZoneParserTNG::getLine()
393{
cfe397d5 394 while(!d_filestates.empty()) {
bf503cc0 395 char buffer[1024];
cfe397d5
BH
396 if(fgets(buffer, 1024, d_filestates.top().d_fp)) {
397 d_filestates.top().d_lineno++;
bf503cc0
BH
398 d_line=buffer;
399 return true;
400 }
cfe397d5
BH
401 fclose(d_filestates.top().d_fp);
402 d_filestates.pop();
f814d7c8
BH
403 }
404 return false;
405}