#!/usr/local/bin/perl -w
######################################################################
#
# DNS/Config/File/Nsd.pm
#
# $Id: Nsd.pm,v 1.3 2003/02/16 10:15:32 awolf Exp $
# $Revision: 1.3 $
# $Author: awolf $
# $Date: 2003/02/16 10:15:32 $
#
# Copyright (C)2003 Bruce Campbell. All rights reserved.
# Base Class (Bind9) (C)2001-2003 Andy Wolf. All rights reserved.
#
# This library is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#
######################################################################

package DNS::Config::File::Nsd;

no warnings 'portable';
use 5.6.0;
use strict;
use warnings;

use vars qw(@ISA);

use DNS::Config;
use DNS::Config::Server;
use DNS::Config::Statement;

@ISA = qw(DNS::Config::File);

my $VERSION   = '0.66';
my $REVISION  = sprintf("%d.%02d", q$Revision: 1.3 $ =~ /(\d+)\.(\d+)/);

# FILE is the nsd.zones file.
sub new {
	my($pkg, $file, $config) = @_;
	my $class = ref($pkg) || $pkg;

	my $self = {
		'FILE' => $file
	};

	$self->{'CONFIG'} = $config if($config);
	
	bless $self, $class;
	
	return $self;
}

# NSD has an additional config file for nsdc.  Return the filename
# if it has been defined.
sub nsdc {
	my( $self, $file ) = (@_);

	if( defined( $file ) ){
		$self->{'NSDC'} = $file;
	}

	return( $self->{'NSDC'} );
}

# NSD has a directory for TSIG keys.  Return the directory if it
# has been defined.
sub nsdkeysdir {
        my( $self, $dir ) = (@_);

        if( defined( $dir ) ){
                $self->{'NSDKEYSDIR'} = $dir;
        }

        return( $self->{'NSDKEYSDIR'} );
}


