]>
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_zonedata(zonedata
), d_defaultttl(3600),
50 d_templatecounter(0), d_templatestop(0), d_templatestep(0),
51 d_havedollarttl(false), d_fromfile(false)
53 d_zonedataline
= d_zonedata
.begin();
56 void ZoneParserTNG::stackFile(const std::string
& fname
)
58 FILE *fp
=fopen(fname
.c_str(), "r");
60 std::error_code
ec (errno
,std::generic_category());
61 throw std::system_error(ec
, "Unable to open file '"+fname
+"': "+stringerror());
64 filestate
fs(fp
, fname
);
65 d_filestates
.push(fs
);
69 ZoneParserTNG::~ZoneParserTNG()
71 while(!d_filestates
.empty()) {
72 fclose(d_filestates
.top().d_fp
);
77 static string
makeString(const string
& line
, const pair
<string::size_type
, string::size_type
>& range
)
79 return string(line
.c_str() + range
.first
, range
.second
- range
.first
);
82 static bool isTimeSpec(const string
& nextpart
)
86 for(string::const_iterator iter
= nextpart
.begin(); iter
!= nextpart
.end(); ++iter
) {
89 if(iter
+1 != nextpart
.end())
91 char c
=tolower(*iter
);
92 return (c
=='s' || c
=='m' || c
=='h' || c
=='d' || c
=='w' || c
=='y');
98 unsigned int ZoneParserTNG::makeTTLFromZone(const string
& str
)
107 catch (const std::out_of_range
& oor
) {
108 throw PDNSException("Unable to parse time specification '"+str
+"' "+getLineOfFile());
111 char lc
=dns_tolower(str
[str
.length()-1]);
117 val
*=60; // minutes, not months!
133 throw PDNSException("Unable to parse time specification '"+str
+"' "+getLineOfFile());
138 bool ZoneParserTNG::getTemplateLine()
140 if(d_templateparts
.empty() || d_templatecounter
> d_templatestop
) // no template, or done with
144 for(parts_t::const_iterator iter
= d_templateparts
.begin() ; iter
!= d_templateparts
.end(); ++iter
) {
145 if(iter
!= d_templateparts
.begin())
148 string part
=makeString(d_templateline
, *iter
);
150 /* a part can contain a 'naked' $, an escaped $ (\$), or ${offset,width,radix}, with width defaulting to 0,
151 and radix being 'd', 'o', 'x' or 'X', defaulting to 'd'.
153 The width is zero-padded, so if the counter is at 1, the offset is 15, with is 3, and the radix is 'x',
154 output will be '010', from the input of ${15,3,x}
158 outpart
.reserve(part
.size()+5);
161 for(string::size_type pos
= 0; pos
< part
.size() ; ++pos
) {
164 outpart
.append(1, c
);
169 if(part
[pos
]=='\\') {
174 if(pos
+ 1 == part
.size() || part
[pos
+1]!='{') { // a trailing $, or not followed by {
175 outpart
.append(std::to_string(d_templatecounter
));
179 // need to deal with { case
182 string::size_type startPos
=pos
;
183 for(; pos
< part
.size() && part
[pos
]!='}' ; ++pos
)
186 if(pos
== part
.size()) // partial spec
191 string
spec(part
.c_str() + startPos
, part
.c_str() + pos
);
192 int offset
=0, width
=0;
194 sscanf(spec
.c_str(), "%d,%d,%c", &offset
, &width
, &radix
); // parse format specifier
197 snprintf(sformat
, sizeof(sformat
), "%%0%d%c", width
, radix
); // make into printf-style format
200 snprintf(tmp
, sizeof(tmp
), sformat
, d_templatecounter
+ offset
); // and do the actual printing
204 outpart
.append(1, c
);
208 d_templatecounter
+=d_templatestep
;
214 void chopComment(string
& line
)
216 if(line
.find(';')==string::npos
)
218 string::size_type pos
, len
= line
.length();
220 for(pos
= 0 ; pos
< len
; ++pos
) {
223 else if(line
[pos
]=='"')
225 else if(line
[pos
]==';' && !inQuote
)
232 bool findAndElide(string
& line
, char c
)
234 string::size_type pos
, len
= line
.length();
236 for(pos
= 0 ; pos
< len
; ++pos
) {
239 else if(line
[pos
]=='"')
241 else if(line
[pos
]==c
&& !inQuote
)
251 DNSName
ZoneParserTNG::getZoneName()
256 string
ZoneParserTNG::getLineOfFile()
258 if (d_zonedata
.size() > 0)
259 return "on line "+std::to_string(std::distance(d_zonedata
.begin(), d_zonedataline
))+" of given string";
261 if (d_filestates
.empty())
264 return "on line "+std::to_string(d_filestates
.top().d_lineno
)+" of file '"+d_filestates
.top().d_filename
+"'";
267 pair
<string
,int> ZoneParserTNG::getLineNumAndFile()
269 if (d_filestates
.empty())
272 return {d_filestates
.top().d_filename
, d_filestates
.top().d_lineno
};
275 bool ZoneParserTNG::get(DNSResourceRecord
& rr
, std::string
* comment
)
278 if(!getTemplateLine() && !getLine())
281 boost::trim_right_if(d_line
, is_any_of(" \t\r\n\x1a"));
284 if(comment
&& d_line
.find(';') != string::npos
)
285 *comment
= d_line
.substr(d_line
.find(';'));
287 vstringtok(parts
, d_line
);
292 if(parts
[0].first
!= parts
[0].second
&& d_line
[parts
[0].first
]==';') // line consisting of nothing but comments
296 string command
=makeString(d_line
, parts
[0]);
297 if(pdns_iequals(command
,"$TTL") && parts
.size() > 1) {
298 d_defaultttl
=makeTTLFromZone(trim_right_copy_if(makeString(d_line
, parts
[1]), is_any_of(";")));
299 d_havedollarttl
=true;
301 else if(pdns_iequals(command
,"$INCLUDE") && parts
.size() > 1 && d_fromfile
) {
302 string fname
=unquotify(makeString(d_line
, parts
[1]));
303 if(!fname
.empty() && fname
[0]!='/' && !d_reldir
.empty())
304 fname
=d_reldir
+"/"+fname
;
307 else if(pdns_iequals(command
, "$ORIGIN") && parts
.size() > 1) {
308 d_zonename
= DNSName(makeString(d_line
, parts
[1]));
310 else if(pdns_iequals(command
, "$GENERATE") && parts
.size() > 2) {
311 // $GENERATE 1-127 $ CNAME $.0
312 string range
=makeString(d_line
, parts
[1]);
315 sscanf(range
.c_str(),"%u-%u/%u", &d_templatecounter
, &d_templatestop
, &d_templatestep
);
316 d_templateline
=d_line
;
320 d_templateparts
=parts
;
324 throw exception("Can't parse zone line '"+d_line
+"' "+getLineOfFile());
328 bool prevqname
=false;
329 string qname
= makeString(d_line
, parts
[0]); // Don't use DNSName here!
330 if(dns_isspace(d_line
[0])) {
331 rr
.qname
=d_prevqname
;
334 rr
.qname
=DNSName(qname
);
336 if(qname
.empty() || qname
[0]==';')
341 else if(!prevqname
&& !isCanonical(qname
))
342 rr
.qname
+= d_zonename
;
343 d_prevqname
=rr
.qname
;
346 throw exception("Line with too little parts "+getLineOfFile());
351 bool haveTTL
=0, haveQTYPE
=0;
352 pair
<string::size_type
, string::size_type
> range
;
354 while(!parts
.empty()) {
357 nextpart
=makeString(d_line
, range
);
361 if(nextpart
.find(';')!=string::npos
) {
365 // cout<<"Next part: '"<<nextpart<<"'"<<endl;
367 if(pdns_iequals(nextpart
, g_INstr
)) {
368 // cout<<"Ignoring 'IN'\n";
371 if(!haveTTL
&& !haveQTYPE
&& isTimeSpec(nextpart
)) {
372 rr
.ttl
=makeTTLFromZone(nextpart
);
374 d_defaultttl
= rr
.ttl
;
376 // cout<<"ttl is probably: "<<rr.ttl<<endl;
383 rr
.qtype
=DNSRecordContent::TypeToNumber(nextpart
);
384 // cout<<"Got qtype ("<<rr.qtype.getCode()<<")\n";
389 throw runtime_error("Parsing zone content "+getLineOfFile()+
391 "' doesn't look like a qtype, stopping loop");
395 throw exception("Malformed line "+getLineOfFile()+": '"+d_line
+"'");
397 // rr.content=d_line.substr(range.first);
398 rr
.content
.assign(d_line
, range
.first
, string::npos
);
399 chopComment(rr
.content
);
400 trim_if(rr
.content
, is_any_of(" \r\n\t\x1a"));
402 if(rr
.content
.size()==1 && rr
.content
[0]=='@')
403 rr
.content
=d_zonename
.toString();
405 if(findAndElide(rr
.content
, '(')) { // have found a ( and elided it
406 if(!findAndElide(rr
.content
, ')')) {
412 bool ended
= findAndElide(d_line
, ')');
413 rr
.content
+=" "+d_line
;
419 trim_if(rr
.content
, is_any_of(" \r\n\t\x1a"));
421 vector
<string
> recparts
;
422 switch(rr
.qtype
.getCode()) {
424 stringtok(recparts
, rr
.content
);
425 if(recparts
.size()==2) {
426 if (recparts
[1]!=".") {
428 recparts
[1] = toCanonic(d_zonename
, recparts
[1]).toStringRootDot();
429 } catch (std::exception
&e
) {
430 throw PDNSException("Error in record '" + rr
.qname
.toLogString() + " " + rr
.qtype
.getName() + "': " + e
.what());
433 rr
.content
=recparts
[0]+" "+recparts
[1];
438 stringtok(recparts
, rr
.content
);
439 if(recparts
.size()==2) {
440 recparts
[0] = toCanonic(d_zonename
, recparts
[0]).toStringRootDot();
441 recparts
[1] = toCanonic(d_zonename
, recparts
[1]).toStringRootDot();
442 rr
.content
=recparts
[0]+" "+recparts
[1];
447 stringtok(recparts
, rr
.content
);
448 if(recparts
.size()==4) {
449 if(recparts
[3]!=".") {
451 recparts
[3] = toCanonic(d_zonename
, recparts
[3]).toStringRootDot();
452 } catch (std::exception
&e
) {
453 throw PDNSException("Error in record '" + rr
.qname
.toLogString() + " " + rr
.qtype
.getName() + "': " + e
.what());
456 rr
.content
=recparts
[0]+" "+recparts
[1]+" "+recparts
[2]+" "+recparts
[3];
466 rr
.content
= toCanonic(d_zonename
, rr
.content
).toStringRootDot();
467 } catch (std::exception
&e
) {
468 throw PDNSException("Error in record '" + rr
.qname
.toLogString() + " " + rr
.qtype
.getName() + "': " + e
.what());
472 stringtok(recparts
, rr
.content
);
473 if(recparts
.size() == 2) {
475 recparts
[1]=toCanonic(d_zonename
, recparts
[1]).toStringRootDot();
476 } catch (std::exception
&e
) {
477 throw PDNSException("Error in record '" + rr
.qname
.toLogString() + " " + rr
.qtype
.getName() + "': " + e
.what());
480 throw PDNSException("AFSDB record for "+rr
.qname
.toLogString()+" invalid");
483 for(string::size_type n
= 0; n
< recparts
.size(); ++n
) {
485 rr
.content
.append(1,' ');
487 rr
.content
+=recparts
[n
];
491 stringtok(recparts
, rr
.content
);
492 if(recparts
.size() > 7)
493 throw PDNSException("SOA record contents for "+rr
.qname
.toLogString()+" contains too many parts");
494 if(recparts
.size() > 1) {
496 recparts
[0]=toCanonic(d_zonename
, recparts
[0]).toStringRootDot();
497 recparts
[1]=toCanonic(d_zonename
, recparts
[1]).toStringRootDot();
498 } catch (std::exception
&e
) {
499 throw PDNSException("Error in record '" + rr
.qname
.toLogString() + " " + rr
.qtype
.getName() + "': " + e
.what());
503 for(string::size_type n
= 0; n
< recparts
.size(); ++n
) {
505 rr
.content
.append(1,' ');
508 rr
.content
+=std::to_string(makeTTLFromZone(recparts
[n
]));
510 rr
.content
+=recparts
[n
];
519 bool ZoneParserTNG::getLine()
521 if (d_zonedata
.size() > 0) {
522 if (d_zonedataline
!= d_zonedata
.end()) {
523 d_line
= *d_zonedataline
;
529 while(!d_filestates
.empty()) {
530 if(stringfgets(d_filestates
.top().d_fp
, d_line
)) {
531 d_filestates
.top().d_lineno
++;
534 fclose(d_filestates
.top().d_fp
);