#!/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 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::IP; #for converting IPs to their reverse zones use Data::Dumper; #conf my $def_rdns = 'hosted-by.mycompany.com'; my $def_dns = '8.8.8.8'; #variables for arguments our $verify = ''; our $force = ''; our $reset = ''; our $nosync = ''; our $fsync = ''; our $delptr = ''; our $newzone = ''; our $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) || 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); 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 || 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); my $p = $r->search($rrec, 'PTR'); return 0 unless defined $p; my @res = $p->answer; return 1 unless scalar @res < 1 || $res[0]->ptrdname."." ne $fqdn; return 0; } sub get_rdns { my $ip = shift; return ""; } sub set_rdns { my $ip = shift; my $fqdn = shift; 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 or nicedie "No FQDN given!" unless $fsync || $reset || $delptr; #conditionally allow the user to not specify a fqdn nicedie "Invalid FQDN '$domain'!" if defined $domain && !validate_domain $domain; $domain =~ s/([a-zA-Z])$/$1./; #Append final period if it doesn't exist #main flow