sub do_gettsig {
	my $self = shift;
	my $tsigdir = shift;
	my $keyname = shift;

	# This really should be a subroutine in DNS::Config::Statement::Keys.
	my %algs = (
			"157",	"hmac-md5",
			);

	# Get what it is.
	my $t_type = undef;
	my $t_zone = undef;
	my $t_ip = undef;
	if( $keyname =~ /^\s*(zi)\-(\S+)\-([^\-]+)\s*$/i ){
		$t_type = lc( $1 );
		$t_zone = $2;
		$t_ip = $3;
	}elsif( $keyname =~ /^\s*(ip|zo)\-(\S+)\s*$/i ){
		# either zo-$zone or ip-$ip
		$t_type = lc($1);
		$t_zone = $2;
		if( $t_type eq "ip" ){
			$t_ip = $t_zone;
			$t_zone = undef;
		}
	}else{
		$t_type = "unknown";
	}

	# We return a string (or maybe not) that should be inserted into
	# the main stream.  Usually we define a key, or a server statement,
	# but only if we haven't already done so for this key or server.
	my $retmain = undef;
	my $retkey = undef;

	if( ! defined( $self->{'TSIG'} ) ){
		%{$self->{'TSIG'}} = ();
	}

	if( ! defined( ${$self->{'TSIG'}}{$keyname} ) ){
		# We need to read in the file.
		my $t_file = "$tsigdir" . "/" . "$keyname" . ".tsiginfo";
		if( -f "$t_file" ){
			#
			if( open( TSIGINPUT, "$t_file" ) ){
				# Server IP address.
				my $t_addr = <TSIGINPUT>;
				my $t_name = <TSIGINPUT>;
				my $t_alg = <TSIGINPUT>;
				my $t_sec = undef;
				while( my $line = <TSIGINPUT> ){
					chomp( $line );
					$t_sec .= $line;
				}
				close( TSIGINPUT );

				chomp( $t_addr );
				chomp( $t_name );
				chomp( $t_alg );
				chomp( $t_sec );

				# print STDERR "Blot - $t_addr $t_name $t_alg $t_sec\n";

				# Store it here, and elsewhere.
				${$self->{'TSIG'}}{$keyname}{'ip'} = $t_addr;
				${$self->{'TSIG'}}{$keyname}{'name'} = $t_name;
				${$self->{'TSIG'}}{$keyname}{'algorithm-num'} = $t_alg;
				if( defined( $algs{$t_alg} ) ){
					${$self->{'TSIG'}}{$keyname}{'algorithm'} = $algs{$t_alg};
				}else{
					${$self->{'TSIG'}}{$keyname}{'algorithm'} = $t_alg;
				}
				
				${$self->{'TSIG'}}{$keyname}{'secret'} = $t_sec;
				${$self->{'TSIG'}}{$keyname}{'realname'} = "___$t_name";
				
			}
		}
	}

	# See if we've got this one.
	if( defined( ${$self->{'TSIG'}}{$keyname} ) ){
		# We've got it.  Whats the actual keyname?
		my $t_real = ${$self->{'TSIG'}}{$keyname}{'realname'};

		# If its not there, copy it.
		if( ! defined( ${$self->{'TSIG'}}{$t_real} ) ){
			${$self->{'TSIG'}}{$t_real}{'ip'} = ${$self->{'TSIG'}}{$keyname}{'ip'};
			${$self->{'TSIG'}}{$t_real}{'name'} = ${$self->{'TSIG'}}{$keyname}{'name'};
			${$self->{'TSIG'}}{$t_real}{'secret'} = ${$self->{'TSIG'}}{$keyname}{'secret'};
			${$self->{'TSIG'}}{$t_real}{'algorithm'} = ${$self->{'TSIG'}}{$keyname}{'algorithm'};
			${$self->{'TSIG'}}{$t_real}{'algorithm-num'} = ${$self->{'TSIG'}}{$keyname}{'algorithm-num'};
		}

		# Whats the name of this one?
		$retkey = ${$self->{'TSIG'}}{$t_real}{'name'};

		# Do we need to define this one?
		if( ! defined( ${$self->{'TSIG'}}{$t_real}{'done'} ) ){
			# We do need to define it.
			$retmain .= " key " . ${$self->{'TSIG'}}{$t_real}{'name'} . " {";
			$retmain .= " algorithm " . ${$self->{'TSIG'}}{$t_real}{'algorithm'} . ";";
			$retmain .= " secret \"" . ${$self->{'TSIG'}}{$t_real}{'secret'} . "\";";
			$retmain .= " };";

			# Say that we've defined this one.
			${$self->{'TSIG'}}{$t_real}{'done'}++;
		}

		if( $t_type eq "ip" && ! defined( ${$self->{'TSIG'}}{$keyname}{'done'} ) && defined( $t_ip ) ){
			# We now need to use this key with the server.
			$retmain .= " server $t_ip { keys { " . ${$self->{'TSIG'}}{$keyname}{'name'} . "; }; };";
			${$self->{'TSIG'}}{$keyname}{'done'}++;

			# print STDERR "Foo - $retmain\n";
		}
	}


	return( $retmain, $retkey );

}

