Subversion Repositories sysadmin_scripts

Rev

Go to most recent revision | 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 = '/usr/bin/nsupdate ' . ( $configuration{'local'} ? '-l ' : " -k $configuration{keyfile} " );
my $configFile = $appDir . 'updatedns.conf';

my $hostname;
my $realIP;
my %entries;

# 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'};

# 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, '=' );

#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;