]>
git.ipfire.org Git - thirdparty/pdns.git/blob - pdns/zoneparser-tng.cc
2 * This file is part of PowerDNS or dnsdist.
3 * Copyright -- PowerDNS.COM B.V. and its contributors
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.
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.
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.
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.
26 #include "dnsparser.hh"
29 #include "dnswriter.hh"
30 #include "dnsrecords.hh"
34 #include "zoneparser-tng.hh"
36 #include <boost/algorithm/string.hpp>
37 #include <system_error>
39 static string
g_INstr("IN");
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){
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)
53 d_zonedata
= zonedata
;
54 d_zonedataline
= d_zonedata
.begin();
58 void ZoneParserTNG::stackFile(const std::string
& fname
)
60 FILE *fp
=fopen(fname
.c_str(), "r");
62 std::error_code
ec (errno
,std::generic_category());
63 throw std::system_error(ec
, "Unable to open file '"+fname
+"': "+stringerror());
66 filestate
fs(fp
, fname
);
67 d_filestates
.push(fs
);
71 ZoneParserTNG::~ZoneParserTNG()
73 while(!d_filestates
.empty()) {
74 fclose(d_filestates
.top().d_fp
);
79 static string
makeString(const string
& line
, const pair
<string::size_type
, string::size_type
>& range
)
81 return string(line
.c_str() + range
.first
, range
.second
- range
.first
);
84 static bool isTimeSpec(const string
& nextpart
)
88 for(string::const_iterator iter
= nextpart
.begin(); iter
!= nextpart
.end(); ++iter
) {
91 if(iter
+1 != nextpart
.end())
93 char c
=tolower(*iter
);
94 return (c
=='s' || c
=='m' || c
=='h' || c
=='d' || c
=='w' || c
=='y');
100 unsigned int ZoneParserTNG::makeTTLFromZone(const string
& str
)
109 catch (const std::out_of_range
& oor
) {
110 throw PDNSException("Unable to parse time specification '"+str
+"' "+getLineOfFile());
113 char lc
=dns_tolower(str
[str
.length()-1]);
119 val
*=60; // minutes, not months!
135 throw PDNSException("Unable to parse time specification '"+str
+"' "+getLineOfFile());
140 bool ZoneParserTNG::getTemplateLine()
142 if(d_templateparts
.empty() || d_templatecounter
> d_templatestop
) // no template, or done with
146 for(parts_t::const_iterator iter
= d_templateparts
.begin() ; iter
!= d_templateparts
.end(); ++iter
) {
147 if(iter
!= d_templateparts
.begin())
150 string part
=makeString(d_templateline
, *iter
);
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'.
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}
160 outpart
.reserve(part
.size()+5);
163 for(string::size_type pos
= 0; pos
< part
.size() ; ++pos
) {
166 outpart
.append(1, c
);
171 if(part
[pos
]=='\\') {
176 if(pos
+ 1 == part
.size() || part
[pos
+1]!='{') { // a trailing $, or not followed by {
177 outpart
.append(std::to_string(d_templatecounter
));
181 // need to deal with { case
184 string::size_type startPos
=pos
;
185 for(; pos
< part
.size() && part
[pos
]!='}' ; ++pos
)
188 if(pos
== part
.size()) // partial spec
193 string
spec(part
.c_str() + startPos
, part
.c_str() + pos
);
194 int offset
=0, width
=0;
196 sscanf(spec
.c_str(), "%d,%d,%c", &offset
, &width
, &radix
); // parse format specifier
199 snprintf(sformat
, sizeof(sformat
) - 1, "%%0%d%c", width
, radix
); // make into printf-style format
202 snprintf(tmp
, sizeof(tmp
)-1, sformat
, d_templatecounter
+ offset
); // and do the actual printing
206 outpart
.append(1, c
);
210 d_templatecounter
+=d_templatestep
;
216 void chopComment(string
& line
)
218 if(line
.find(';')==string::npos
)
220 string::size_type pos
, len
= line
.length();
222 for(pos
= 0 ; pos
< len
; ++pos
) {
225 else if(line
[pos
]=='"')
227 else if(line
[pos
]==';' && !inQuote
)
234 bool findAndElide(string
& line
, char c
)
236 string::size_type pos
, len
= line
.length();
238 for(pos
= 0 ; pos
< len
; ++pos
) {
241 else if(line
[pos
]=='"')
243 else if(line
[pos
]==c
&& !inQuote
)
253 DNSName
ZoneParserTNG::getZoneName()
258 string
ZoneParserTNG::getLineOfFile()
260 if (d_zonedata
.size() > 0)
261 return "on line "+std::to_string(std::distance(d_zonedata
.begin(), d_zonedataline
))+" of given string";
263 if (d_filestates
.empty())
266 return "on line "+std::to_string(d_filestates
.top().d_lineno
)+" of file '"+d_filestates
.top().d_filename
+"'";
269 pair
<string
,int> ZoneParserTNG::getLineNumAndFile()
271 if (d_filestates
.empty())
274 return {d_filestates
.top().d_filename
, d_filestates
.top().d_lineno
};
277 bool ZoneParserTNG::get(DNSResourceRecord
& rr
, std::string
* comment
)
280 if(!getTemplateLine() && !getLine())
283 boost::trim_right_if(d_line
, is_any_of(" \t\r\n\x1a"));
286 if(comment
&& d_line
.find(';') != string::npos
)
287 *comment
= d_line
.substr(d_line
.find(';'));
289 vstringtok(parts
, d_line
);
294 if(parts
[0].first
!= parts
[0].second
&& d_line
[parts
[0].first
]==';') // line consisting of nothing but comments
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;
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
;
309 else if(pdns_iequals(command
, "$ORIGIN") && parts
.size() > 1) {
310 d_zonename
= DNSName(makeString(d_line
, parts
[1]));
312 else if(pdns_iequals(command
, "$GENERATE") && parts
.size() > 2) {
313 // $GENERATE 1-127 $ CNAME $.0
314 string range
=makeString(d_line
, parts
[1]);
317 sscanf(range
.c_str(),"%d-%d/%d", &d_templatecounter
, &d_templatestop
, &d_templatestep
);
318 d_templateline
=d_line
;
322 d_templateparts
=parts
;
326 throw exception("Can't parse zone line '"+d_line
+"' "+getLineOfFile());
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
;
336 rr
.qname
=DNSName(qname
);
338 if(qname
.empty() || qname
[0]==';')
343 else if(!prevqname
&& !isCanonical(qname
))
344 rr
.qname
+= d_zonename
;
345 d_prevqname
=rr
.qname
;
348 throw exception("Line with too little parts "+getLineOfFile());
353 bool haveTTL
=0, haveQTYPE
=0;
354 pair
<string::size_type
, string::size_type
> range
;
356 while(!parts
.empty()) {
359 nextpart
=makeString(d_line
, range
);
363 if(nextpart
.find(';')!=string::npos
) {
367 // cout<<"Next part: '"<<nextpart<<"'"<<endl;
369 if(pdns_iequals(nextpart
, g_INstr
)) {
370 // cout<<"Ignoring 'IN'\n";
373 if(!haveTTL
&& !haveQTYPE
&& isTimeSpec(nextpart
)) {
374 rr
.ttl
=makeTTLFromZone(nextpart
);
376 d_defaultttl
= rr
.ttl
;
378 // cout<<"ttl is probably: "<<rr.ttl<<endl;
385 rr
.qtype
=DNSRecordContent::TypeToNumber(nextpart
);
386 // cout<<"Got qtype ("<<rr.qtype.getCode()<<")\n";
391 throw runtime_error("Parsing zone content "+getLineOfFile()+
393 "' doesn't look like a qtype, stopping loop");
397 throw exception("Malformed line "+getLineOfFile()+": '"+d_line
+"'");
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"));
404 if(rr
.content
.size()==1 && rr
.content
[0]=='@')
405 rr
.content
=d_zonename
.toString();
407 if(findAndElide(rr
.content
, '(')) { // have found a ( and elided it
408 if(!findAndElide(rr
.content
, ')')) {
414 bool ended
= findAndElide(d_line
, ')');
415 rr
.content
+=" "+d_line
;
421 trim_if(rr
.content
, is_any_of(" \r\n\t\x1a"));
423 vector
<string
> recparts
;
424 switch(rr
.qtype
.getCode()) {
426 stringtok(recparts
, rr
.content
);
427 if(recparts
.size()==2) {
428 if (recparts
[1]!=".") {
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());
435 rr
.content
=recparts
[0]+" "+recparts
[1];
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];
449 stringtok(recparts
, rr
.content
);
450 if(recparts
.size()==4) {
451 if(recparts
[3]!=".") {
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());
458 rr
.content
=recparts
[0]+" "+recparts
[1]+" "+recparts
[2]+" "+recparts
[3];
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());
474 stringtok(recparts
, rr
.content
);
475 if(recparts
.size() == 2) {
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());
482 throw PDNSException("AFSDB record for "+rr
.qname
.toString()+" invalid");
485 for(string::size_type n
= 0; n
< recparts
.size(); ++n
) {
487 rr
.content
.append(1,' ');
489 rr
.content
+=recparts
[n
];
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) {
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());
505 for(string::size_type n
= 0; n
< recparts
.size(); ++n
) {
507 rr
.content
.append(1,' ');
510 rr
.content
+=std::to_string(makeTTLFromZone(recparts
[n
]));
512 rr
.content
+=recparts
[n
];
521 bool ZoneParserTNG::getLine()
523 if (d_zonedata
.size() > 0) {
524 if (d_zonedataline
!= d_zonedata
.end()) {
525 d_line
= *d_zonedataline
;
531 while(!d_filestates
.empty()) {
532 if(stringfgets(d_filestates
.top().d_fp
, d_line
)) {
533 d_filestates
.top().d_lineno
++;
536 fclose(d_filestates
.top().d_fp
);