]> git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/zoneparser-tng.cc
auth 4.1 build: Switch to devtoolset 7 for el6
[thirdparty/pdns.git] / pdns / zoneparser-tng.cc
1 /*
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of version 2 of the GNU General Public License as
7 * published by the Free Software Foundation.
8 *
9 * In addition, for the avoidance of any doubt, permission is granted to
10 * link this program with OpenSSL and to (re)distribute the binaries
11 * produced as the result of such linking.
12 *
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
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21 */
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25 #include "ascii.hh"
26 #include "dnsparser.hh"
27 #include "sstuff.hh"
28 #include "misc.hh"
29 #include "dnswriter.hh"
30 #include "dnsrecords.hh"
31 #include "misc.hh"
32 #include <fstream>
33 #include "dns.hh"
34 #include "zoneparser-tng.hh"
35 #include <deque>
36 #include <boost/algorithm/string.hpp>
37 #include <system_error>
38
39 static string g_INstr("IN");
40
41 ZoneParserTNG::ZoneParserTNG(const string& fname, const DNSName& zname, const string& reldir) : d_reldir(reldir),
42 d_zonename(zname), d_defaultttl(3600),
43 d_templatecounter(0), d_templatestop(0),
44 d_templatestep(0), d_havedollarttl(false){
45 stackFile(fname);
46 }
47
48 ZoneParserTNG::ZoneParserTNG(const vector<string> zonedata, const DNSName& zname):
49 d_zonename(zname), d_defaultttl(3600),
50 d_templatecounter(0), d_templatestop(0),
51 d_templatestep(0), d_havedollarttl(false)
52 {
53 d_zonedata = zonedata;
54 d_zonedataline = d_zonedata.begin();
55 d_fromfile = false;
56 }
57
58 void ZoneParserTNG::stackFile(const std::string& fname)
59 {
60 FILE *fp=fopen(fname.c_str(), "r");
61 if(!fp) {
62 std::error_code ec (errno,std::generic_category());
63 throw std::system_error(ec, "Unable to open file '"+fname+"': "+stringerror());
64 }
65
66 filestate fs(fp, fname);
67 d_filestates.push(fs);
68 d_fromfile = true;
69 }
70
71 ZoneParserTNG::~ZoneParserTNG()
72 {
73 while(!d_filestates.empty()) {
74 fclose(d_filestates.top().d_fp);
75 d_filestates.pop();
76 }
77 }
78
79 static string makeString(const string& line, const pair<string::size_type, string::size_type>& range)
80 {
81 return string(line.c_str() + range.first, range.second - range.first);
82 }
83
84 static bool isTimeSpec(const string& nextpart)
85 {
86 if(nextpart.empty())
87 return false;
88 for(string::const_iterator iter = nextpart.begin(); iter != nextpart.end(); ++iter) {
89 if(isdigit(*iter))
90 continue;
91 if(iter+1 != nextpart.end())
92 return false;
93 char c=tolower(*iter);
94 return (c=='s' || c=='m' || c=='h' || c=='d' || c=='w' || c=='y');
95 }
96 return true;
97 }
98
99
100 unsigned int ZoneParserTNG::makeTTLFromZone(const string& str)
101 {
102 if(str.empty())
103 return 0;
104
105 unsigned int val;
106 try {
107 val=pdns_stou(str);
108 }
109 catch (const std::out_of_range& oor) {
110 throw PDNSException("Unable to parse time specification '"+str+"' "+getLineOfFile());
111 }
112
113 char lc=dns_tolower(str[str.length()-1]);
114 if(!isdigit(lc))
115 switch(lc) {
116 case 's':
117 break;
118 case 'm':
119 val*=60; // minutes, not months!
120 break;
121 case 'h':
122 val*=3600;
123 break;
124 case 'd':
125 val*=3600*24;
126 break;
127 case 'w':
128 val*=3600*24*7;
129 break;
130 case 'y': // ? :-)
131 val*=3600*24*365;
132 break;
133
134 default:
135 throw PDNSException("Unable to parse time specification '"+str+"' "+getLineOfFile());
136 }
137 return val;
138 }
139
140 bool ZoneParserTNG::getTemplateLine()
141 {
142 if(d_templateparts.empty() || d_templatecounter > d_templatestop) // no template, or done with
143 return false;
144
145 string retline;
146 for(parts_t::const_iterator iter = d_templateparts.begin() ; iter != d_templateparts.end(); ++iter) {
147 if(iter != d_templateparts.begin())
148 retline+=" ";
149
150 string part=makeString(d_templateline, *iter);
151
152 /* a part can contain a 'naked' $, an escaped $ (\$), or ${offset,width,radix}, with width defaulting to 0,
153 and radix being 'd', 'o', 'x' or 'X', defaulting to 'd'.
154
155 The width is zero-padded, so if the counter is at 1, the offset is 15, with is 3, and the radix is 'x',
156 output will be '010', from the input of ${15,3,x}
157 */
158
159 string outpart;
160 outpart.reserve(part.size()+5);
161 bool inescape=false;
162
163 for(string::size_type pos = 0; pos < part.size() ; ++pos) {
164 char c=part[pos];
165 if(inescape) {
166 outpart.append(1, c);
167 inescape=false;
168 continue;
169 }
170
171 if(part[pos]=='\\') {
172 inescape=true;
173 continue;
174 }
175 if(c=='$') {
176 if(pos + 1 == part.size() || part[pos+1]!='{') { // a trailing $, or not followed by {
177 outpart.append(std::to_string(d_templatecounter));
178 continue;
179 }
180
181 // need to deal with { case
182
183 pos+=2;
184 string::size_type startPos=pos;
185 for(; pos < part.size() && part[pos]!='}' ; ++pos)
186 ;
187
188 if(pos == part.size()) // partial spec
189 break;
190
191 // we are on the '}'
192
193 string spec(part.c_str() + startPos, part.c_str() + pos);
194 int offset=0, width=0;
195 char radix='d';
196 sscanf(spec.c_str(), "%d,%d,%c", &offset, &width, &radix); // parse format specifier
197
198 char sformat[12];
199 snprintf(sformat, sizeof(sformat) - 1, "%%0%d%c", width, radix); // make into printf-style format
200
201 char tmp[80];
202 snprintf(tmp, sizeof(tmp)-1, sformat, d_templatecounter + offset); // and do the actual printing
203 outpart+=tmp;
204 }
205 else
206 outpart.append(1, c);
207 }
208 retline+=outpart;
209 }
210 d_templatecounter+=d_templatestep;
211
212 d_line = retline;
213 return true;
214 }
215
216 void chopComment(string& line)
217 {
218 if(line.find(';')==string::npos)
219 return;
220 string::size_type pos, len = line.length();
221 bool inQuote=false;
222 for(pos = 0 ; pos < len; ++pos) {
223 if(line[pos]=='\\')
224 pos++;
225 else if(line[pos]=='"')
226 inQuote=!inQuote;
227 else if(line[pos]==';' && !inQuote)
228 break;
229 }
230 if(pos != len)
231 line.resize(pos);
232 }
233
234 bool findAndElide(string& line, char c)
235 {
236 string::size_type pos, len = line.length();
237 bool inQuote=false;
238 for(pos = 0 ; pos < len; ++pos) {
239 if(line[pos]=='\\')
240 pos++;
241 else if(line[pos]=='"')
242 inQuote=!inQuote;
243 else if(line[pos]==c && !inQuote)
244 break;
245 }
246 if(pos != len) {
247 line.erase(pos, 1);
248 return true;
249 }
250 return false;
251 }
252
253 DNSName ZoneParserTNG::getZoneName()
254 {
255 return d_zonename;
256 }
257
258 string ZoneParserTNG::getLineOfFile()
259 {
260 if (d_zonedata.size() > 0)
261 return "on line "+std::to_string(std::distance(d_zonedata.begin(), d_zonedataline))+" of given string";
262
263 if (d_filestates.empty())
264 return "";
265
266 return "on line "+std::to_string(d_filestates.top().d_lineno)+" of file '"+d_filestates.top().d_filename+"'";
267 }
268
269 pair<string,int> ZoneParserTNG::getLineNumAndFile()
270 {
271 if (d_filestates.empty())
272 return {"", 0};
273 else
274 return {d_filestates.top().d_filename, d_filestates.top().d_lineno};
275 }
276
277 bool ZoneParserTNG::get(DNSResourceRecord& rr, std::string* comment)
278 {
279 retry:;
280 if(!getTemplateLine() && !getLine())
281 return false;
282
283 boost::trim_right_if(d_line, is_any_of(" \t\r\n\x1a"));
284 if(comment)
285 comment->clear();
286 if(comment && d_line.find(';') != string::npos)
287 *comment = d_line.substr(d_line.find(';'));
288 parts_t parts;
289 vstringtok(parts, d_line);
290
291 if(parts.empty())
292 goto retry;
293
294 if(parts[0].first != parts[0].second && d_line[parts[0].first]==';') // line consisting of nothing but comments
295 goto retry;
296
297 if(d_line[0]=='$') {
298 string command=makeString(d_line, parts[0]);
299 if(pdns_iequals(command,"$TTL") && parts.size() > 1) {
300 d_defaultttl=makeTTLFromZone(trim_right_copy_if(makeString(d_line, parts[1]), is_any_of(";")));
301 d_havedollarttl=true;
302 }
303 else if(pdns_iequals(command,"$INCLUDE") && parts.size() > 1 && d_fromfile) {
304 string fname=unquotify(makeString(d_line, parts[1]));
305 if(!fname.empty() && fname[0]!='/' && !d_reldir.empty())
306 fname=d_reldir+"/"+fname;
307 stackFile(fname);
308 }
309 else if(pdns_iequals(command, "$ORIGIN") && parts.size() > 1) {
310 d_zonename = DNSName(makeString(d_line, parts[1]));
311 }
312 else if(pdns_iequals(command, "$GENERATE") && parts.size() > 2) {
313 // $GENERATE 1-127 $ CNAME $.0
314 string range=makeString(d_line, parts[1]);
315 d_templatestep=1;
316 d_templatestop=0;
317 sscanf(range.c_str(),"%d-%d/%d", &d_templatecounter, &d_templatestop, &d_templatestep);
318 d_templateline=d_line;
319 parts.pop_front();
320 parts.pop_front();
321
322 d_templateparts=parts;
323 goto retry;
324 }
325 else
326 throw exception("Can't parse zone line '"+d_line+"' "+getLineOfFile());
327 goto retry;
328 }
329
330 bool prevqname=false;
331 string qname = makeString(d_line, parts[0]); // Don't use DNSName here!
332 if(dns_isspace(d_line[0])) {
333 rr.qname=d_prevqname;
334 prevqname=true;
335 }else {
336 rr.qname=DNSName(qname);
337 parts.pop_front();
338 if(qname.empty() || qname[0]==';')
339 goto retry;
340 }
341 if(qname=="@")
342 rr.qname=d_zonename;
343 else if(!prevqname && !isCanonical(qname))
344 rr.qname += d_zonename;
345 d_prevqname=rr.qname;
346
347 if(parts.empty())
348 throw exception("Line with too little parts "+getLineOfFile());
349
350 string nextpart;
351
352 rr.ttl=d_defaultttl;
353 bool haveTTL=0, haveQTYPE=0;
354 pair<string::size_type, string::size_type> range;
355
356 while(!parts.empty()) {
357 range=parts.front();
358 parts.pop_front();
359 nextpart=makeString(d_line, range);
360 if(nextpart.empty())
361 break;
362
363 if(nextpart.find(';')!=string::npos) {
364 break;
365 }
366
367 // cout<<"Next part: '"<<nextpart<<"'"<<endl;
368
369 if(pdns_iequals(nextpart, g_INstr)) {
370 // cout<<"Ignoring 'IN'\n";
371 continue;
372 }
373 if(!haveTTL && !haveQTYPE && isTimeSpec(nextpart)) {
374 rr.ttl=makeTTLFromZone(nextpart);
375 if(!d_havedollarttl)
376 d_defaultttl = rr.ttl;
377 haveTTL=true;
378 // cout<<"ttl is probably: "<<rr.ttl<<endl;
379 continue;
380 }
381 if(haveQTYPE)
382 break;
383
384 try {
385 rr.qtype=DNSRecordContent::TypeToNumber(nextpart);
386 // cout<<"Got qtype ("<<rr.qtype.getCode()<<")\n";
387 haveQTYPE=1;
388 continue;
389 }
390 catch(...) {
391 throw runtime_error("Parsing zone content "+getLineOfFile()+
392 ": '"+nextpart+
393 "' doesn't look like a qtype, stopping loop");
394 }
395 }
396 if(!haveQTYPE)
397 throw exception("Malformed line "+getLineOfFile()+": '"+d_line+"'");
398
399 // rr.content=d_line.substr(range.first);
400 rr.content.assign(d_line, range.first, string::npos);
401 chopComment(rr.content);
402 trim_if(rr.content, is_any_of(" \r\n\t\x1a"));
403
404 if(rr.content.size()==1 && rr.content[0]=='@')
405 rr.content=d_zonename.toString();
406
407 if(findAndElide(rr.content, '(')) { // have found a ( and elided it
408 if(!findAndElide(rr.content, ')')) {
409 while(getLine()) {
410 trim_right(d_line);
411 chopComment(d_line);
412 trim(d_line);
413
414 bool ended = findAndElide(d_line, ')');
415 rr.content+=" "+d_line;
416 if(ended)
417 break;
418 }
419 }
420 }
421 trim_if(rr.content, is_any_of(" \r\n\t\x1a"));
422
423 vector<string> recparts;
424 switch(rr.qtype.getCode()) {
425 case QType::MX:
426 stringtok(recparts, rr.content);
427 if(recparts.size()==2) {
428 if (recparts[1]!=".") {
429 try {
430 recparts[1] = toCanonic(d_zonename, recparts[1]).toStringRootDot();
431 } catch (std::exception &e) {
432 throw PDNSException("Error in record '" + rr.qname.toString() + " " + rr.qtype.getName() + "': " + e.what());
433 }
434 }
435 rr.content=recparts[0]+" "+recparts[1];
436 }
437 break;
438
439 case QType::RP:
440 stringtok(recparts, rr.content);
441 if(recparts.size()==2) {
442 recparts[0] = toCanonic(d_zonename, recparts[0]).toStringRootDot();
443 recparts[1] = toCanonic(d_zonename, recparts[1]).toStringRootDot();
444 rr.content=recparts[0]+" "+recparts[1];
445 }
446 break;
447
448 case QType::SRV:
449 stringtok(recparts, rr.content);
450 if(recparts.size()==4) {
451 if(recparts[3]!=".") {
452 try {
453 recparts[3] = toCanonic(d_zonename, recparts[3]).toStringRootDot();
454 } catch (std::exception &e) {
455 throw PDNSException("Error in record '" + rr.qname.toString() + " " + rr.qtype.getName() + "': " + e.what());
456 }
457 }
458 rr.content=recparts[0]+" "+recparts[1]+" "+recparts[2]+" "+recparts[3];
459 }
460 break;
461
462
463 case QType::NS:
464 case QType::CNAME:
465 case QType::DNAME:
466 case QType::PTR:
467 try {
468 rr.content = toCanonic(d_zonename, rr.content).toStringRootDot();
469 } catch (std::exception &e) {
470 throw PDNSException("Error in record '" + rr.qname.toString() + " " + rr.qtype.getName() + "': " + e.what());
471 }
472 break;
473 case QType::AFSDB:
474 stringtok(recparts, rr.content);
475 if(recparts.size() == 2) {
476 try {
477 recparts[1]=toCanonic(d_zonename, recparts[1]).toStringRootDot();
478 } catch (std::exception &e) {
479 throw PDNSException("Error in record '" + rr.qname.toString() + " " + rr.qtype.getName() + "': " + e.what());
480 }
481 } else {
482 throw PDNSException("AFSDB record for "+rr.qname.toString()+" invalid");
483 }
484 rr.content.clear();
485 for(string::size_type n = 0; n < recparts.size(); ++n) {
486 if(n)
487 rr.content.append(1,' ');
488
489 rr.content+=recparts[n];
490 }
491 break;
492 case QType::SOA:
493 stringtok(recparts, rr.content);
494 if(recparts.size() > 7)
495 throw PDNSException("SOA record contents for "+rr.qname.toString()+" contains too many parts");
496 if(recparts.size() > 1) {
497 try {
498 recparts[0]=toCanonic(d_zonename, recparts[0]).toStringRootDot();
499 recparts[1]=toCanonic(d_zonename, recparts[1]).toStringRootDot();
500 } catch (std::exception &e) {
501 throw PDNSException("Error in record '" + rr.qname.toString() + " " + rr.qtype.getName() + "': " + e.what());
502 }
503 }
504 rr.content.clear();
505 for(string::size_type n = 0; n < recparts.size(); ++n) {
506 if(n)
507 rr.content.append(1,' ');
508
509 if(n > 1)
510 rr.content+=std::to_string(makeTTLFromZone(recparts[n]));
511 else
512 rr.content+=recparts[n];
513 }
514 break;
515 default:;
516 }
517 return true;
518 }
519
520
521 bool ZoneParserTNG::getLine()
522 {
523 if (d_zonedata.size() > 0) {
524 if (d_zonedataline != d_zonedata.end()) {
525 d_line = *d_zonedataline;
526 d_zonedataline++;
527 return true;
528 }
529 return false;
530 }
531 while(!d_filestates.empty()) {
532 if(stringfgets(d_filestates.top().d_fp, d_line)) {
533 d_filestates.top().d_lineno++;
534 return true;
535 }
536 fclose(d_filestates.top().d_fp);
537 d_filestates.pop();
538 }
539 return false;
540 }