From 2fd19a6808a9d7b86ad294ae20ad9dcf1c4f6325 Mon Sep 17 00:00:00 2001 From: Sergey Poznyakoff Date: Wed, 1 Apr 2015 16:21:40 +0300 Subject: nsdbimport: implement import to the database; optionally expand $GENERATE directives --- nsdbimport/nsdbimport | 203 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 155 insertions(+), 48 deletions(-) (limited to 'nsdbimport') diff --git a/nsdbimport/nsdbimport b/nsdbimport/nsdbimport index a1db672..da6b501 100755 --- a/nsdbimport/nsdbimport +++ b/nsdbimport/nsdbimport @@ -20,6 +20,7 @@ use File::Basename; use Pod::Usage; use Pod::Man; use String::Regexp; +use DBI; use constant EX_OK => 0; use constant EX_USAGE => 64; # command line usage error @@ -34,8 +35,11 @@ use constant EX_CONFIG => 78; # configuration error my $progname = basename($0); # This script name; my $progdescr = "converts BIND zone files to SQL database"; my $debug; +my $dry_run; my $bind_directory; my $bind_config; +my $dbd; +my $generate_seen; sub error { my $msg = shift; @@ -60,10 +64,11 @@ sub abend { my %config; sub readconfig { - my $file = shift; + my ($file, $kw) = @_; open(my $fd, "<", $file) or abend(EX_UNAVAILABLE, "can't open configuration file $file: $!"); my $line; + my $err; while (<$fd>) { ++$line; chomp; @@ -84,18 +89,24 @@ sub readconfig { } my ($k,$v) = ($1, $2); - # unless (exists($config{$k})) { - # error("$file:$line: unknown keyword $k"); - # next; - # } - + unless (exists($kw->{$k})) { + error("$file:$line: unknown keyword $k"); + ++$err; + next; + } $config{$k} = $v; } close $fd; + exit(EX_CONFIG) if $err; abend(EX_CONFIG, "rr-query is not defined in $file") unless defined($config{'rr-query'}); } +sub istrue { + my $s = shift; + return $s =~ /^([1yt])|(yes)|(true)|(enable)$/i; +} + # ################### sub parse_named_conf { my %zone; @@ -121,21 +132,33 @@ sub parse_named_conf { } return %files; } +# ################### +my $rx_class = array_to_regexp('IN', 'HS', 'CH'); +my @rrtypes = ('A', 'AAAA', 'A6', 'AFSDB', + 'CNAME', 'DNAME', 'DNSKEY', 'DS', + 'EUI48', 'EUI64', 'HINFO', 'ISDN', + 'KEY', 'LOC', 'MX', 'NAPTR', + 'NS', 'NSEC', 'NXT', 'NXT', + 'PTR', 'RP', 'RRSIG', 'RT', + 'SIG', 'SOA', 'SPF', 'SRV', + 'TXT', 'WKS', 'X25'); +my $rx_rr = array_to_regexp(@rrtypes); + # ################### sub replvar { my ($rec, $var, $opt) = @_; my %option; if (defined($opt)) { foreach my $x (split /,/, $opt) { - if ($x =~ /([?+\.-@])(.*)/) { + if ($x =~ /([?+\.@-])(.*)/) { $option{$1} = $2; } } } - my $val = $rec->{$var}; + my $val = $rec->{$var} || ''; - if (!defined($val)) { + if ($val eq '') { if (defined($option{'-'})) { $val = $option{'-'}; } elsif (defined($option{'?'})) { @@ -146,7 +169,7 @@ sub replvar { $val = $option{'+'}; } - if (defined($val)) { + if ($val ne '') { if ($val eq '@' and defined($option{'@'})) { $val = $option{'@'} || $rec->{origin}; $val .= '.' unless $val =~ /\.$/; @@ -160,17 +183,62 @@ sub replvar { sub query_expand { my ($q, $rec) = @_; - $q =~ s/\$\{([\w_-]+)(?::(.+))?\}/replvar($rec, $1, $2)/gex; + $q =~ s/\$\{([\w_-]+)(?::(.+?))?\}/replvar($rec, $1, $2)/gex; debug(2, "$rec->{locus}: $q"); return $q; } +sub sql_query { + my ($tmpl, $rec) = @_; + my $q = query_expand($tmpl, $rec); + unless ($dry_run) { + $dbd->do($q) + or abend(EX_UNAVAILABLE, $dbd->errstr."\nFailed query: $q"); + } +} + # generate($expr, $origin, $file, $line); # $GENERATE start-stop[/step] lhs[{offset[,width[,type]]}] rr-type rhs[{offset[,width[,type]]}] # http://www.zytrax.com/books/dns/ch8/generate.html + +sub format_part { + my ($i, $xhs, $xwid, $xtyp, $loc) = @_; + my $ctr; + + if ($xtyp =~ /^[doxX]$/) { + $ctr = sprintf($xwid > 0 ? "%0${xwid}$xtyp" : "%$xtyp", $i); + } elsif ($xtyp =~ /[nN]/) { + my $f = $xtyp eq 'n' ? '%x' : '%X'; + my @a = reverse(split(//, sprintf("%x", $i))); + unshift @a, '0' while (($#a+1) < $xwid); + $ctr = join('.', @a); + } else { + error("invalid type $xtyp in \$GENERATE", prefix => $loc); + } + $xhs =~ s/\$/$ctr/; + return $xhs; +} + sub generate { - my ($expr, $origin, $file, $line) = @_; - # FIXME + my ($expr, $origin, $ttl, $file, $line) = @_; + my $p = '([^\s\{]+)(?:\{(\d+)(?:(?:,(\d+))(?:,([doxXnN])))\})?'; + if ($expr =~ /(\d+)-(\d+)(?:\/(\d+))?\s+$p\s+($rx_rr)\s+$p/) { + my ($start,$stop,$step, + $lhs,$loff,$lwid,$ltyp, + $rr, + $rhs,$roff,$rwid,$rtyp) = ($1, $2, $3 || 1, + $4, $5 || 0, $6 || 0, $7 || 'd', + $8, + $9, $10 || 0, $11 || 0, $12 || 'd'); + for (my $i = $start; $i <= $stop; $i += $step) { + import_record({origin => $origin, + locus => "$file:$line", + ttl => $ttl, + rr => $rr, + label => format_part($i + $loff,$lhs,$lwid,$ltyp), + data => format_part($i + $roff,$rhs,$rwid,$rtyp)}); + } + } } # ################### @@ -185,8 +253,7 @@ sub insert_soa { $rec->{retry} = parsetime($5, $rec->{origin}); $rec->{expire} = parsetime($6, $rec->{origin}); $rec->{minimum} = parsetime($7, $rec->{origin}); - my $q = query_expand($tmpl, $rec); - # FIXME: SQL + sql_query($tmpl, $rec); } else { error("malformed SOA: '$rec->{data}'", prefix => "warning: $rec->{locus}"); @@ -198,8 +265,7 @@ sub insert_mx { if ($rec->{data} =~ /^(\d+)\s+(.+)$/) { $rec->{mx_priority} = $1; $rec->{mx} = $2; - my $q = query_expand($tmpl, $rec); - # FIXME: SQL + sql_query($tmpl, $rec); } else { error("malformed MX: '$rec->{data}'", prefix => "warning: $rec->{locus}"); @@ -211,8 +277,7 @@ my %rrfun = (SOA => \&insert_soa, sub insert_rr { my ($rec, $tmpl) = @_; - my $q = query_expand($tmpl || $config{'rr-query'}, $rec); - # FIXME: SQL + sql_query($tmpl || $config{'rr-query'}, $rec); } # ################### @@ -256,23 +321,23 @@ sub parsetime { return $t; } -my $rx_class = array_to_regexp('IN', 'HS', 'CH'); +sub import_record { + my $rec = shift; + my $kw = lc($rec->{rr}).'-query'; -my $rx_rr = array_to_regexp('A', 'AAAA', 'A6', 'AFSDB', - 'CNAME', 'DNAME', 'DNSKEY', 'DS', - 'EUI48', 'EUI64', 'HINFO', 'ISDN', - 'KEY', 'LOC', 'MX', 'NAPTR', - 'NS', 'NSEC', 'NXT', 'NXT', - 'PTR', 'RP', 'RRSIG', 'RT', - 'SIG', 'SOA', 'SPF', 'SRV', - 'TXT', 'WKS', 'X25'); + if (defined($rrfun{$rec->{rr}}) and defined($config{$kw})) { + &{$rrfun{$rec->{rr}}}($rec, $config{$kw}); + } else { + insert_rr($rec, $config{$kw}); + } +} sub zimport { my ($file, $origin) = @_; $file = "$bind_directory/$file" if $file !~ m#^/# and defined($bind_directory); - + debug(1, "processing file $file, origin $origin"); open(my $fd, "<", $file) or abend(EX_NOINPUT, "can't open input file $file: $!"); my $line; @@ -293,7 +358,13 @@ sub zimport { } elsif (/^\$INCLUDE\s+(.+)/) { zimport($1, $origin); } elsif (/^\$GENERATE\s+(.+)/) { - generate($1, $origin, $file, $line); + if (istrue($config{generate})) { + generate($1, $origin, $ttl, $file, $line); + } else { + error('$GENERATE ignored', + prefix => "$file:$line: warning"); + $generate_seen = 1; + } } elsif (/^\$/) { error("ignoring unrecognized directive", prefix => "$file:$line: warning"); @@ -308,24 +379,16 @@ sub zimport { redo; } } - - my %record = (origin => $origin, - label => $1, - ttl => parsetime($2, "$file:$line") || $ttl, - rr => $rr, - data => $4, - locus => "$file:$line"); - my $kw = lc($rr).'-query'; - - if (defined($rrfun{$rr}) and defined($config{$kw})) { - &{$rrfun{$rr}}(\%record, $config{$kw}); - } else { - insert_rr(\%record, $config{$kw}); - } + import_record({origin => $origin, + label => $1, + ttl => parsetime($2, "$file:$line") || $ttl, + rr => $rr, + data => $4, + locus => "$file:$line"}); if ($rr eq 'SOA') { - $origin = $record{label} unless $record{label} eq '@'; + $origin = $1 unless $1 eq '@'; } } } @@ -350,8 +413,11 @@ GetOptions("h" => sub { "from-config=s" => \$bind_config, "origin=s" => \$origin, "debug|d+" => \$debug, + "dry-run|n" => \$dry_run, "config|c=s" => \$conffile) or exit(EX_USAGE); - + +++$debug if $dry_run; + unless (defined($conffile)) { ($conffile) = grep { -e $_ } ( '.nsdbimport.conf', @@ -362,7 +428,45 @@ unless (defined($conffile)) { abend(EX_USAGE, "no configuration file; please use the --config option") unless defined $conffile; -readconfig $conffile; +my %kw = ('database' => 1, + 'host' => 1, + 'port' => 1, + 'db-params' => 1, + 'user' => 1, + 'password' => 1, + 'db-default-file' => 1, + 'generate' => 1, + 'rr-query' => 1); + +foreach my $rr (@rrtypes) { + $kw{lc($rr).'-query'} = 1; +} + +readconfig($conffile, \%kw); + +debug(1, "connecting to the database"); + +my $arg = ""; +$arg .= ":database=$config{database}" if defined $config{database}; +$arg .= ":host=$config{host}" if defined $config{host}; +$arg .= ":port=$config{port}" if defined $config{port}; +$arg .= ":$config{'db-params'}" if defined $config{'db-params'}; +$arg .= ":;mysql_read_default_file=$config{'db-default-file'}" + if defined $config{'db-default-file'}; + +if (!$arg + or (!defined($config{user}) and !defined($config{'db-default-file'}))) { + my $my_cnf = "$ENV{HOME}/.my.cnf"; + if (-r $my_cnf) { + debug(1, "using mysql option file $my_cnf"); + $arg .= ";mysql_read_default_file=$my_cnf"; + } +} +$arg = 'DBI:mysql'.$arg; + +$dbd = DBI->connect($arg, $config{user}, $config{password}, + { PrintError => 0, AutoCommit => 1}) + or exit(EX_UNAVAILABLE); if (defined($bind_config)) { abend(EX_USAGE, "extra input files") if $#ARGV >= 0; @@ -377,4 +481,7 @@ if (defined($bind_config)) { } } - +error('set generate=yes to enable processing of $GENERATE directives') + if ($generate_seen); + +$dbd->disconnect; -- cgit v1.2.1