178 lines
5.8 KiB
Perl
Executable File
178 lines
5.8 KiB
Perl
Executable File
#!/usr/bin/env perl
|
|
# License: 3-Clause BSD. Author: Matthew Connelly.
|
|
# This is a (formerly Bash, now Perl) script for managing in-addr.arpa and ip6.arpa zones.
|
|
# If you have any questions or issues, open an issue at https://bitbucket.org/MaffC/script-collection/issues
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
package DNS::Reverse::Manager;
|
|
use feature qw(switch);
|
|
use vars '$VERSION'; $VERSION = '1.0.0'; #Version number
|
|
|
|
use Data::Validate::Domain qw(is_domain); #for validating domains
|
|
use Data::Validate::IP qw(is_public_ipv4 is_public_ipv6); #for validating v4/v6 addresses
|
|
use Getopt::Long qw(:config posix_default bundling pass_through); #for intelligently handling cli arguments
|
|
use Net::DNS; #for doing forward and reverse lookups
|
|
use Net::DNS::ZoneFile; #for working with BIND zones
|
|
use Net::IP; #for converting IPs to their reverse zones
|
|
use Data::Dumper; #debugging
|
|
|
|
#conf
|
|
my $def_rdns = 'hosted-by.mycompany.com'; #Recomend default is "hosted-by.your-website.tld".
|
|
my $def_dns = '8.8.8.8'; #Recommended default is 8.8.8.8 or 4.2.2.1.
|
|
my $zone_dir = '/var/named/'; #for cPanel, use /var/named/.
|
|
my $zone_ext = ".db"; #Default for most environments is ".db".
|
|
my $net_type = "cpanel"; #This was originally written to support cPanel-based DNS environments, and primarily impacts how rdns-manager "syncs".
|
|
|
|
#variables for arguments
|
|
my $verify = '';
|
|
my $force = '';
|
|
my $reset = '';
|
|
my $nosync = '';
|
|
my $fsync = '';
|
|
my $delptr = '';
|
|
my $newzone = '';
|
|
my $prefixlen = 64;
|
|
|
|
#functions
|
|
sub nicedie {
|
|
print shift."\n";
|
|
exit 1;
|
|
}
|
|
sub validate_domain {
|
|
my $domain = shift;
|
|
return 1 if is_domain $domain;
|
|
return 0;
|
|
}
|
|
sub validate_ip {
|
|
my $ip = shift;
|
|
return 1 if is_public_ipv4 $ip or is_public_ipv6 $ip;
|
|
return 0;
|
|
}
|
|
sub get_arpa {
|
|
my $ip = shift;
|
|
if(is_public_ipv4 $ip) {
|
|
$ip =~ m/^([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)$/;
|
|
return ($4, "$3.$2.$1.in-addr.arpa");
|
|
}
|
|
my $len = ($prefixlen/2);
|
|
Net::IP->new($ip)->reverse_ip =~ /^(.*)\.(.{$len}ip6\.arpa)\.$/;
|
|
return ($1,$2);
|
|
}
|
|
#TODO make these work for DNS roundrobins. I doubt anyone would be stupid enough to have more than one PTR of the same name
|
|
# and i'm not sure if it's even legal, but hey.
|
|
sub does_fqdn_match {
|
|
my ($fqdn,$ip) = @_;
|
|
my $r = Net::DNS::Resolver->new(recurse => 1,tcp_timepit => 5,udp_timeout => 5,nameservers => [$def_dns,]);
|
|
my $p = $r->search($fqdn, 'A');
|
|
$p = $r->search($fqdn, 'AAAA') unless is_public_ipv4 $ip;
|
|
return 0 unless defined $p;
|
|
my @res = $p->answer;
|
|
#due to IPv6 shortening, we need to use Net::IP here
|
|
return 1 unless scalar @res < 1 or Net::IP->new($res[0]->address)->ip ne Net::IP->new($ip)->ip;
|
|
return 0;
|
|
}
|
|
sub confirm_rdns {
|
|
my ($fqdn,$ip) = @_;
|
|
my ($rec,$zone) = get_arpa $ip;
|
|
my $rrec = $rec.".".$zone;
|
|
my $r = Net::DNS::Resolver->new(recurse => 1,tcp_timeout => 5,udp_timeout => 5,nameservers => [$def_dns,]);
|
|
my $p = $r->search($rrec, 'PTR');
|
|
return 0 unless defined $p;
|
|
my @res = $p->answer;
|
|
return 1 unless scalar @res < 1 or $res[0]->ptrdname."." ne $fqdn;
|
|
return 0;
|
|
}
|
|
sub does_zone_exist {
|
|
my $ip = shift;
|
|
my ($rec,$zone) = get_arpa $ip;
|
|
return -2 if !-e "$zone_dir/$zone$zone_ext";
|
|
return -1 if -z "$zone_dir/$zone$zone_ext";
|
|
return 0 if !-w "$zone_dir/$zone$zone_ext";
|
|
return 1;
|
|
}
|
|
sub get_zone_array {
|
|
#returns 1 on record exists, 0 on record doesn't exist, -1 on zone exists but isn't writeable, -2 on file exists but isn't a zone, -3 on file doesn't exist
|
|
my $ip = shift;
|
|
my ($rec,$zone) = get_arpa $ip;
|
|
return unless does_zone_exist $ip;
|
|
my $zf = new Net::DNS::ZoneFile("$zone_dir/$zone$zone_ext");
|
|
my @z = $zf->read;
|
|
return @z;
|
|
}
|
|
sub does_record_exist {
|
|
my $ip = shift;
|
|
my ($rec,$zone) = get_arpa $ip;
|
|
my @z = get_zone_array $ip;
|
|
return 0 unless @z;
|
|
foreach(@z) {
|
|
return 1 if $_->name eq "$rec.$zone";
|
|
}
|
|
return 0;
|
|
}
|
|
sub get_rdns {
|
|
my $ip = shift;
|
|
return unless does_record_exist $ip;
|
|
my ($rec,$zone) = get_arpa $ip;
|
|
my @z = get_zone_array $ip;
|
|
foreach(@z) {
|
|
return $_->ptrdname if $_->name eq "$rec.$zone";
|
|
}
|
|
return "";
|
|
}
|
|
sub set_rdns {
|
|
my ($ip,$fqdn) = @_;
|
|
my ($record,$zone) = get_arpa $ip;
|
|
return 1;
|
|
}
|
|
sub generate_zone {
|
|
my ($rec,$zone) = get_arpa shift;
|
|
return 1;
|
|
}
|
|
sub sync_cpanel {
|
|
return 1;
|
|
}
|
|
|
|
#main
|
|
#do argument parsing. all unknown arguments get left in @ARGV so I can `shift`.
|
|
GetOptions
|
|
'reset-hostname=s' => \$def_rdns,
|
|
'dns-server=s' => \$def_dns,
|
|
'v|verify-rdns' => \$verify,
|
|
'f|force' => \$force,
|
|
'r|reset' => \$reset,
|
|
'p|populate' => \$newzone,
|
|
'd|no-sync' => \$nosync,
|
|
's|force-sync' => \$fsync,
|
|
'R|remove-ptr' => \$delptr;
|
|
|
|
#get IP and domain, validate.
|
|
my $ip = shift or nicedie "No IP given!";
|
|
$prefixlen = $1 if $ip =~ s/\/([0-9]+)//; #split off prefixlen (if given) into variable for later use
|
|
nicedie "Invalid IP address '$ip'!" unless validate_ip $ip;
|
|
my $domain = shift;
|
|
nicedie "Invalid FQDN '$domain'!" if defined $domain and !validate_domain $domain;
|
|
$domain =~ s/([a-zA-Z])$/$1./ if defined $domain; #Append final period if it doesn't exist
|
|
|
|
my $testing = 1;
|
|
|
|
#testing data
|
|
if($testing) {
|
|
print "Testing data. IP: $ip";
|
|
(defined $domain) ? print ", Domain: $domain" : print ".";
|
|
print "\n";
|
|
my ($testrec,$testz) = get_arpa $ip;
|
|
print "Authoritative zone (for IPv6, based off prefixlen $prefixlen): $testz, record: $testrec\n";
|
|
print "Zone ";
|
|
for(does_zone_exist $ip) {
|
|
print "doesn't exist" when -2;
|
|
print "exists, but isn't a zone" when -1;
|
|
print "exists, but isn't writeable" when 0;
|
|
default {print "exists and is writeable";}
|
|
}
|
|
print "\nRecord $testrec ";
|
|
(does_record_exist $ip) ? print "exists, and points to ".get_rdns $ip : print "doesn't exist.";
|
|
print "\n";
|
|
}
|