]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/zoneparser-tng.cc
add NS, SOA and TXT code to the example pipe backend
[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
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
df1d406a
BH
32ZoneParserTNG::ZoneParserTNG(const string& fname, const string& zname, const string& reldir) : d_reldir(reldir),
33 d_zonename(zname), d_defaultttl(3600),
34 d_havedollarttl(false)
f814d7c8 35{
98544e13 36 d_zonename = toCanonic("", d_zonename);
bf503cc0
BH
37 stackFile(fname);
38}
39
40void ZoneParserTNG::stackFile(const std::string& fname)
41{
42 FILE *fp=fopen(fname.c_str(), "r");
43 if(!fp)
f814d7c8 44 throw runtime_error("Unable to open file '"+fname+"': "+stringerror());
cfe397d5
BH
45
46 filestate fs(fp, fname);
47 d_filestates.push(fs);
f814d7c8
BH
48}
49
50ZoneParserTNG::~ZoneParserTNG()
51{
cfe397d5
BH
52 while(!d_filestates.empty()) {
53 fclose(d_filestates.top().d_fp);
54 d_filestates.pop();
bf503cc0 55 }
f814d7c8
BH
56}
57
125e4840
BH
58static string makeString(const string& line, const pair<string::size_type, string::size_type>& range)
59{
60 return string(line.c_str() + range.first, range.second - range.first);
61}
62
cfe397d5
BH
63static bool isTimeSpec(const string& nextpart)
64{
65 if(nextpart.empty())
66 return false;
67 for(string::const_iterator iter = nextpart.begin(); iter != nextpart.end(); ++iter) {
68 if(isdigit(*iter))
69 continue;
70 if(iter+1 != nextpart.end())
71 return false;
72 char c=tolower(*iter);
73 return (c=='m' || c=='h' || c=='d' || c=='w' || c=='y');
74 }
75 return true;
76}
77
78
79unsigned int ZoneParserTNG::makeTTLFromZone(const string& str)
125e4840
BH
80{
81 if(str.empty())
82 return 0;
83
84 unsigned int val=atoi(str.c_str());
85 char lc=toupper(str[str.length()-1]);
86 if(!isdigit(lc))
87 switch(lc) {
38e655b6
BH
88 case 'M':
89 val*=60; // minutes, not months!
90 break;
125e4840
BH
91 case 'H':
92 val*=3600;
93 break;
94 case 'D':
95 val*=3600*24;
96 break;
97 case 'W':
98 val*=3600*24*7;
99 break;
125e4840
BH
100 case 'Y': // ? :-)
101 val*=3600*24*365;
102 break;
cfe397d5 103
125e4840 104 default:
d27ea394 105 throw ZoneParserTNG::exception("Unable to parse time specification '"+str+"' "+getLineOfFile());
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
d27ea394
BH
221string ZoneParserTNG::getLineOfFile()
222{
223 return "on line "+lexical_cast<string>(d_filestates.top().d_lineno)+" of file '"+d_filestates.top().d_filename+"'";
224}
cfe397d5 225
f814d7c8
BH
226bool ZoneParserTNG::get(DNSResourceRecord& rr)
227{
228 retry:;
2e83ba09 229 if(!getTemplateLine() && !getLine())
f814d7c8 230 return false;
125e4840 231
df1d406a 232 boost::trim_right_if(d_line, is_any_of(" \r\n\x1a"));
2e83ba09
BH
233
234 parts_t parts;
125e4840
BH
235 vstringtok(parts, d_line);
236
237 if(parts.empty())
238 goto retry;
239
4d2c97aa
BH
240 if(parts[0].first != parts[0].second && makeString(d_line, parts[0])[0]==';') // line consisting of nothing but comments
241 goto retry;
242
125e4840 243 if(d_line[0]=='$') {
bf503cc0 244 string command=makeString(d_line, parts[0]);
df1d406a 245 if(iequals(command,"$TTL") && parts.size() > 1) {
cfe397d5 246 d_defaultttl=makeTTLFromZone(trim_right_copy_if(makeString(d_line, parts[1]), is_any_of(";")));
df1d406a
BH
247 d_havedollarttl=true;
248 }
c098c6ac 249 else if(iequals(command,"$INCLUDE") && parts.size() > 1) {
da042e6e
BH
250 string fname=unquotify(makeString(d_line, parts[1]));
251 if(!fname.empty() && fname[0]!='/' && !d_reldir.empty())
252 fname=d_reldir+"/"+fname;
253 stackFile(fname);
bf503cc0 254 }
c098c6ac 255 else if(iequals(command, "$ORIGIN") && parts.size() > 1) {
2e83ba09
BH
256 d_zonename = toCanonic("", makeString(d_line, parts[1]));
257 }
c098c6ac 258 else if(iequals(command, "$GENERATE") && parts.size() > 2) {
bf503cc0
BH
259 // $GENERATE 1-127 $ CNAME $.0
260 string range=makeString(d_line, parts[1]);
2e83ba09
BH
261 d_templatestep=1;
262 d_templatestop=0;
263 sscanf(range.c_str(),"%d-%d/%d", &d_templatecounter, &d_templatestop, &d_templatestep);
264 d_templateline=d_line;
265 parts.pop_front();
266 parts.pop_front();
267
268 d_templateparts=parts;
269 goto retry;
bf503cc0 270 }
125e4840 271 else
d27ea394 272 throw exception("Can't parse zone line '"+d_line+"' "+getLineOfFile());
f814d7c8 273 goto retry;
f814d7c8 274 }
125e4840 275
125e4840
BH
276 if(isspace(d_line[0]))
277 rr.qname=d_prevqname;
278 else {
279 rr.qname=makeString(d_line, parts[0]);
280 parts.pop_front();
281 if(rr.qname.empty() || rr.qname[0]==';')
282 goto retry;
283 }
284 if(rr.qname=="@")
285 rr.qname=d_zonename;
286 else if(!isCanonical(rr.qname)) {
287 rr.qname.append(1,'.');
288 rr.qname.append(d_zonename);
289 }
290 d_prevqname=rr.qname;
291
292 if(parts.empty())
d27ea394 293 throw exception("Line with too little parts "+getLineOfFile());
125e4840 294
125e4840 295 string nextpart;
f814d7c8 296
125e4840
BH
297 rr.ttl=d_defaultttl;
298 bool haveTTL=0, haveQTYPE=0;
299 pair<string::size_type, string::size_type> range;
300
301 while(!parts.empty()) {
302 range=parts.front();
303 parts.pop_front();
304 nextpart=makeString(d_line, range);
305 if(nextpart.empty())
306 break;
307
308 if(nextpart.find(';')!=string::npos)
309 break;
310
311 // cout<<"Next part: '"<<nextpart<<"'"<<endl;
312
705f31ae 313 if(!Utility::strcasecmp(nextpart.c_str(), "IN")) {
125e4840
BH
314 // cout<<"Ignoring 'IN'\n";
315 continue;
316 }
cfe397d5 317 if(!haveTTL && !haveQTYPE && isTimeSpec(nextpart)) {
125e4840
BH
318 rr.ttl=makeTTLFromZone(nextpart);
319 haveTTL=true;
320 // cout<<"ttl is probably: "<<rr.ttl<<endl;
321 continue;
322 }
323 if(haveQTYPE)
324 break;
325
326 try {
327 rr.qtype=DNSRecordContent::TypeToNumber(nextpart);
328 // cout<<"Got qtype ("<<rr.qtype.getCode()<<")\n";
329 haveQTYPE=1;
330 continue;
331 }
332 catch(...) {
d27ea394
BH
333 throw runtime_error("Parsing zone content "+getLineOfFile()+
334 ": '"+nextpart+
cfe397d5 335 "' doesn't look like a qtype, stopping loop");
125e4840
BH
336 }
337 }
338 if(!haveQTYPE)
d27ea394 339 throw exception("Malformed line "+getLineOfFile()+": '"+d_line+"'");
125e4840
BH
340
341 rr.content=d_line.substr(range.first);
342
a09683af 343 chopComment(rr.content);
62c821cd 344 trim(rr.content);
849fde0b 345
b33702d5
BH
346 if(equals(rr.content, "@"))
347 rr.content=d_zonename;
348
9f0076d7
BH
349 if(findAndElide(rr.content, '(')) { // have found a ( and elided it
350 if(!findAndElide(rr.content, ')')) {
351 while(getLine()) {
df1d406a 352 trim_right(d_line);
9f0076d7 353 chopComment(d_line);
125e4840 354 trim(d_line);
9f0076d7
BH
355
356 bool ended = findAndElide(d_line, ')');
125e4840 357 rr.content+=" "+d_line;
9f0076d7
BH
358 if(ended)
359 break;
125e4840 360 }
125e4840
BH
361 }
362 }
9f0076d7 363
125e4840
BH
364 vector<string> soaparts;
365 switch(rr.qtype.getCode()) {
366 case QType::MX:
367 case QType::NS:
368 case QType::CNAME:
369 case QType::PTR:
370 case QType::SRV:
37f47031 371 case QType::AFSDB:
125e4840
BH
372 rr.content=toCanonic(d_zonename, rr.content);
373 break;
374
375 case QType::SOA:
376 stringtok(soaparts, rr.content);
377 if(soaparts.size() > 1) {
378 soaparts[0]=toCanonic(d_zonename, soaparts[0]);
379 soaparts[1]=toCanonic(d_zonename, soaparts[1]);
380 }
381 rr.content.clear();
382 for(string::size_type n = 0; n < soaparts.size(); ++n) {
383 if(n)
384 rr.content.append(1,' ');
c9f935fa 385
82cc6877
BH
386 if(n > 1)
387 rr.content+=lexical_cast<string>(makeTTLFromZone(soaparts[n]));
388 else
389 rr.content+=soaparts[n];
c9f935fa 390
df1d406a 391 if(n==6 && !d_havedollarttl)
c9f935fa 392 d_defaultttl=makeTTLFromZone(soaparts[n]);
125e4840 393 }
38e655b6 394 break;
125e4840
BH
395 default:;
396 }
397
398 rr.d_place=DNSResourceRecord::ANSWER;
f814d7c8
BH
399 return true;
400}
401
402bool ZoneParserTNG::getLine()
403{
cfe397d5 404 while(!d_filestates.empty()) {
bf503cc0 405 char buffer[1024];
cfe397d5
BH
406 if(fgets(buffer, 1024, d_filestates.top().d_fp)) {
407 d_filestates.top().d_lineno++;
bf503cc0
BH
408 d_line=buffer;
409 return true;
410 }
cfe397d5
BH
411 fclose(d_filestates.top().d_fp);
412 d_filestates.pop();
f814d7c8
BH
413 }
414 return false;
415}