#!/usr/bin/env perl ########################################################## # Written 9/25/2008 and released under the GNU/GPLv2 ## # (c) Jeff Schroeder (jeffschroeder@computer.org) # # ########################################################## # # # # # nsupdate.pl - Dynamic zones are a pain to manage when # # # you need to update or add a lot of hosts.# # # This is an attempt at making it easier # # # # # ########################################################## # $Id: nsupdate.pl 68104 2008-10-02 15:55:15Z jschroeder $ use strict; use File::Basename qw(basename); #use diagnostics; # Like warnings with way more cowbell use Getopt::Long; sub usage { my $name = basename($0); print STDERR << "EOF"; Usage: $name [options] -f [hostlist] Create nsupdate compatible dns command files -f, --file [hostlist] - Mandatory argument specifying host list file -v, --verbose - Enable verbose output -u, --update - Run nsupdate and load the generated file. This argument requires -k. -d, --delete - Delete host and do not re-add it -k, --keyfile [keyfile] - Key file to use when auto-updating dns The hostlist should be whitespace seperated or csv with one host per line: core001.core.nyc01.mtt 192.168.101.101 core002.core.nyc01.mtt 192.168.101.102 core001.core.chi01.mtt 192.168.102.101 EOF exit 1; } # Read in a file full of hostname <---> ip address pairings # The file format should look something like: # # # Even a 1000 line file shouldn't take up enough memory to worry # about putting the whole thing in a hash sub read_file { my $file = shift; if ( -e "$file" ) { open (CONFIG, "$file"); my %hostlist = (); while () { next if $_ !~ /^[a-zA-Z]/; if ($_ =~ /^([\w\.]+)(\s+|,)([0-9\.]+$)/) { my $host = $1; my $ip = $3; $hostlist{$host} = $ip; } } close(CONFIG); return %hostlist; } else { die "ERROR: Can't open $file: $!\n"; } } sub header { my $zone = forward_zone(shift); my $server = "localhost"; print "server $server"; print "zone $zone"; } # Very ignorant mini-function to reverse an ip address sub reverse_ip { my $ip = shift; my $rev_ip = join('.', reverse(split(/\./, $ip)), "in-addr", "arpa"); return $rev_ip; } sub forward_zone { my @hostname = split(/\./, shift); my $domainname = join('.', @hostname[1..$#hostname]); return $domainname; } sub reverse_zone { my @rev_ip = split(/\./, shift); my $rev_zone = join('.', @rev_ip[1..$#rev_ip]); return $rev_zone; } sub forward_nsupdate { my $host = shift; my $ip = shift; my $action = shift; my $zone = forward_zone($host); print "zone $zone\n"; print "update delete $host\n"; print "send\n"; # Re-add the host unless told not to if (! defined $action) { print "update add $host 3000 A $ip\n"; print "send\n"; } } sub reverse_nsupdate { my $host = shift; my $rev_ip = shift; my $action = shift; my $rev_zone = reverse_zone($rev_ip); print "zone $rev_zone\n"; print "update delete $rev_ip\n"; print "send\n"; # Re-add the host unless told not to if (! defined $action) { print "update add $rev_ip 3000 PTR $host.\n"; print "send\n"; } } ##### MAIN PROGRAM STARTS HERE ##### my ($file, $verbose, $debug, $update, $delete, $reverse, $forward, $keyfile, $help); # FIXME: Add (-u) update support that forks nsupdate with the created file # FIXME: Add support for reading the keyfile and using it for nsupdate GetOptions( 'f|file=s' => \$file, # Whitespace seperated or csv host list file 'v|verbose' => \$verbose, # Increase verbosity (useful for debugging) 'u|update' => \$update, # Fork out to nsupdate and update dns 'd|delete' => \$delete, # Delete and do not readd entries 'k|keyfile=s' => \$keyfile, # DNS update key. Required with -u 'h|help' => \$help ); # Make sure we were given a sane set of arguments before doing anything # FIXME: Fully flesh out argument sanity checking if (not $file) { usage(); } usage if defined $help; # Suck the host list file into a big hash to play with my %hosts = read_file($file); my $current_zone; die "ERROR: No hosts found or wrong format in $file!\n" if not %hosts; print "server localhost\n"; foreach my $key (sort keys %hosts) { my $host = $key; my $ip = $hosts{$host}; my $rev_ip = reverse_ip($ip); my $zone = forward_zone($host); my $rev_zone = reverse_zone($rev_ip); $current_zone = $zone if (not $current_zone); if ($current_zone) { $current_zone = $zone; } if ($verbose) { warn "=========================DEBUG=========================\n"; warn "IP:\t\t$ip\n"; warn "HOST:\t\t$host\n"; warn "REV IP:\t\t$rev_ip\n"; warn "REV ZONE:\t$rev_zone\n"; warn "ZONE:\t\t$zone\n"; warn "=========================DEBUG=========================\n"; } # FIXME: Use %hosts hash to batch forward and reverse updates if (defined $delete) { forward_nsupdate($host, $ip, "delete"); reverse_nsupdate($host, $rev_ip, "delete"); } else { forward_nsupdate($host, $ip); reverse_nsupdate($host, $rev_ip); } }