83 |
rodolico |
1 |
#! /usr/bin/env perl
|
|
|
2 |
|
|
|
3 |
use warnings;
|
|
|
4 |
use strict;
|
|
|
5 |
|
|
|
6 |
use Sys::Syslog;
|
|
|
7 |
use version
|
|
|
8 |
our $VERSION = version->declare( '2.0.0' );
|
|
|
9 |
|
|
|
10 |
# Copyright (c) 2021, R. W. Rodolico
|
|
|
11 |
#
|
|
|
12 |
# Redistribution and use in source and binary forms, with or without
|
|
|
13 |
# modification, are permitted provided that the following conditions
|
|
|
14 |
# are met:
|
|
|
15 |
#
|
|
|
16 |
# Redistributions of source code must retain the above copyright
|
|
|
17 |
# notice, this list of conditions and the following disclaimer.
|
|
|
18 |
#
|
|
|
19 |
# Redistributions in binary form must reproduce the above
|
|
|
20 |
# copyright notice, this list of conditions and the following
|
|
|
21 |
# disclaimer in the documentation and/or other materials provided
|
|
|
22 |
# with the distribution.
|
|
|
23 |
#
|
|
|
24 |
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
|
25 |
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
|
26 |
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
|
27 |
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
|
28 |
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
|
29 |
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
|
30 |
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
|
31 |
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
|
32 |
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
|
33 |
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
|
34 |
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
|
35 |
# POSSIBILITY OF SUCH DAMAGE.[9]
|
|
|
36 |
|
|
|
37 |
# called as an ssh command
|
|
|
38 |
# ssh my_server hostname [ip]
|
|
|
39 |
# where hostname is the host name to be processed
|
|
|
40 |
# if IP is not given, will take the source IP from the ssh connection
|
|
|
41 |
# Can also be called from command line as
|
|
|
42 |
# ./updatedns hostname ip
|
|
|
43 |
|
|
|
44 |
# version 2.0.0
|
|
|
45 |
# 20210417 RWR
|
|
|
46 |
# major modification to use one script for local and remote
|
|
|
47 |
# also, added ability to set up a configuration file
|
|
|
48 |
|
|
|
49 |
|
|
|
50 |
# we'll store the configuration here, so we can load it easily from
|
|
|
51 |
# a config file, if it exists
|
|
|
52 |
my %configuration = (
|
|
|
53 |
'local' => 1,
|
|
|
54 |
'domain' => '',
|
|
|
55 |
'server' => '',
|
|
|
56 |
'keyfile' => '',
|
|
|
57 |
'ttl' => 3600
|
|
|
58 |
);
|
|
|
59 |
|
|
|
60 |
|
|
|
61 |
######################################################
|
|
|
62 |
# you should not have to change anything below here. #
|
|
|
63 |
######################################################
|
|
|
64 |
|
|
|
65 |
# get script path so we can find the auxiliary files
|
|
|
66 |
use Cwd qw(abs_path);
|
|
|
67 |
use File::Basename;
|
|
|
68 |
my $appDir = dirname(abs_path(__FILE__)) . '/';
|
|
|
69 |
|
|
|
70 |
my $statusFile = $appDir . 'updatedns.status'; # stores current status of known entries
|
|
|
71 |
my $template = $appDir . 'updatedns.template'; # a template used for commands to nsupdate
|
|
|
72 |
# If local set, use -l parameter, otherwise, use the key file
|
84 |
rodolico |
73 |
my $nsupdate = `which nsupdate`; chomp $nsupdate;
|
83 |
rodolico |
74 |
my $configFile = $appDir . 'updatedns.conf';
|
|
|
75 |
|
|
|
76 |
my $hostname;
|
|
|
77 |
my $realIP;
|
|
|
78 |
my %entries;
|
|
|
79 |
|
|
|
80 |
# this reads an INI type file in the form
|
|
|
81 |
# key:value
|
|
|
82 |
# where : is any delimiter
|
|
|
83 |
# it then merges it into %$hash, overwriting anything with the same
|
|
|
84 |
# key with a new value
|
|
|
85 |
sub readFile {
|
|
|
86 |
my %temp;
|
|
|
87 |
my ($filename,$hash,$delimiter ) = @_;
|
|
|
88 |
$delimiter = ':' unless $delimiter;
|
|
|
89 |
return unless -f $filename;
|
|
|
90 |
open CONF,"<$filename" or die "Could not read existing configuration file $filename: $!\n";
|
|
|
91 |
# turn the delimited file into a hash
|
|
|
92 |
chomp( %temp = map { split $delimiter } grep{ !/^#/ } my (@a) = <CONF> );
|
|
|
93 |
close CONF;
|
|
|
94 |
# merge into existing hash, leaving items not common alone
|
|
|
95 |
@$hash{keys %temp} = values %temp;
|
|
|
96 |
}
|
|
|
97 |
|
|
|
98 |
# merge the template file with our values and save it to a file
|
|
|
99 |
# in /tmp/hostname.nsupdate (overwriting if necessary)
|
|
|
100 |
# Call nsupdate with the filename as a parameter.
|
|
|
101 |
# leaves the file in /tmp for debugging
|
|
|
102 |
sub doUpdateNS {
|
|
|
103 |
my ($hostname, $ip) = @_;
|
|
|
104 |
my $nsupdateFileName = "/tmp/$hostname.nsupdate";
|
|
|
105 |
# grab the template
|
|
|
106 |
open TEMPLATE, "<$template" or die "could not open template $template for read: $!\n";
|
|
|
107 |
my $template = join( '', <TEMPLATE> );
|
|
|
108 |
close TEMPLATE;
|
|
|
109 |
# now, replace our keys with our current values
|
|
|
110 |
$template =~ s/\{hostname\}/$hostname/gi;
|
|
|
111 |
$template =~ s/\{server\}/$configuration{'server'}/gi;
|
|
|
112 |
$template =~ s/\{zone\}/$configuration{'domain'}/gi;
|
|
|
113 |
$template =~ s/\{ttl\}/$configuration{'ttl'}/gi;
|
|
|
114 |
$template =~ s/\{ip\}/$ip/gi;
|
|
|
115 |
# save the file
|
|
|
116 |
open OUTPUT, ">$nsupdateFileName" or die "Could not create $nsupdateFileName: $!\n";
|
|
|
117 |
print OUTPUT $template;
|
|
|
118 |
close OUTPUT;
|
|
|
119 |
# execute nsupdate, and return the results to the caller
|
|
|
120 |
return `$nsupdate $nsupdateFileName`;
|
|
|
121 |
}
|
|
|
122 |
|
|
|
123 |
&readFile( $configFile, \%configuration, '=' );
|
|
|
124 |
|
84 |
rodolico |
125 |
# prepend 'server ' to the server command if it exists and we are not local
|
|
|
126 |
$configuration{'server'} = "server $configuration{server}" if $configuration{'server'} and not $configuration{'local'};
|
|
|
127 |
# and set nsupdate for local or remote
|
|
|
128 |
$nsupdate .= $configuration{'local'} ? ' -l ' : " -k $configuration{keyfile} ";
|
|
|
129 |
|
|
|
130 |
|
83 |
rodolico |
131 |
#foreach my $key ( sort keys %configuration ) {
|
|
|
132 |
# print "$key\t$configuration{$key}\n";
|
|
|
133 |
#}
|
84 |
rodolico |
134 |
# die;
|
83 |
rodolico |
135 |
|
|
|
136 |
# user should send hostname as a parameter on the command
|
|
|
137 |
# user may also send the IP
|
|
|
138 |
|
|
|
139 |
if ( defined $ENV{'SSH_ORIGINAL_COMMAND'} ) {
|
|
|
140 |
($hostname,$realIP) = split( ' ', $ENV{'SSH_ORIGINAL_COMMAND'});
|
|
|
141 |
$realIP = $ENV{'SSH_CLIENT'} unless $realIP;
|
|
|
142 |
} else {
|
|
|
143 |
$hostname = shift;
|
|
|
144 |
$realIP = shift;
|
|
|
145 |
}
|
|
|
146 |
|
|
|
147 |
die "Invalid call\n" unless $hostname and $realIP;
|
|
|
148 |
# validate IP is valid IPv4
|
|
|
149 |
# NOT a very good regex for that, but ...
|
|
|
150 |
$realIP =~ m/^([\d.]+)/;
|
|
|
151 |
$realIP = $1;
|
|
|
152 |
|
|
|
153 |
#die "$hostname\t$realIP\n";
|
|
|
154 |
|
|
|
155 |
# Start logging to syslog
|
|
|
156 |
openlog('updatedns', 'cons,pid', 'user');
|
|
|
157 |
|
|
|
158 |
unless ( $hostname ) {
|
|
|
159 |
syslog( 'FATAL','%s',"no hostname passed in from IP $realIP" );
|
|
|
160 |
die "Invalid Invocation: hostname\n" ;
|
|
|
161 |
}
|
|
|
162 |
|
|
|
163 |
# validate the hostname is part of the $domain
|
|
|
164 |
if ( $hostname !~ m/[a-z0-9-]\.$configuration{'domain'}/ ) {
|
|
|
165 |
syslog( 'warning', '%s', "Attempt to set incoming server name to invalid host [$hostname]");
|
|
|
166 |
exit;
|
|
|
167 |
}
|
|
|
168 |
# slup in the status file
|
|
|
169 |
|
|
|
170 |
&readFile( $statusFile,\%entries, "\t" );
|
|
|
171 |
|
|
|
172 |
# is the entry for $hostname alread set? Do nothing except log it
|
|
|
173 |
if ( ( exists $entries{$hostname} ) && ( $entries{$hostname} eq $realIP ) ) {
|
|
|
174 |
print "$configuration{'domain'}: Already set to the correct IP: [$realIP]\n";
|
|
|
175 |
syslog('info', '%s', "Already set to the correct IP, [$hostname] = [$realIP]");
|
|
|
176 |
} else { # we have a new IP, or possibly a new hostname, so set it
|
|
|
177 |
$entries{$hostname} = $realIP;
|
|
|
178 |
open STATUS, ">$statusFile" or die "could not open $statusFile: $!\n";
|
|
|
179 |
foreach my $key ( keys %entries ) {
|
|
|
180 |
print STATUS "$key\t$entries{$key}\n";
|
|
|
181 |
} # foreach
|
|
|
182 |
my $output = &doUpdateNS( $hostname, $realIP );
|
|
|
183 |
print "$hostname updated to $realIP, results are\n$output\n";
|
|
|
184 |
syslog('info', '%s', "$hostname updated to $realIP" );
|
|
|
185 |
}
|
|
|
186 |
closelog();
|
|
|
187 |
|
|
|
188 |
1;
|