aboutsummaryrefslogtreecommitdiff
path: root/dnsdbck
diff options
context:
space:
mode:
authorSergey Poznyakoff <gray@gnu.org.ua>2012-03-26 11:55:58 +0300
committerSergey Poznyakoff <gray@gnu.org.ua>2012-03-26 11:58:25 +0300
commit9d6e6d1c7bb737da4b9bef81d612ffa702ab2795 (patch)
tree8b2ec7c629d2cd883bc04080447f16b4df8c2bc9 /dnsdbck
downloaddnstools-9d6e6d1c7bb737da4b9bef81d612ffa702ab2795.tar.gz
dnstools-9d6e6d1c7bb737da4b9bef81d612ffa702ab2795.tar.bz2
Initial commit.
Diffstat (limited to 'dnsdbck')
-rw-r--r--dnsdbck/MANIFEST3
-rw-r--r--dnsdbck/Makefile.PL15
-rwxr-xr-xdnsdbck/dnsdbck785
3 files changed, 803 insertions, 0 deletions
diff --git a/dnsdbck/MANIFEST b/dnsdbck/MANIFEST
new file mode 100644
index 0000000..fd61d5c
--- /dev/null
+++ b/dnsdbck/MANIFEST
@@ -0,0 +1,3 @@
+MANIFEST
+Makefile.PL
+dnsdbck
diff --git a/dnsdbck/Makefile.PL b/dnsdbck/Makefile.PL
new file mode 100644
index 0000000..4e54f1b
--- /dev/null
+++ b/dnsdbck/Makefile.PL
@@ -0,0 +1,15 @@
+# -*- perl -*-
+use ExtUtils::MakeMaker;
+
+# See lib/ExtUtils/MakeMaker.pm for details of how to influence
+# the contents of the Makefile that is written.
+WriteMakefile(
+ 'NAME' => 'dnsdbck',
+ 'FIRST_MAKEFILE' => 'Makefile',
+ 'VERSION' => '1.00',
+ 'EXE_FILES' => [ 'dnsdbck' ],
+ 'PREREQ_PM' => { 'Getopt::Long' => 2.34,
+ 'DBI' , => 1.607,
+ 'Net::DNS' => 0.66,
+ 'Net::CIDR' => 0.14 }
+);
diff --git a/dnsdbck/dnsdbck b/dnsdbck/dnsdbck
new file mode 100755
index 0000000..cf741ce
--- /dev/null
+++ b/dnsdbck/dnsdbck
@@ -0,0 +1,785 @@
+#! /usr/bin/perl
+# Copyright (C) 2011 Sergey Poznyakoff <gray@gnu.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+use strict;
+use Getopt::Long qw(:config gnu_getopt no_ignore_case);
+use DBI;
+use POSIX qw(strftime setuid setgid);
+use File::Temp qw(tempfile);
+use Socket;
+use Net::DNS;
+use Net::CIDR;
+use Pod::Usage;
+use Pod::Man;
+
+# Options:
+my $sys_config_file = "/etc/dnsdbck.conf"; # Configuration file name
+my $debug; # Debug mode indicator.
+my $logfile; # Name of the logfile.
+my $dry_run; # Dry-run mode.
+my $help; # Show help and exit.
+my $man; # Show man and exit.
+my $my_cnf; # Name of the mysql option file.
+my $all_zones; # Process all zones.
+my $author; # Author name for INSERT statements.
+
+my $username;
+
+my $sqlfilename; # Name of the output SQL file.
+my $sqlfile; # Its handle.
+
+# Global vars:
+my $descr = "check and repair the DNS database";
+my $script; # This script name.
+
+my %debug_level = ( 'GENERAL' => 0,
+ 'SQL' => 0,
+ 'DNS' => 0,
+ 'MISSING' => 0 );
+
+# FIXME: these three should be configurable too
+my %ns_name = ( 'ns1.farlep.net' => 1, 'ns3.farlep.net' => 1 );
+my $primary_ns = 'ns1.farlep.net';
+my $responsible_person = 'hostmaster.ns1.farlep.net';
+
+my @create_soa_allow_list; # list of CIDRs for which creating SOA records
+ # is allowed.
+
+my $dbd; # Database connection descriptor.
+
+our $sys_dbname;
+our $sys_dbhost;
+our $sys_dbport;
+our $sys_dbuser;
+our $sys_dbpasswd;
+our $sys_dbparams;
+
+my %ignored_zone;
+my %ignored_host;
+my %nosoa_warning; # Used to avoid duplicate 'no SOA' warnings.
+##
+
+sub logit {
+ print LOG "@_\n";
+}
+
+sub loginit {
+ if ($logfile and (!-e $logfile or -w $logfile)) {
+ print STDERR "$script: logging to $logfile\n";
+ open(LOG, ">$logfile");
+ } else {
+ open(LOG, ">&STDERR");
+ }
+}
+
+sub logdone {
+}
+
+sub debug {
+ my $category = shift;
+ my $level = shift;
+# print STDERR "$category: $debug_level{$category} >= $level\n";
+ if ($debug_level{$category} >= $level) {
+ print LOG "$script: DEBUG[$category]: @_\n";
+ }
+}
+
+sub read_config_file($) {
+ my $config_file = shift;
+ print STDERR "reading $config_file\n" if ($debug);
+ open(FILE, "<", $config_file) or die("cannot open $config_file: $!");
+ while (<FILE>) {
+ chomp;
+ s/^\s+//;
+ s/\s+$//;
+ s/\s+=\s+/=/;
+ s/#.*//;
+ next if ($_ eq "");
+ unshift(@ARGV, "--$_");
+ }
+}
+
+## Returns a hash of lists of entries
+# arg0 : table
+# arg1 : criterion
+# arg2 : fields to show (* for all)
+sub GetDBHashArray {
+ my $table = $_[0];
+ my $criterion;
+ my $fields = "*";
+ my $hop;
+ my @ret;
+
+ $criterion = "WHERE ".$_[1] if $_[1];
+ $fields = $_[2] if $_[2];
+
+ my $query = "SELECT ".$fields." FROM ".$table." ".$criterion;
+ debug('SQL', 2, "Query $query");
+ $hop = $dbd->prepare($query);
+ $hop->execute;
+ while (my $ref = $hop->fetchrow_hashref) {
+ push(@ret, $ref);
+ }
+ $hop->finish;
+ return @ret;
+}
+
+## Returns a list of entries
+# arg0 : table
+# arg1 : criterion
+# arg2 : fields to show (* for all)
+sub GetDB {
+ my $table = $_[0];
+ my $criterion;
+ my $fields = "*";
+ my $hop;
+ my @ret;
+
+ $criterion = "WHERE ".$_[1] if $_[1];
+ $fields = $_[2] if $_[2];
+
+ my $query = "SELECT ".$fields." FROM ".$table." ".$criterion;
+ debug('SQL', 2, "Query $query");
+ $hop = $dbd->prepare($query);
+ $hop->execute;
+ while (my (@line) = $hop->fetchrow_array) {
+ push(@ret, join(",", map {defined $_ ? $_ : "0"} @line));
+
+ }
+ $hop->finish;
+ return @ret;
+}
+
+sub matches_direct($) {
+ my $arg = $_[0];
+ return 1 if ($#ARGV == -1);
+ foreach my $zone (@ARGV) {
+ return 1 if (substr($arg, - (length($zone) + 1)) eq "$zone.");
+ }
+ return 0;
+}
+
+my %soa_records;
+sub has_soa($) {
+ my $zone = $_[0];
+ $zone =~ s/\.$//;
+ unless (defined($soa_records{$zone})) {
+ my @soa = GetDB("dns_soa",
+ "type='SOA' AND zone='".$zone."'");
+ $soa_records{$zone} = ($#soa >= 0);
+ }
+ return $soa_records{$zone};
+}
+
+sub build_insert_query($$$$$$) {
+ my ($zone, $host, $type, $data, $view, $ttl) = @_;
+ return sprintf("INSERT INTO dns_records (author,zone,host,type,data,view,ttl)"
+ . " VALUES('%s','%s','%s','%s','%s','%s','%s')",
+ $author,
+ $zone, $host, $type, $data, $view, $ttl);
+}
+
+sub read_from_file($%) {
+ my ($file, $hashref) = @_;
+ open(FILE, "<", $file)
+ or die("Cannot open file $file for reading");
+ while (<FILE>) {
+ chomp;
+ s/^\s+//;
+ s/\s+$//;
+ s/#.*//;
+ next if ($_ eq "");
+ $hashref->{$_} = 1;
+ }
+ close(FILE);
+}
+
+sub build_delete_query($) {
+ my ($serial_id) = @_;
+ return "DELETE FROM dns_records WHERE serial=$serial_id";
+}
+
+sub build_soa($$) {
+ my ($key,$view) = @_;
+
+ my $query = sprintf("INSERT INTO dns_soa (zone,type,data,serial,resp_person,view) VALUES('%s','SOA','%s.','%s','%s.','%s');\n",
+ $key, $primary_ns,
+ strftime('%Y%m%d00', localtime),
+ $responsible_person,
+ $view);
+ foreach my $ns (keys %ns_name) {
+ $query .= sprintf("INSERT INTO dns_soa (zone,type,data,view) VALUES('%s','NS','%s.','%s');\n", $key, $ns, $view);
+ }
+ return $query;
+}
+#######
+
+my $resolver;
+
+sub check_zone_ns($) {
+ my ($zone) = @_;
+ $resolver = Net::DNS::Resolver->new if (!$resolver);
+ debug('DNS', 1, "querying NSs for $zone");
+ my $query = $resolver->query($zone, "NS");
+ if ($query) {
+ foreach my $rr (grep { $_->type eq 'NS' } $query->answer) {
+ debug('DNS', 2, "$zone: ns=".$rr->nsdname);
+ return 1 if ($ns_name{$rr->nsdname});
+ }
+ } else {
+ debug('DNS', 1, "query failed: ", $resolver->errorstring);
+ return 2 if (($resolver->errorstring eq "NXDOMAIN") or
+ ($resolver->errorstring eq "NOERROR"));
+ return -1;
+ }
+ debug('DNS', 1, "$zone: not ours");
+ return 0;
+}
+
+sub check_host_name($$) {
+ my ($addr, $name) = @_;
+ $name =~ s/\.$//;
+ $resolver = Net::DNS::Resolver->new if (!$resolver);
+ debug('DNS', 1, "matching PTRs for $addr against $name");
+ my $query = $resolver->query($addr, "PTR");
+ if ($query) {
+ foreach my $rr (grep { $_->type eq 'PTR' } $query->answer) {
+ my $s;
+ debug('DNS', 2, "$addr: ptr=".$rr->name."; string=".$rr->rdatastr);
+ ($s = $rr->rdatastr) =~ s/\.$//;
+ return 1 if ($s eq $name);
+ return 2 if ((length($s) > length($name)
+ and substr($s, - (length($name) + 1)) eq ".$name") or
+ (length($name) > length($s)
+ and substr($name, - (length($s) + 1)) eq ".$s"))
+ }
+ } else {
+ debug('DNS', 1, "query failed: ", $resolver->errorstring);
+ return -2 if ($resolver->errorstring eq "NXDOMAIN");
+ return -1;
+ }
+ debug('DNS', 1, "$addr: no match");
+ return 0;
+}
+
+my @private_network_cidr_list;
+
+sub private_network($) {
+ my $arg = shift;
+
+ if ($#private_network_cidr_list == -1) {
+ @private_network_cidr_list =
+ Net::CIDR::cidradd("10.0.0.0/8", @private_network_cidr_list);
+ @private_network_cidr_list =
+ Net::CIDR::cidradd("172.16.0.0/12", @private_network_cidr_list);
+ @private_network_cidr_list =
+ Net::CIDR::cidradd("192.168.0.0/16", @private_network_cidr_list);
+ }
+ if ($arg =~ /.*\.in-addr\.arpa$/) {
+ my @octet = split(/\./, $arg);
+ $arg = "$octet[2].$octet[1].$octet[0].0/24";
+ }
+ return Net::CIDR::cidrlookup($arg, @private_network_cidr_list);
+}
+
+# Return true if creating SOA record for reverse $zone is allowed.
+sub create_soa_allowed($) {
+ my $arg = shift;
+
+ if ($arg =~ /.*\.in-addr\.arpa$/) {
+ my @octet = split(/\./, $arg);
+ $arg = "$octet[2].$octet[1].$octet[0].0/24";
+ }
+ return Net::CIDR::cidrlookup($arg, @create_soa_allow_list);
+}
+
+#############
+
+($script = $0) =~ s/.*\///;
+
+my $home;
+
+eval {
+ my @ar = getpwuid($<);
+ $home = $ar[7];
+};
+
+if ($ENV{'DNSDBCK_CONF'}) {
+ read_config_file($ENV{'DNSDBCK_CONF'});
+} elsif (-e "$home/.dnsdbck.conf") {
+ read_config_file("$home/.dnsdbck.conf");
+} elsif (-e "$sys_config_file") {
+ read_config_file("$sys_config_file");
+}
+
+GetOptions("help|h" => \$help,
+ "man" => \$man,
+ "debug|d:s" => sub {
+ if (!$_[1]) {
+ foreach my $key (keys %debug_level) {
+ $debug_level{$key} = 1;
+ }
+ } else {
+ foreach my $cat (split(/,/, $_[1])) {
+ my @s = split(/[:=]/, $cat, 2);
+ $s[0] =~ tr/[a-z]/[A-Z]/;
+ if (defined($debug_level{$s[0]})) {
+ $debug_level{$s[0]} =
+ ($#s == 1) ? $s[1] : 1;
+ } else {
+ print STDERR "$script: no such category: $s[0]\n";
+ exit(1);
+ }
+ }
+ }
+ },
+ "create-reverse-soa=s" => sub {
+ foreach my $cidr (split(/,/, $_[1])) {
+# unless (Net::CIDR::cidrvalidate($cidr)) {
+# print STDERR "$script: invalid CIDR: $cidr\n";
+# exit(1);
+# }
+ @create_soa_allow_list =
+ Net::CIDR::cidradd($cidr, @create_soa_allow_list);
+ }
+ },
+ "dry-run|n" => \$dry_run,
+ "my-cnf|c=s" => \$my_cnf,
+ "author|a=s" => \$author,
+ "outfile|o=s" => \$sqlfilename,
+ "all|A" => \$all_zones,
+ "ignore-zone=s" => sub {
+ foreach my $zone (split(/,/, $_[1])) {
+ $ignored_zone{$zone} = 1;
+ }
+ },
+ "ignore-zones-from=s" => sub {
+ read_from_file($_[1], \%ignored_zone);
+ },
+ "ignore-host=s" => sub {
+ foreach my $host (split(/,/, $_[1])) {
+ $ignored_host{$host} = 1;
+ }
+ },
+ "ignore-hosts-from=s" => sub {
+ read_from_file($_[1], \%ignored_host);
+ },
+ "log-file|l=s" => \$logfile,
+ "user-name|u=s" => \$username
+ ) or exit(1);
+
+pod2usage(-message => "$script: $descr", -exitstatus => 0) if $help;
+pod2usage(-exitstatus => 0, -verbose => 2) if $man;
+
+if ($username) {
+ my ($login,$pw,$uid,$gid,$quota,$comment,$gecos,$dir) = getpwnam($username)
+ or die("no such user: $username");
+ setgid($gid);
+ setuid($uid);
+ die("cannot switch to $username privileges")
+ unless ($< == $uid and $( == $gid);
+ $home = $dir;
+}
+
+unless (defined($author)) {
+ my ($login) = getpwuid($<);
+ $author = $login;
+}
+
+if ($#ARGV == -1 and !$all_zones) {
+ print STDERR "$script: no zones specified, try `$script --help' for more info\n";
+ exit(1);
+}
+
+if (defined($sqlfilename)) {
+ $dry_run = 1;
+ open($sqlfile, "+>", $sqlfilename) or
+ die("cannot open file $sqlfilename: $!");
+ print $sqlfile strftime "-- SQL script generated by $script on %c.\n",
+ localtime;
+} else {
+ ($sqlfile, $sqlfilename) = tempfile(UNLINK => 1);
+}
+
+loginit();
+
+debug('SQL', 1, "connecting to the database");
+
+my $arg = "";
+$arg .= ":database=$sys_dbname" if ($sys_dbname);
+$arg .= ":host=$sys_dbhost" if ($sys_dbhost);
+$arg .= ":port=$sys_dbport" if ($sys_dbport);
+$arg .= ":$sys_dbparams" if ($sys_dbparams);
+
+if (!$arg) {
+ $my_cnf = "$home/.my.cnf" if (!$my_cnf);
+ debug('SQL', 1, "using mysql option file $my_cnf");
+ $arg = ":;mysql_read_default_file=$my_cnf";
+}
+$arg = 'DBI:mysql'.$arg;
+
+$dbd = DBI->connect($arg,
+ $sys_dbuser, $sys_dbpasswd,
+ { RaiseError => 1, AutoCommit => 1}) or exit(1);
+
+my $criterion = "type='A'";
+if ($#ARGV >= 0) {
+ $criterion .= " AND zone IN ("
+ . join(',', map { s/[\\\"\']/\\$&/g; "'".$_."'" } @ARGV)
+ . ")";
+}
+
+my %reverse_zone;
+my $count;
+my $insert_count = 0;
+my $delete_count = 0;
+debug('GENERAL', 1, "collecting reverse zones");
+foreach my $ref (GetDBHashArray("dns_records",
+ $criterion,
+ "zone,host,data,view,ttl,serial")) {
+ my %row = %{$ref};
+ $row{'data'} =~ s/\s*$//;
+ $row{'host'} =~ s/\s*$//;
+ my @octets = split(/\./, $row{'data'});
+ my $rev = "$octets[2].$octets[1].$octets[0].in-addr.arpa";
+ debug('GENERAL', 10, "reverse zone $rev");
+ if (!$reverse_zone{$rev}) {
+ $reverse_zone{$rev} = {};
+ }
+ my $dirzone;
+ if ($row{'host'} eq "@") {
+ $dirzone = $row{'zone'}.".";
+ } else {
+ $dirzone = $row{'host'}.".".$row{'zone'}.".";
+ }
+ $reverse_zone{$rev}{$octets[3]} = [$dirzone, $row{'view'}, $row{'ttl'}, $row{'serial'}];
+ ++$count;
+}
+debug('GENERAL', 1,
+ "collected $count addresses in " . keys(%reverse_zone) . " reverse zones");
+
+
+REVLOOP:
+while (my ($key, $value) = each(%reverse_zone)) {
+ my %revhash = %{$value};
+
+ debug('GENERAL', 2,
+ "checking reverse zone $key (".keys(%revhash)." hosts)");
+
+ next if ($ignored_zone{$key});
+ unless (has_soa($key)) {
+ if (private_network($key) and create_soa_allowed($key)) {
+ debug('GENERAL', 1, "creating SOA/NS for $key");
+ my $query = build_soa($key, 'internal');
+ print $sqlfile "$query";
+ } else {
+ while (my ($host, $val) = each(%revhash)) {
+ my @ar = @{$val};
+ my $hostname = $ar[0];
+
+ my $canon_hostname;
+ ($canon_hostname = $hostname) =~ s/\.$//;
+ next if ($ignored_host{$canon_hostname});
+ $canon_hostname =~ s/[^\.]+\.//;
+ next if ($ignored_zone{$canon_hostname});
+
+ my $fullkey = "$host.$key";
+ my $msg = "$ar[3]: $hostname => $fullkey: no corresponding reverse record and no SOA for $key in the database";
+ my $rc = check_host_name($fullkey, $hostname);
+ if ($rc == 1) {
+ debug('GENERAL', 2, "$msg, but resolve is OK");
+ } elsif ($rc == 2) {
+ debug('GENERAL', 2, "$msg, but resolves to a subdomain, so OK");
+ } elsif ($rc == 0) {
+ logit("$msg, but the reverse resolves");
+ } else {
+ logit($msg);
+ }
+ }
+ next;
+ }
+ }
+
+ $criterion = "zone='$key' AND type='PTR'";
+ DBLOOP:
+ foreach my $ref (GetDBHashArray("dns_records",
+ $criterion,
+ "host,data,view,ttl,serial")) {
+ my %row = %{$ref};
+ next unless matches_direct($row{'data'});
+
+ if (defined($revhash{$row{'host'}})) {
+ delete $revhash{$row{'host'}};
+ } else {
+ my $fullkey = $row{'host'}.".".$key;
+ my $revhost;
+ ($revhost = $row{'data'}) =~ s/\.$//;
+ next if ($ignored_host{$revhost} or
+ $ignored_zone{$revhost} or
+ $ignored_host{$fullkey});
+ debug('MISSING', 1,
+ $row{'serial'}.": $fullkey => "
+ .$row{'data'}
+ ." direct record missing");
+
+ my $dir;
+ my $pref = "";
+ ($dir = $row{'data'}) =~ s/[^\.]+\.//;
+ $dir =~ s/\.$//;
+ next if ($ignored_zone{$dir});
+
+ unless (has_soa($revhost) or has_soa($dir)) {
+ my $msg = $row{'serial'}
+ .": $fullkey => "
+ .$row{'data'}
+ .": no SOA for $dir in the database";
+
+ my ($name,$aliases,$addrtype,$length,@addrs)
+ = gethostbyname($row{'data'});
+ if (defined(@addrs)) {
+ foreach my $addr (@addrs) {
+ my @oct = split(/\./, inet_ntoa($addr));
+ my $revaddr = "$oct[3].$oct[2].$oct[1].$oct[0].in-addr.arpa";
+ debug('DNS', 2, "$row{'data'} resolves to $revaddr");
+# print STDERR "$revaddr eq $fullkey\n";
+ if ($revaddr eq $fullkey) {
+ debug('GENERAL', 2, "$msg, but backresolve is OK");
+ next DBLOOP;
+ }
+ }
+ }
+
+ my $nsres = check_zone_ns($dir);
+ if ($nsres == 1) {
+ $msg .= ", but the zone is handled by our NSs";
+ } elsif ($nsres == 0) {
+ $msg .= " and the zone is NOT handled by our NSs: deleted";
+ my $query = build_delete_query($row{'serial'});
+ print $sqlfile "$query;\n";
+ $delete_count++;
+ logit($msg);
+ next;
+ } elsif ($nsres == 2) {
+ $msg .= " and the zone does not exist: deleted";
+ my $query = build_delete_query($row{'serial'});
+ print $sqlfile "$query;\n";
+ $delete_count++;
+ logit($msg);
+ next;
+ } else {
+ $msg .= " (resolve error)";
+ }
+
+ logit($msg);
+
+ $pref = "-- ";
+ }
+
+ my @octets = split(/\./, $key);
+ my $query = build_insert_query($dir,
+ substr($row{'data'}, 0,
+ length($row{'data'})
+ - length($dir)-2),
+ "A",
+ "$octets[2].$octets[1].$octets[0]."
+ .$row{'host'},
+ $row{'view'},
+ $row{'ttl'});
+ print $sqlfile "$pref$query;\n";
+ $insert_count++ unless ($pref);
+ }
+ }
+
+ # Generate missing reverse entries
+ while (my ($host, $val) = each(%revhash)) {
+ my @ar = @{$val};
+ debug('MISSING', 1,
+ "$ar[3]: $ar[0] => $host.$key: reverse record missing");
+ my $query = build_insert_query($key,
+ $host,
+ "PTR",
+ $ar[0],
+ $ar[1],
+ $ar[2]);
+ print $sqlfile "$query;\n";
+ $insert_count++;
+ }
+}
+
+if ($insert_count + $delete_count) {
+ debug('GENERAL', 1, "updating database");
+ unless ($dry_run) {
+ seek($sqlfile, 0, 0);
+ while (<$sqlfile>) {
+ next if (/^--/);
+ s/;$//;
+ debug('SQL', 2, "Query: $_\n");
+ $dbd->do($_);
+ }
+ debug('GENERAL', 1,
+ "inserted: $insert_count, deleted: $delete_count records");
+ }
+} else {
+ debug('GENERAL', 1, "nothing to update in the database");
+}
+
+debug('SQL', 1, "closing database");
+$dbd->disconnect();
+logdone();
+debug('GENERAL', 1, "finished");
+
+__END__
+
+=head1 DNSDBCK
+
+dnsdbck - check and, if possible, repair the DNS database
+
+=head1 SYNOPSIS
+
+dnsdbck [I<options>] [B<zone>] [B<zone>...]
+
+=head1 DESCRIPTION
+
+B<Dnsdbck> checks the DNS database for consistency and repairs it, when
+possible. For each record in a specified set of zones, it checks whether
+a corresponding reverse exists. If it does not, B<dnsdbck> tries to
+create it, if the corresponding C<in-addr.arpa> zone is present in the
+database or if its IP address falls within CIDRs set using the
+B<--create-reverse-soa> option (see below). In the latter case,
+corresponding B<SOA> and B<NS> records will be created.
+
+=head1 OPTIONS
+
+=over 4
+
+=item B<--all>, B<-a>
+
+Process all zones. When this option is used, any B<zone> arguments are
+ignored.
+
+=item B<--create-reverse-soa>=I<cidr>,[I<cidr>...]
+
+Create SOA and NS records for reverse zones matching given CIDRs.
+
+=item B<--ignore-zone>=I<zone>[,I<zone>...]
+
+Ignore changes to the listed zones.
+
+=item B<--ignore-zone-from>=I<FILE>
+
+Read the list of zones to ignore from I<FILE>. The file format is: one
+zone per line, UNIX comments and empty lines are ignored.
+
+=item B<--ignore-host>=I<host>[,I<host>...]
+
+Ignore changes to the listed hostnames.
+
+=item B<--ignore-host-from>=I<FILE>
+
+Read the list of hostnames to ignore from I<FILE>. The file format is: one
+host per line, UNIX comments and empty lines are ignored.
+
+=item B<--my-cnf>=I<FILE>, B<-c> I<FILE>
+
+Use I<FILE> as MySQL options file. Default is "$HOME/.my.cnf".
+
+=item B<--author>=I<NAME>, B<-a> I<NAME>
+
+Use I<NAME> for the B<author> column. Default is your login name.
+
+=item B<--user-name>=I<NAME>, B<-u> I<NAME>
+
+Switch to the privileges of user I<NAME> after startup.
+
+=item B<--outfile>=I<FILE>, B<-o> I<FILE>
+
+Write SQL instructions to repair the database to I<FILE>. Implies
+B<--dry-run>.
+
+=item B<--log-file>=I<FILE>, B<-l> I<FILE>
+
+Write diagnostic output to I<FILE>, instead of standard error.
+
+=item B<--dry-run>, B<-n>
+
+Do not try to repair the database.
+
+=item B<--debug>[=I<spec>[,I<spec>...]], B<-d>[I<spec>[,I<spec>...]]
+
+Set debugging level. I<spec> is either B<category> or B<category>=B<level>,
+B<category> is a debugging category name and B<level> is a decimal
+verbosity level. Valid categories are: C<GENERAL>, C<SQL>, C<DNS> and
+C<MISSING> (all case-insensitive). If B<level> is not supplied, 1 is used
+instead.
+
+=item B<--help>, B<-h>
+
+Show a terse help summary and exit.
+
+=item B<--man>
+
+Prints the manual page and exits.
+
+=back
+
+=head1 CONFIGURATION
+
+The program reads its configuration from one of the following locations:
+
+=over 4
+
+=item B<a.> File name given by C<DNSDBCK_CONF> environment variable (if set)
+
+=item B<b.> B<~>/.dnsdbck.conf
+
+=item B<c.> /etc/dnsdbck.conf
+
+=back
+
+First of these files that exists is read. It is an error, if the
+B<$DNSDBCK_CONF> variable is set, but points to a file that does not exist.
+It is not an error if B<$DNSDBCK_CONF> is not set and neither of the two
+remaining files exist. It is, however, an error if any of the file exists,
+but is not readable.
+
+The configuration file uses usual UNIX configuration format. Empty
+lines and UNIX comments are ignored. Each non-empty line is either an
+option name, or option assignment, i.e. B<opt>=B<val>, with any amount of
+optional whitespace around the equals sign. Valid option names are
+the same as long command line options, but without the leading B<-->.
+For example:
+
+ all
+ ignore-zones-from = /etc/dns/zone.ignore
+ ignore-hosts-from = /etc/dns/host.ignore
+ my-cnf = /etc/dns/my.cnf
+
+=head1 ENVIRONMENT
+
+=over 4
+
+=item DNSDBCK_CONF
+
+The name of the configuration file to read, instead of the default
+F</etc/dnsdbck.conf>.
+
+=back
+
+=head1 AUTHOR
+
+Sergey Poznyakoff <gray@gnu.org>
+
+=cut

Return to:

Send suggestions and report system problems to the System administrator.