Rev 84 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed
#! /usr/bin/env perl
use warnings;
use strict;
use Sys::Syslog;
use version
our $VERSION = version->declare( '2.0.0' );
# Copyright (c) 2021, R. W. Rodolico
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.[9]
# called as an ssh command
# ssh my_server hostname [ip]
# where hostname is the host name to be processed
# if IP is not given, will take the source IP from the ssh connection
# Can also be called from command line as
# ./updatedns hostname ip
# version 2.0.0
# 20210417 RWR
# major modification to use one script for local and remote
# also, added ability to set up a configuration file
# we'll store the configuration here, so we can load it easily from
# a config file, if it exists
my %configuration = (
'local' => 1,
'domain' => '',
'server' => '',
'keyfile' => '',
'ttl' => 3600
);
######################################################
# you should not have to change anything below here. #
######################################################
# get script path so we can find the auxiliary files
use Cwd qw(abs_path);
use File::Basename;
my $appDir = dirname(abs_path(__FILE__)) . '/';
my $statusFile = $appDir . 'updatedns.status'; # stores current status of known entries
my $template = $appDir . 'updatedns.template'; # a template used for commands to nsupdate
# If local set, use -l parameter, otherwise, use the key file
my $nsupdate = `which nsupdate`; chomp $nsupdate;
my $configFile = $appDir . 'updatedns.conf';
my $hostname;
my $realIP;
my %entries;
# this reads an INI type file in the form
# key:value
# where : is any delimiter
# it then merges it into %$hash, overwriting anything with the same
# key with a new value
sub readFile {
my %temp;
my ($filename,$hash,$delimiter ) = @_;
$delimiter = ':' unless $delimiter;
return unless -f $filename;
open CONF,"<$filename" or die "Could not read existing configuration file $filename: $!\n";
# turn the delimited file into a hash
chomp( %temp = map { split $delimiter } grep{ !/^#/ } my (@a) = <CONF> );
close CONF;
# merge into existing hash, leaving items not common alone
@$hash{keys %temp} = values %temp;
}
# merge the template file with our values and save it to a file
# in /tmp/hostname.nsupdate (overwriting if necessary)
# Call nsupdate with the filename as a parameter.
# leaves the file in /tmp for debugging
sub doUpdateNS {
my ($hostname, $ip) = @_;
my $nsupdateFileName = "/tmp/$hostname.nsupdate";
# grab the template
open TEMPLATE, "<$template" or die "could not open template $template for read: $!\n";
my $template = join( '', <TEMPLATE> );
close TEMPLATE;
# now, replace our keys with our current values
$template =~ s/\{hostname\}/$hostname/gi;
$template =~ s/\{server\}/$configuration{'server'}/gi;
$template =~ s/\{zone\}/$configuration{'domain'}/gi;
$template =~ s/\{ttl\}/$configuration{'ttl'}/gi;
$template =~ s/\{ip\}/$ip/gi;
# save the file
open OUTPUT, ">$nsupdateFileName" or die "Could not create $nsupdateFileName: $!\n";
print OUTPUT $template;
close OUTPUT;
# execute nsupdate, and return the results to the caller
return `$nsupdate $nsupdateFileName`;
}
&readFile( $configFile, \%configuration, '=' );
# prepend 'server ' to the server command if it exists and we are not local
$configuration{'server'} = "server $configuration{server}" if $configuration{'server'} and not $configuration{'local'};
# and set nsupdate for local or remote
$nsupdate .= $configuration{'local'} ? ' -l ' : " -k $configuration{keyfile} ";
#foreach my $key ( sort keys %configuration ) {
# print "$key\t$configuration{$key}\n";
#}
# die;
# user should send hostname as a parameter on the command
# user may also send the IP
if ( defined $ENV{'SSH_ORIGINAL_COMMAND'} ) {
($hostname,$realIP) = split( ' ', $ENV{'SSH_ORIGINAL_COMMAND'});
$realIP = $ENV{'SSH_CLIENT'} unless $realIP;
} else {
$hostname = shift;
$realIP = shift;
}
die "Invalid call\n" unless $hostname and $realIP;
# validate IP is valid IPv4
# NOT a very good regex for that, but ...
$realIP =~ m/^([\d.]+)/;
$realIP = $1;
#die "$hostname\t$realIP\n";
# Start logging to syslog
openlog('updatedns', 'cons,pid', 'user');
unless ( $hostname ) {
syslog( 'FATAL','%s',"no hostname passed in from IP $realIP" );
die "Invalid Invocation: hostname\n" ;
}
# validate the hostname is part of the $domain
if ( $hostname !~ m/[a-z0-9-]\.$configuration{'domain'}/ ) {
syslog( 'warning', '%s', "Attempt to set incoming server name to invalid host [$hostname]");
exit;
}
# slup in the status file
&readFile( $statusFile,\%entries, "\t" );
# is the entry for $hostname alread set? Do nothing except log it
if ( ( exists $entries{$hostname} ) && ( $entries{$hostname} eq $realIP ) ) {
print "$configuration{'domain'}: Already set to the correct IP: [$realIP]\n";
syslog('info', '%s', "Already set to the correct IP, [$hostname] = [$realIP]");
} else { # we have a new IP, or possibly a new hostname, so set it
$entries{$hostname} = $realIP;
open STATUS, ">$statusFile" or die "could not open $statusFile: $!\n";
foreach my $key ( keys %entries ) {
print STATUS "$key\t$entries{$key}\n";
} # foreach
my $output = &doUpdateNS( $hostname, $realIP );
print "$hostname updated to $realIP, results are\n$output\n";
syslog('info', '%s', "$hostname updated to $realIP" );
}
closelog();
1;