]>
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
199 snprintf ( tmp
, sizeof ( tmp
), "%0*o" , width
, d_templatecounter
+ offset
);
202 snprintf ( tmp
, sizeof ( tmp
), "%0*x" , width
, d_templatecounter
+ offset
);
205 snprintf ( tmp
, sizeof ( tmp
), "%0*X" , width
, d_templatecounter
+ offset
);
209 snprintf ( tmp
, sizeof ( tmp
), "%0*d" , width
, d_templatecounter
+ offset
);
215 outpart
. append ( 1 , c
);
219 d_templatecounter
+= d_templatestep
;
225 static void chopComment ( string
& line
)
227 if ( line
. find ( ';' )== string :: npos
)
229 string :: size_type pos
, len
= line
. length ();
231 for ( pos
= 0 ; pos
< len
; ++ pos
) {
234 else if ( line
[ pos
]== '"' )
236 else if ( line
[ pos
]== ';' && ! inQuote
)
243 static bool findAndElide ( string
& line
, char c
)
245 string :: size_type pos
, len
= line
. length ();
247 for ( pos
= 0 ; pos
< len
; ++ pos
) {
250 else if ( line
[ pos
]== '"' )
252 else if ( line
[ pos
]== c
&& ! inQuote
)
262 DNSName
ZoneParserTNG :: getZoneName ()
267 string
ZoneParserTNG :: getLineOfFile ()
269 if ( d_zonedata
. size () > 0 )
270 return "on line " + std :: to_string ( std :: distance ( d_zonedata
. begin (), d_zonedataline
))+ " of given string" ;
272 if ( d_filestates
. empty ())
275 return "on line " + std :: to_string ( d_filestates
. top (). d_lineno
)+ " of file '" + d_filestates
. top (). d_filename
+ "'" ;
278 pair
< string
, int > ZoneParserTNG :: getLineNumAndFile ()
280 if ( d_filestates
. empty ())
283 return { d_filestates
. top (). d_filename
, d_filestates
. top (). d_lineno
};
286 bool ZoneParserTNG :: get ( DNSResourceRecord
& rr
, std :: string
* comment
)
289 if (! getTemplateLine () && ! getLine ())
292 boost :: trim_right_if ( d_line
, is_any_of ( " \t\r\n\x1a " ));
295 if ( comment
&& d_line
. find ( ';' ) != string :: npos
)
296 * comment
= d_line
. substr ( d_line
. find ( ';' ));
299 vstringtok ( d_parts
, d_line
);
304 if ( d_parts
[ 0 ]. first
!= d_parts
[ 0 ]. second
&& d_line
[ d_parts
[ 0 ]. first
]== ';' ) // line consisting of nothing but comments
308 string command
= makeString ( d_line
, d_parts
[ 0 ]);
309 if ( pdns_iequals ( command
, "$TTL" ) && d_parts
. size () > 1 ) {
310 d_defaultttl
= makeTTLFromZone ( trim_right_copy_if ( makeString ( d_line
, d_parts
[ 1 ]), is_any_of ( ";" )));
311 d_havedollarttl
= true ;
313 else if ( pdns_iequals ( command
, "$INCLUDE" ) && d_parts
. size () > 1 && d_fromfile
) {
314 string fname
= unquotify ( makeString ( d_line
, d_parts
[ 1 ]));
315 if (! fname
. empty () && fname
[ 0 ]!= '/' && ! d_reldir
. empty ())
316 fname
= d_reldir
+ "/" + fname
;
319 else if ( pdns_iequals ( command
, "$ORIGIN" ) && d_parts
. size () > 1 ) {
320 d_zonename
= DNSName ( makeString ( d_line
, d_parts
[ 1 ]));
322 else if ( pdns_iequals ( command
, "$GENERATE" ) && d_parts
. size () > 2 ) {
323 if (! d_generateEnabled
) {
324 throw exception ( "$GENERATE is not allowed in this zone" );
326 // $GENERATE 1-127 $ CNAME $.0
327 string range
= makeString ( d_line
, d_parts
[ 1 ]);
330 sscanf ( range
. c_str (), "%u-%u/%u" , & d_templatecounter
, & d_templatestop
, & d_templatestep
);
331 if ( d_templatestep
< 1 ||
332 d_templatestop
< d_templatecounter
) {
333 throw exception ( "Invalid $GENERATE parameters" );
335 if ( d_maxGenerateSteps
!= 0 ) {
336 size_t numberOfSteps
= ( d_templatestop
- d_templatecounter
) / d_templatestep
;
337 if ( numberOfSteps
> d_maxGenerateSteps
) {
338 throw exception ( "The number of $GENERATE steps (" + std :: to_string ( numberOfSteps
) + ") is too high, the maximum is set to " + std :: to_string ( d_maxGenerateSteps
));
341 d_templateline
= d_line
;
345 d_templateparts
= d_parts
;
349 throw exception ( "Can't parse zone line '" + d_line
+ "' " + getLineOfFile ());
353 bool prevqname
= false ;
354 string qname
= makeString ( d_line
, d_parts
[ 0 ]); // Don't use DNSName here!
355 if ( dns_isspace ( d_line
[ 0 ])) {
356 rr
. qname
= d_prevqname
;
359 rr
. qname
= DNSName ( qname
);
361 if ( qname
. empty () || qname
[ 0 ]== ';' )
366 else if (! prevqname
&& ! isCanonical ( qname
))
367 rr
. qname
+= d_zonename
;
368 d_prevqname
= rr
. qname
;
371 throw exception ( "Line with too little parts " + getLineOfFile ());
376 bool haveTTL
= 0 , haveQTYPE
= 0 ;
377 pair
< string :: size_type
, string :: size_type
> range
;
379 while (! d_parts
. empty ()) {
380 range
= d_parts
. front ();
382 nextpart
= makeString ( d_line
, range
);
386 if ( nextpart
. find ( ';' )!= string :: npos
) {
390 // cout<<"Next part: '"<<nextpart<<"'"<<endl;
392 if ( pdns_iequals ( nextpart
, g_INstr
)) {
393 // cout<<"Ignoring 'IN'\n";
396 if (! haveTTL
&& ! haveQTYPE
&& isTimeSpec ( nextpart
)) {
397 rr
. ttl
= makeTTLFromZone ( nextpart
);
399 d_defaultttl
= rr
. ttl
;
401 // cout<<"ttl is probably: "<<rr.ttl<<endl;
408 rr
. qtype
= DNSRecordContent :: TypeToNumber ( nextpart
);
409 // cout<<"Got qtype ("<<rr.qtype.getCode()<<")\n";
414 throw runtime_error ( "Parsing zone content " + getLineOfFile ()+
416 "' doesn't look like a qtype, stopping loop" );
420 throw exception ( "Malformed line " + getLineOfFile ()+ ": '" + d_line
+ "'" );
422 // rr.content=d_line.substr(range.first);
423 rr
. content
. assign ( d_line
, range
. first
, string :: npos
);
424 chopComment ( rr
. content
);
425 trim_if ( rr
. content
, is_any_of ( " \r\n\t\x1a " ));
427 if ( rr
. content
. size ()== 1 && rr
. content
[ 0 ]== '@' )
428 rr
. content
= d_zonename
. toString ();
430 if ( findAndElide ( rr
. content
, '(' )) { // have found a ( and elided it
431 if (! findAndElide ( rr
. content
, ')' )) {
437 bool ended
= findAndElide ( d_line
, ')' );
438 rr
. content
+= " " + d_line
;
444 trim_if ( rr
. content
, is_any_of ( " \r\n\t\x1a " ));
446 vector
< string
> recparts
;
447 switch ( rr
. qtype
. getCode ()) {
449 stringtok ( recparts
, rr
. content
);
450 if ( recparts
. size ()== 2 ) {
451 if ( recparts
[ 1 ]!= "." ) {
453 recparts
[ 1 ] = toCanonic ( d_zonename
, recparts
[ 1 ]). toStringRootDot ();
454 } catch ( std :: exception
& e
) {
455 throw PDNSException ( "Error in record '" + rr
. qname
. toLogString () + " " + rr
. qtype
. getName () + "': " + e
. what ());
458 rr
. content
= recparts
[ 0 ]+ " " + recparts
[ 1 ];
463 stringtok ( recparts
, rr
. content
);
464 if ( recparts
. size ()== 2 ) {
465 recparts
[ 0 ] = toCanonic ( d_zonename
, recparts
[ 0 ]). toStringRootDot ();
466 recparts
[ 1 ] = toCanonic ( d_zonename
, recparts
[ 1 ]). toStringRootDot ();
467 rr
. content
= recparts
[ 0 ]+ " " + recparts
[ 1 ];
472 stringtok ( recparts
, rr
. content
);
473 if ( recparts
. size ()== 4 ) {
474 if ( recparts
[ 3 ]!= "." ) {
476 recparts
[ 3 ] = toCanonic ( d_zonename
, recparts
[ 3 ]). toStringRootDot ();
477 } catch ( std :: exception
& e
) {
478 throw PDNSException ( "Error in record '" + rr
. qname
. toLogString () + " " + rr
. qtype
. getName () + "': " + e
. what ());
481 rr
. content
= recparts
[ 0 ]+ " " + recparts
[ 1 ]+ " " + recparts
[ 2 ]+ " " + recparts
[ 3 ];
491 rr
. content
= toCanonic ( d_zonename
, rr
. content
). toStringRootDot ();
492 } catch ( std :: exception
& e
) {
493 throw PDNSException ( "Error in record '" + rr
. qname
. toLogString () + " " + rr
. qtype
. getName () + "': " + e
. what ());
497 stringtok ( recparts
, rr
. content
);
498 if ( recparts
. size () == 2 ) {
500 recparts
[ 1 ]= toCanonic ( d_zonename
, recparts
[ 1 ]). toStringRootDot ();
501 } catch ( std :: exception
& e
) {
502 throw PDNSException ( "Error in record '" + rr
. qname
. toLogString () + " " + rr
. qtype
. getName () + "': " + e
. what ());
505 throw PDNSException ( "AFSDB record for " + rr
. qname
. toLogString ()+ " invalid" );
508 for ( string :: size_type n
= 0 ; n
< recparts
. size (); ++ n
) {
510 rr
. content
. append ( 1 , ' ' );
512 rr
. content
+= recparts
[ n
];
516 stringtok ( recparts
, rr
. content
);
517 if ( recparts
. size () > 7 )
518 throw PDNSException ( "SOA record contents for " + rr
. qname
. toLogString ()+ " contains too many parts" );
519 if ( recparts
. size () > 1 ) {
521 recparts
[ 0 ]= toCanonic ( d_zonename
, recparts
[ 0 ]). toStringRootDot ();
522 recparts
[ 1 ]= toCanonic ( d_zonename
, recparts
[ 1 ]). toStringRootDot ();
523 } catch ( std :: exception
& e
) {
524 throw PDNSException ( "Error in record '" + rr
. qname
. toLogString () + " " + rr
. qtype
. getName () + "': " + e
. what ());
528 for ( string :: size_type n
= 0 ; n
< recparts
. size (); ++ n
) {
530 rr
. content
. append ( 1 , ' ' );
533 rr
. content
+= std :: to_string ( makeTTLFromZone ( recparts
[ n
]));
535 rr
. content
+= recparts
[ n
];
544 bool ZoneParserTNG :: getLine ()
546 if ( d_zonedata
. size () > 0 ) {
547 if ( d_zonedataline
!= d_zonedata
. end ()) {
548 d_line
= * d_zonedataline
;
554 while (! d_filestates
. empty ()) {
555 if ( stringfgets ( d_filestates
. top (). d_fp
, d_line
)) {
556 d_filestates
. top (). d_lineno
++;
559 fclose ( d_filestates
. top (). d_fp
);