sub parse {
	my($self, $file) = @_;

	$file = $file || $self->{'FILE'};

	my @lines = $self->read($file);

	my @nsdc = $self->read( $self->nsdc() );

	# This space left blank for any includes.
	
	return undef unless(scalar @lines);

	$self->{'CONFIG'} = new DNS::Config() if(!$self->{'CONFIG'});
	
	my $result;

	my %nsdc_h = (
			"namedxfer",	"CP:named-xfer VAL;",
			"nsdzonesdir",	"CP:directory VAL;",
			"nsdflags",	"SPECIAL",
			"nsdkeysdir",	"SPECIAL",
			);

	$result .= " options {";

	# Loop through the nsdc lines.
	my $nsdkeysdir = undef;

	for my $line (@nsdc) {

		next unless( $line =~ /^\s*(\S+)\s*=\s*\"(.*)\"\s*(\#.*)?$/ );
		my $name = lc( $1 );
		my $fill = $2;

		next unless( defined( $nsdc_h{$name} ) );

		my $tval = $nsdc_h{$name};
		if( $tval =~ /^CP:(\S+.*)\s*$/ ){
			$tval = $1;
			$tval =~ s/VAL/$fill/g;
			$result .= " $tval";
		}elsif( $tval eq 'SPECIAL' && $name eq 'nsdflags' ){
			# Special processing required.
			my @tsplit = split( /\s+/, $fill );
			my $curflag = undef;
			my @addys = ();
			my $port = 53;
			foreach my $kkey( @tsplit ){
				if( $kkey =~ /^\s*\-[ap]\s*$/ ){
					$curflag = $kkey;
				}elsif( defined($curflag) ){
					if( $curflag eq '-a' ){
						push @addys, $kkey;
					}elsif( $curflag eq '-p' ){
						$port = $kkey;
					}
				}
			}


			$result .= " listen-on port $port {";
			foreach my $kkey( @addys ){
				$result .= " $kkey;";
			}

			$result .= " };";
		}elsif( $tval eq 'SPECIAL' && $name eq 'nsdkeysdir' ){
			$nsdkeysdir = $fill;
		}else{
			next;
		}

	}

	$nsdkeysdir = $self->nsdkeysdir( $nsdkeysdir );

	$result .= " };";

	# tsig stuff.  Wheee.
	my %tsigs = ();

	# Loop through the lines in nsd.zones.
	for my $line (@lines) {
	
		# replace lots of space with one space.
		$line =~ s/\s+/ /g;

		# Remove '//' style comments.
		$line =~ s/\/\/.*$//g;

		# Remove '#' style comments.
		$line =~ s/\#.*$//g;

		# nsd.zones only has lines beginning with 'zone'.
		next unless( $line =~ /^\s*zone\s+(\S+)\s+(\S+)\s*(\S*.*)\s*$/ );
		my $this_zone=$1;
		my $this_file=$2;
		my $this_rest=$3;

		# We rework the string into Bind9-style, as the code for
		# dealing with this is nice and solid.

		# Set up a temporary line first.  We may need to insert
		# stuff into the stream beforehand (With BIND, you need to
		# define keys before you use them.  By inserting stuff in
		# this stream before we use them, we hopefully stop people 
		# shooting themselves in the foot if they generate a named.conf
		# file by simply dumping the config out.

		my $tmpresult = " zone \"$this_zone\" in { file \"$this_file\";";

		my $tmptype = "master";
		if( $this_rest =~ /masters\s*((\s+(\d+\.){3,3}\d+|\s+(([0-9a-f]*:){1,15}(:[0-9a-f]+){1,15}))){1,}\s*(notify|$)/ ){
			my @tmpres3 = split( / /, $1 );
			$tmpresult .= " masters {";
			foreach my $tval ( @tmpres3 ){
				$tmpresult .= " $tval";
				if( defined( $nsdkeysdir ) ){
					if( -f $nsdkeysdir . "/ip-" . $tval . ".tsiginfo" ){
						# print STDERR "Got dir $nsdkeysdir\n";
						# we need to predefine a key.
						my ($tmpstr, $keyname) = $self->do_gettsig( $nsdkeysdir, "ip-$tval" );
						$result .= $tmpstr if( defined( $tmpstr ) );
					}
					if( -f $nsdkeysdir . "/zi-" . $this_zone . "-" . $tval . ".tsiginfo" ){
						# This key gets used for this
						# one.
						my ($tmpstr, $keyname) = $self->do_gettsig( $nsdkeysdir, "zi-$this_zone-$tval" );
						$result .= $tmpstr if( defined( $tmpstr ) );
						$tmpresult .= " key $keyname" if( defined( $keyname ) );
					}elsif( -f $nsdkeysdir . "/zo-" . $this_zone . ".tsiginfo" ){
						# This key gets used for this
						# one.
						my ($tmpstr, $keyname) = $self->do_gettsig( $nsdkeysdir, "zo-$this_zone" );
						$result .= $tmpstr if( defined( $tmpstr ) );
						$tmpresult .= " key $keyname" if( defined( $keyname ) );
					}
				}

				$tmpresult .= ";";
			}
			$tmpresult .= " };";
			$tmptype = "slave";
		}

		if( $this_rest =~ /notify\s*((\s+(\d+\.){3,3}\d+|\s+(([0-9a-f]*:){1,15}(:[0-9a-f]+){1,15}))){1,}\s*(masters|$)/ ){

			my @tmpres3 = split( / /, $1 );
			$tmpresult .= " also-notify {";
			foreach my $tval ( @tmpres3 ){
				$tmpresult .= " $tval;";
			}
			$tmpresult .= " };"
		}

		# We need to check for tsig keys now.
		if( defined( $nsdkeysdir ) ){
			if( -f $nsdkeysdir . "/zo-" . $this_zone . ".tsiginfo" ){
				my ($tmpstr, $keyname) = $self->do_gettsig( $nsdkeysdir, "zo-$this_zone" );
				$result .= $tmpstr if( defined( $tmpstr ) );
				$tmpresult .= "allow-transfer { key $keyname;};" if( defined( $keyname ) );
			}
		}
				
		# Now that we've put tsig stuff beforehand, put in the zone.
		$result .= " $tmpresult type $tmptype;";
		# and end it.
		$result .= " };";
	}

	my $tree = &analyze_brackets($result);
	my @res = &analyze_statements(@$tree);

	foreach my $temp (@res) {
		my @temp = @$temp;
		my $type = shift @temp;

		my $statement;

		eval {
			my $tmp = 'DNS::Config::Statement::' . ucfirst(lc $type);

			if ( eval "require $tmp" ){
				$statement = $tmp->new();
				$statement->parse_tree(@temp);
			}else{
				# Doesn't exist.
				print STDERR "Require of $tmp failed\n";
			}
		};

		if($@) {
			#warn $@;
			
			$statement = DNS::Config::Statement->new();
			$statement->parse_tree($type, @temp);
		}

		$self->{'CONFIG'}->add($statement);
	}
		
	return $self;
}


# This routine only dumps the nsd.zones file.
sub dump_nsd_zones {
	my($self, $file) = @_;
	
	$file = $file || $self->{'FILE'};

	return undef unless($file);
	return undef unless($self->{'CONFIG'});

	my $config = $self->config;
	my @statements = $config->statements;

	my $infile = 0;
	my $old_fh = undef;
	if($file) {
		if(open(FILE, ">$file")) {
			$old_fh = select(FILE);
			$infile = 1;
		}else{
			return( undef );
		}
	}

	# We need to iterate through the config outselves
	foreach my $statement ( @statements ){
		my $tmpref = ref $statement;

		# Dump only the zone mentions.
		next unless( $tmpref =~ /^DNS::Config::Statement::Zone$/ );

		# ; zone^Iname^I^Ifilename^I^I[ masters/notify ip-address ]$
		# zone^I.^I^Iprimary/root.zone^Inotify 128.9.0.107 192.33.4.12 128.8.10.90$
		# zone^Iww.net^I^Iprimary/ww.net$
		# zone^Inlnetlabs.nl^Isecondary/nlnetlabs.nl^Imasters 213.53.69.1$
		print "zone\t" . $statement->{'NAME'} . "\t\t" . $statement->{'FILE'};
		my @masters = $statement->masters();
		my @anotify = $statement->also_notifys();
		if( ( scalar @masters ) > 0 ){
			print "\tmasters";
			foreach my $kkey( @masters ){
				my @foo = @{$kkey};
				foreach my $kkey2( @foo ){
					print " $kkey2";
				}
			}
		}
		if( ( scalar @anotify ) > 0 ){
			print "\tnotify";
			foreach my $kkey( @anotify ){
				my @foo = @{$kkey};
				foreach my $kkey2( @foo ){
					print " $kkey2";
				}
			}
		}
		print "\n";
		# print "Foo " . $statement->master . "\n";
	}


	# If we're in a file, select() back.
	if( $infile ){
		# map { $_->dump() } $self->config()->statements();
		select($old_fh);
		close FILE;
		$infile = 0;
	}
	
	return $self;
}

sub dump_nsdc {
	my($self, $file) = @_;
	
	$file = $file || $self->{'FILE'};

	return undef unless($file);
	return undef unless($self->{'CONFIG'});

	my $config = $self->config;
	my @statements = $config->statements;

	my $infile = 0;
	my $old_fh = undef;
	if($file) {
		if(open(FILE, ">$file")) {
			$old_fh = select(FILE);
			$infile = 1;
		}else{
			return( undef );
		}
	}

	# We need to iterate through the config outselves
	foreach my $statement ( @statements ){
		my $tmpref = ref $statement;

		# Dump only the option mentions.
		next unless( $tmpref =~ /^DNS::Config::Statement::Options$/ );

		# Where named-xfer is.
		if( defined( $statement->{'NAMED-XFER'} ) ){
			print "NAMEDXFER=\"" . $statement->{'NAMED-XFER'} . "\"\n";
		}

		# Where NSDZONES is.
		if( defined( $statement->{'DIRECTORY'} ) ){
			print "NSDZONES=\"" . $statement->{'DIRECTORY'} . "\"\n";
		}

		# Where the NSDKEYSDIR is.
		# nsdkeysdir isn't expected to be in the Options statement.
		if( defined( $self->nsdkeysdir() ) ){
			print "NSDKEYSDIR=\"" . $self->nsdkeysdir() . "\"\n";
		}elsif( defined( $statement->{'NSDKEYSDIR'} ) ){
			print "NSDKEYSDIR=\"" . $statement->{'NSDKEYSDIR'} . "\"\n";
		}elsif( defined( $statement->{'DIRECTORY'} ) ){
			print "NSDKEYSDIR=\"" . $statement->{'DIRECTORY'} . "\"\n";
		}

		# Now for the flags.  Oh my.
		if( defined( $statement->{'LISTEN-ON'} ) ){

			print "NSDFLAGS=\"";

			my @tsplit = @{ $statement->{'LISTEN-ON'} };

			foreach my $kkey( @tsplit ){
				if( ! ref( $kkey ) ){
					if( $kkey =~ /port/i ){
						print " -p";
					}else{
						print " $kkey";
					}
				}else{
					my @tref1 = @{$kkey};
					foreach my $kkey2( @tref1 ){
						if( ref( $kkey2 ) ){
							push @tref1, @{$kkey2};
							next;
						}
						if( $kkey2 =~ /any/ ){
							# NSD doesn't handle 
							# multiple interfaces
							# correctly.  This is
							# a hack to deal with
							# these cases.
							print " \`ifconfig -a | perl -e \'while(<>){ next unless(m/^\\s*inet(4|6)?(\\s+addr:)?\\s*(((\\d+\\.){3,3}\\d+)|(([0-9a-f]*:){1,15}(:[0-9a-f]+){1,15}))(\\/\\d+)?\\s+/); print \" -a \$3\"; }\'\`";
						}else{
							print " -a $kkey2";
						}
					}
				}
			}

			print "\"\n";
		}
		print "\n";
	}


	# If we're in a file, select() back.
	if( $infile ){
		# map { $_->dump() } $self->config()->statements();
		select($old_fh);
		close FILE;
		$infile = 0;
	}
	
	return $self;
}

sub dump_tsig() {
	my($self, $dir) = @_;

	$dir = $dir || $self->nsdkeysdir();

	return( undef ) unless( defined( $dir ) );

	# Make sure that its useful.
	return( undef ) unless( -d $dir );
	return( undef ) unless( -r $dir );
	return( undef ) unless( -w $dir );
	return( undef ) unless( -x $dir );

	# Map the algorithms.
	# Should really be invoking DNS::Config::Statement::Key for this.
	my %algs = (
			"157",	"hmac-md5",
			"hmac-md5",	"157",
		);

	# Run through the statements.
	my $config = $self->config;
	my @statements = $config->statements;

	my %keys = ();
	my %keys_written = ();
	my %want_keys = ();

	foreach my $statement( @statements ){
		my $tref = ref( $statement );

		# We only want Key, Zone or Server statements.
		next unless( $tref =~ /^DNS::Config::Statement::(Key|Zone|Server)$/ );

		my $this_ref = $1;
		if( $this_ref eq 'Key' ){
			my $tname = $statement->name();
			my $talg = $statement->algorithm();
			my $tsecret = $statement->secret();

			if( $talg =~ /\D/ ){
				$talg = $algs{$talg};
			}

			$keys{$tname}{'name'} = $tname;
			$keys{$tname}{'algorithm'} = $talg;
			$keys{$tname}{'secret'} = $tsecret;
		}elsif( $this_ref eq 'Server' ){

			my $tname = $statement->name();
			my @tkeys = $statement->keys();
			my %usekeys = ();
			foreach my $kkey( @tkeys ){
				if( ref( $kkey ) ){
					push @tkeys, @{$kkey};
				}else{
					$usekeys{"$kkey"}++;
				}
			}

			foreach my $kkey( keys %usekeys ){
				my $tstr = "ip-$tname.tsiginfo";
				$want_keys{$tstr} = $kkey;
			}
		}elsif( $this_ref eq 'Zone' ){
			my $tname = $statement->name();

			my @masters = $statement->masters();

			my $loop = 0;

			# This is possibly multiple levels of array, that 
			# should be the sequence of things in the 'masters'
			# field of the zone statement.  We *should* have
			# 'ip', 'port', 'port_num', 'key', 'key_id', 'ip' (etc)
			# with the 'port', 'port_num' and 'key', 'key_id'
			# sequences optional.
			while( $loop < scalar @masters ){
				my $kkey = $masters[$loop];
				if( ref( $kkey ) ){
					push @masters, @{$kkey};
					$loop++;
				}else{
					# $ip key $keyname
					my $tip = $kkey;
					$loop++;
					my $tport = undef;
					my $tkey = undef;
					while( ( $loop + 2 ) < ( scalar @masters ) && ! ref( $masters[$loop] ) && ! ref( $masters[$loop+1] ) && $masters[$loop] =~ /(port|key)/i ){
						my $twhat=$1;
						if( $twhat =~ /key/i ){
							$tkey = $masters[$loop+1];
							$loop++;
						}elsif( $twhat =~ /port/i ){
							$tport = $masters[$loop+1];
							$loop++;
						}
						$loop++;
					}

					# We found a key for this zone.  Yay!
					if( defined( $tkey ) ){
						my $tstr = "zi-$tname-$tip.tsiginfo";
						$want_keys{$tstr} = $tkey;
					}
				}
			}
		}
	}

	# Now write out all of the keys.
	foreach my $kkey( keys %want_keys ){
		my $tkey = $want_keys{$kkey};

		print STDERR "Key - $kkey - $tkey\n";
		next if( defined( $keys_written{$kkey} ) );
		next if( ! defined( $keys{$tkey}{'name'} ) );

		# Wheres the IP address?
		my $tip = "IPADDRESS";

		# zi-$zone-$ip.tsiginfo
		if( $kkey =~ /^zi-\S+-([^\-]+).tsiginfo$/ ){
			$tip=$1;
		# ip-$ip.tsiginfo
		}elsif( $kkey =~/^ip-(\S+).tsiginfo$/ ){
			$tip=$1;
		}

		# Write out the file.

		if( open( TSIGOUT, "> $dir/$kkey" ) ){
			print TSIGOUT "$tip\n";	
			print TSIGOUT $keys{$tkey}{'name'} . "\n";	
			print TSIGOUT $keys{$tkey}{'algorithm'} . "\n";	

			# Deal with the secret.
			my $toutsec = undef;
			if( ref( $keys{$tkey}{'secret'} ) ){
				$toutsec = join( ' ', @{$keys{$tkey}{'secret'}} ) ;
			}else{
				$toutsec = $keys{$tkey}{'secret'};
			}
			$toutsec =~ s/^"//g;
			$toutsec =~ s/"$//g;
			print TSIGOUT "$toutsec";
			print TSIGOUT "\n";
			close( TSIGOUT );
			$keys_written{$kkey}++;
		}
			
	}
	
	return( $self );
}

sub dump {
	my($self, $file) = @_;

	# Eventually this could dump all of it, but you need to specify
	# multiple files.
	return( $self->dump_nsd_zones( $file ) );
}

sub config {
	my($self) = @_;
	
	return($self->{'CONFIG'});
}

sub analyze_brackets {
	my($string) = @_;
	
	my @chars = split //, $string;

	my $tree = [];
	my @chunks;
	my @stack;

	my %matching = (
		'(' => ')',
		'[' => ']',
		'<' => '>',
		'{' => '}',
	);

	for my $char (@chars) {
		if(grep {$char eq $_} keys(%matching)) {
			my $temp = [];
			push @$tree, $temp;
			push @chunks, $tree;
			push @stack, $matching{$char};
			$tree = $temp;
		}
		elsif(grep {$char eq $_} values(%matching)) {
			my $expected = pop @stack;
			die "Invalid order !\n" if((!defined $expected) || ($char ne $expected));
			$tree = pop @chunks;
			die "Unmatched closing !\n" if(!ref($tree));
		}
		else {
			my $noe = scalar(@$tree);
			
			if((!$noe) || (ref($$tree[$noe-1]) eq 'ARRAY')) {
				push @$tree, ($char);
			}
			else {
				$$tree[$noe-1] .= $char;
			}
		}
	}

	die "Unbalanced !\n" if(scalar @stack);

	return($tree);
}

sub analyze_statements {
	my(@array) = @_;
	my @result;
	my $full;
	
	for my $line (@array) {
		if(!ref($line)) {
			$line =~ s/\s*\;\s*/\;/g;

			my(@parts) = split /;/, $line, -1;

			shift @parts if(!$parts[0]);

			if($parts[$#parts-1] eq '') {
				$full = 1;
				pop @parts;
			}
			else {
				$full = 0;
			}

			for my $temp (@parts) {
				if($temp) {
					$temp =~ s/^\s*//g;
					
					my @chunks = split / /, $temp;

					push @result, (\@chunks);
				}
			}
		}
		else {
			my @statements = &analyze_statements(@$line);

			my @temp;
			if(!$full) { my $temp = pop @result; @temp = @$temp; }
			push @temp, (\@statements);
			push @result, (\@temp);
		}
	}

	return(@result);
}

1;

__END__

=pod

=head1 NAME

DNS::Config::File::Nsd - Concrete adaptor class

=head1 SYNOPSIS

use DNS::Config::File::Nsd;

my $file = new DNS::Config::File::Nsd($nsd.zones_file);

# Read in an additional config file (needed before invoking ->parse() )
$file->nsdc( $nsdc.conf_file );

# Set the nsdkeysdir (tsig keys in files)
$file->nsdkeysdir( $tsigdir );

# Parse nsd.zones, nsdc.conf, and any TSIG files 
$file->parse();

# Dump the nsd.zones file (also $file->dump_nsd_zones() )
$file->dump();		

# Dump the nsdc.conf file
$file->dump_nsdc();

# Dump the tsig files.
$file->dump_tsig( $tsigdir );

# Debug the output.
$file->debug();

$file->config(new DNS::Config());


=head1 ABSTRACT

This class represents a set of configuration files for NLNetLab's
NSD (Name Server Daemon), an authoritative-only nameserver sponsored
by the RIPE NCC.

=head1 DESCRIPTION

This class, the Nsd file adaptor, knows how to read and write the
information to a file in the NSD daemon specific formats.  Note that
NSD has three places for configuration information, being:

nsd.zones - Zone name and zone file specifications for zonec(1), the
notify servers for nsd-notify(1) and the master servers for nsdc and
named-xfer.

nsdc.conf - Special (shell-script) configuration for nsdc.

NSDKEYSDIR - A directory where TSIG keys can be found, for usage by 
nsdc and named-xfer.


=head1 AUTHOR

Copyright (C)2003 Bruce Campbell. All rights reserved.

This library is free software; you can redistribute it and/or
modify it under the same terms as Perl itself.

Please address bug reports and comments to: 
bxc@users.sourceforge.net


=head1 SEE ALSO

L<DNS::Config>, L<DNS::Config::File>, L<DNS::Config::Bind9>


=cut