This is a verbatim copy of the files at that stage of the repository that was built from the CVS import. It allows future development to see a bit of recent history, but without carrying around the baggage going back to 1997. If that is really required, git grafts can be used.
583 lines
15 KiB
Perl
Executable file
583 lines
15 KiB
Perl
Executable file
#!/usr/bin/perl
|
|
# Copyright (C) Paul Elliott 2002
|
|
my($copyrighttext) = <<'EOF';
|
|
# Copyright (C) Paul Elliott 2002
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
# SEE COPYING FOR DETAILS
|
|
EOF
|
|
|
|
#modules we use.
|
|
|
|
use Socket;
|
|
use Getopt::Std;
|
|
use Net::DNS;
|
|
use Tie::Syslog;
|
|
use File::Temp qw/ :mktemp /;
|
|
use File::Copy;
|
|
|
|
local($res) = new Net::DNS::Resolver;
|
|
|
|
#dns lookup of IP address.
|
|
#returns ip or errorstring.
|
|
sub gethostaddr($) #get ip address from host
|
|
{
|
|
my($host) = shift;
|
|
$query = $res->search($host);
|
|
if ($query) {
|
|
foreach $rr ($query->answer) {
|
|
next unless $rr->type eq "A";
|
|
print $rr->address, "\n" if $pedebug;
|
|
return $rr->address;
|
|
}
|
|
} else {
|
|
print "query failed: ", $res->errorstring, "\n" if $pedebug;
|
|
return $res->errorstring;
|
|
}
|
|
|
|
}
|
|
|
|
#send messages to syslog
|
|
|
|
sub Log($$)
|
|
{
|
|
if ($log) {
|
|
my($level) = shift;
|
|
my($mess) =shift;
|
|
|
|
tie *MYLOG, 'Tie::Syslog',$level,$0,'pid','unix';
|
|
print MYLOG $mess;
|
|
|
|
untie *MYLOG;
|
|
}
|
|
}
|
|
|
|
#send message to output or syslog
|
|
#and die.
|
|
|
|
sub BadDie($)
|
|
{
|
|
my($myerr) =$!;
|
|
my($mess)=shift;
|
|
|
|
if($log){
|
|
tie *MYLOG, 'Tie::Syslog','local0.err',$0,'pid','unix';
|
|
print MYLOG $mess;
|
|
print MYLOG $myerr;
|
|
|
|
untie *MYLOG;
|
|
|
|
} else {
|
|
print "$mess\n$myerr\n";
|
|
}
|
|
die $mess;
|
|
}
|
|
|
|
sub isIpAddr($) #return true if looks like ip address
|
|
{
|
|
my($ip) = shift;
|
|
return 1 if ( $ip =~ m/$ipOnlyPAT/ );
|
|
return 0;
|
|
}
|
|
sub isHostname($) #return true if looks like ip address
|
|
{
|
|
my($ip) = shift;
|
|
return 1 if ( $ip =~ m/$hostnameOnlyPAT/ );
|
|
return 0;
|
|
}
|
|
|
|
#send commands to chronyc by piping.
|
|
sub chronyc($) #send commands to chronyc
|
|
{
|
|
my($command) = shift;
|
|
my($err) = "/var/tmp/chronyc.log";
|
|
my($chronyP) = "/usr/local/bin/chronyc";
|
|
open(CHRONY, "| $chronyP 1>$err 2>&1");
|
|
|
|
print CHRONY "$passwd$command\n";
|
|
|
|
close(CHRONY);
|
|
|
|
Log('local0.info',"chronyc command issued=$command");
|
|
#look at status lines till return bad.
|
|
open( IN, "<$err");
|
|
my($status);
|
|
while (<IN>) {
|
|
$status = $_;
|
|
|
|
unless ( m/\A200 OK/ ) {
|
|
last;
|
|
}
|
|
|
|
}
|
|
|
|
$status ="" if ( $status =~ m/\A200 OK/ );
|
|
close(IN);
|
|
unlink $err;
|
|
Log('local0.info',"chronyc results=$status");
|
|
return $status;
|
|
|
|
}
|
|
|
|
#common patterns
|
|
|
|
# an ip address patern
|
|
local($ipPAT) = qr/\d{1,3}(?:\.\d{1,3}){3}/;
|
|
# an hostname pattern
|
|
local($hostnamePAT) = qr/\w+(?:\.\w+)*/;
|
|
#line with hostname only
|
|
local($hostnameOnlyPAT) = qr/\A$hostnamePAT\Z/;
|
|
#line with ip address only
|
|
local($ipOnlyPAT) =qr/\A$ipPAT\Z/;
|
|
|
|
#options hash
|
|
my(%opts);
|
|
|
|
|
|
getopts('nuadslPSC', \%opts);
|
|
|
|
local($log) = ( $opts{'l'} ) ? 1 : 0;
|
|
|
|
my($offline) = !( $opts{'n'} ) ;
|
|
my($offlineS) = ( $opts{'n'} ) ? " " : " offline" ;
|
|
|
|
# paul elliotts secret debug var. no one will ever find out about it.
|
|
local($pedebug)=( ($ENV{"PAULELLIOTTDEBUG"}) or ($opts{P}) );
|
|
|
|
if ($opts{C}) {
|
|
|
|
print $copyrighttext;
|
|
exit 0;
|
|
}
|
|
|
|
|
|
print <<"EOF" unless $opts{'S'};
|
|
$0, Copyright (C) 2002 Paul Elliott
|
|
$0 comes with ABSOLUTELY NO WARRANTY; for details
|
|
invoke $0 -C. This is free software, and you are welcome
|
|
to redistribute it under certain conditions; invoke $0 -C
|
|
for details.
|
|
EOF
|
|
|
|
|
|
|
|
local($passwd);
|
|
|
|
# password to send to chronyc
|
|
my($pl) = $ENV{"CHRONYPASSWORD"};
|
|
|
|
#password comand to send to chronyc
|
|
if ( $pl ) {
|
|
$passwd = "password $pl\n";
|
|
} else {
|
|
$passwd = "";
|
|
}
|
|
print "passwd=$passwd\n" if ($pedebug);
|
|
|
|
my(%host2ip);
|
|
|
|
# hash of arrays. host2ip{$host}[0] is ip address for this host
|
|
# host2ip{$host}[1] is rest of paramenters for this host exc offline.
|
|
|
|
#if debuging do chrony.conf in current directory.
|
|
my($listfile) =( ($pedebug) ? "./chrony.conf" : "/etc/chrony.conf") ;
|
|
|
|
# This section reads in the old data about
|
|
# hostnames IP addresses and server parameters
|
|
# data is stored as it would be in chrony.conf
|
|
# file i.e.:
|
|
#># HOSTNAME
|
|
#>server IPADDR minpoll 5 maxpoll 10 maxdelay 0.4 offline
|
|
#
|
|
# the parameter offline is omitted if the -n switch is specified.
|
|
# first parameter is the filename of the file usually
|
|
# is /etc/DNSchrony.conf
|
|
# this is where we store the list of DNS hosts.
|
|
# hosts with static IP address shold be kept in chrony.conf
|
|
|
|
# this is header that marks dnyamic host section
|
|
my($noedithead)=<<'EOF';
|
|
## DNSchrony dynamic dns server section. DO NOT EDIT
|
|
## per entry FORMAT:
|
|
## |--------------------------------------------|
|
|
## |#HOSTNAME |
|
|
## |server IP-ADDRESS extra-params [ offline ] |
|
|
## |--------------------------------------------|
|
|
EOF
|
|
#patern that recognizes above.
|
|
my($noeditheadPAT) =
|
|
qr/\#\#\s+DNSchrony\s+dynamic\s+dns\s+server\s+section\.\s+DO\s+NOT\s+EDIT\s*/;
|
|
|
|
#end of header marker.
|
|
my($noeditheadend)=<<'EOF';
|
|
## END OF DNSchrony dynamic dns server section.
|
|
EOF
|
|
|
|
#pattern that matches above.
|
|
my($noeditheadendPAT)=
|
|
qr/\#\#\s+END\s+OF\s+DNSchrony\s+dynamic\s+dns\s+server\s+section.\s*/;
|
|
|
|
#array to hold non dns portion of chrony.conf
|
|
my(@chronyDconf);
|
|
|
|
|
|
my($ip);
|
|
my($rest);
|
|
my($host);
|
|
|
|
# for each entry in the list of hosts....
|
|
open(READIN, "<$listfile") or BadDie("Can not open $listfile");
|
|
|
|
# read till dynamic patern read save in @chronyDconf
|
|
|
|
while ( <READIN> ) {
|
|
|
|
my($line) = $_;
|
|
|
|
last if ( m/\A$noeditheadPAT\Z/ );
|
|
|
|
push(@chronyDconf,$line);
|
|
|
|
}
|
|
|
|
while ( <READIN> ) {
|
|
|
|
#end loop when end of header encountered
|
|
last if ( m/\A$noeditheadendPAT/ );
|
|
|
|
# parse the line giving ip address, extra pamamters, and host
|
|
#do host comment line first
|
|
($host) = m{
|
|
\A\#\s*
|
|
($hostnamePAT)
|
|
\s*\z
|
|
}xio;
|
|
|
|
#no match skip this line.
|
|
next unless ( $host );
|
|
|
|
# read next line
|
|
$_ = <READIN>;
|
|
|
|
# parse out ip address extra parameters.
|
|
($ip,$rest) =
|
|
m{
|
|
\A
|
|
\s*
|
|
server #server comand
|
|
\s+
|
|
($ipPAT) #ip address
|
|
(?ixo: \s )
|
|
\s*
|
|
(
|
|
(?(?!
|
|
(?iox: offline )? #skip to offline #
|
|
\s* #or #
|
|
\Z
|
|
).)*
|
|
)
|
|
(?ixo:
|
|
\s*
|
|
(?ixo: offline )? #consume to #
|
|
\s*
|
|
\Z
|
|
)
|
|
}xio ;
|
|
|
|
#if failure again.
|
|
next unless ( $ip );
|
|
|
|
$rest =~ s/\s*\z//; #remove trail blanks
|
|
#from parameters
|
|
# store the data in the list
|
|
# key is host name value is
|
|
# array [0] is ip address
|
|
# [1] is other parameters
|
|
$host2ip{$host} = [$ip,$rest] ;
|
|
print "ip=$ip rest=$rest host=$host<\n" if $pedebug;
|
|
|
|
}
|
|
#read trailing line into @chronyDconf
|
|
while ( <READIN> ) {
|
|
|
|
push(@chronyDconf,$_);
|
|
|
|
}
|
|
|
|
close(READIN) or BadDie("can not close $listfile");
|
|
|
|
#if the add command:
|
|
# command can be HOST=IPADDRESS OTHER_PARAMETERS
|
|
# means add the server trust the ip address geven with out a dns lookup
|
|
# good for when dns is down but we know the ip addres
|
|
# or
|
|
# HOST OTHER_PARAMETERS
|
|
#we lookup the ip address with dns.
|
|
|
|
if ($opts{'a'}) {
|
|
my($param)= shift;
|
|
|
|
|
|
# parse the param is it hostname
|
|
if ( ($host,$ip) = $param =~ m/\A($hostnamePAT)=($ipPAT)\Z/ ) {
|
|
printf "ip=$ip host=$host\n" if ($pedebug);
|
|
} else {
|
|
|
|
$host = $param;
|
|
|
|
# get the ip address
|
|
$ip = gethostaddr($host);
|
|
|
|
if ( ! isIpAddr($ip) or ! isHostname($host) ) {
|
|
print "query failed: ", $ip, "host=$host\n" if $pedebug;
|
|
exit 1;
|
|
}
|
|
}
|
|
printf "ip=$ip host=$host\n" if ($pedebug);
|
|
|
|
# add the server using chronyc
|
|
my($status) = chronyc("add server $ip $rest");
|
|
if ($status) { #chronyc error
|
|
print "chronyc failed, status=$status\n";
|
|
exit 1;
|
|
}
|
|
|
|
# get rest of arguements
|
|
$rest = join( ' ', @ARGV);
|
|
print "rest=$rest\n" if ($pedebug);
|
|
|
|
#save node in hash
|
|
$host2ip{$host} = [$ip,$rest] ;
|
|
print "ip=$ip rest=$rest host=$host<\n" if $pedebug;
|
|
|
|
}
|
|
|
|
#delete command if arguement is ip address
|
|
#just delete it
|
|
#if a hostname look it up
|
|
#then delete it.
|
|
|
|
if ($opts{'d'}) {
|
|
$host = shift;
|
|
|
|
#get host name is it ap address
|
|
if ( isIpAddr($host) ) { # if ip address
|
|
my($hostIT);
|
|
my($found) =0;
|
|
foreach $hostIT (keys(%host2ip) ) { #search for match
|
|
if ( $host2ip{$hostIT}[0] eq $host) {
|
|
$found=1; #record match
|
|
}
|
|
} #end of search
|
|
if ($found) { #if match found
|
|
my($status) = chronyc("delete $host"); #chronyc
|
|
if ($status) { #chronyc error
|
|
print "chronyc failed, status=$status\n";
|
|
exit 1;
|
|
} else { #reiterate
|
|
foreach $hostIT (keys(%host2ip) ) {
|
|
if ( $host2ip{$hostIT}[0] eq $host) {
|
|
delete $host2ip{$hostIT}; #deleting match hosts
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
} else { #else not ip address
|
|
#must be hostname
|
|
if ( ! $host2ip{$host} ) {
|
|
print "No such host as $host listed\n";
|
|
exit 1;
|
|
}
|
|
#get ip address
|
|
$ip=gethostaddr($host);
|
|
if ( ! isIpAddr($ip) ) { #no ip address
|
|
print "query failed: ", $ip, "\n" if $pedebug;
|
|
exit 1;
|
|
}
|
|
|
|
printf "ip=$ip host=$host\n" if ($pedebug);
|
|
|
|
my($listed_host_ip) = $host2ip{$host}[0]; # get the ip address saved
|
|
|
|
if ( $ip ne $listed_host_ip) {
|
|
print
|
|
"Info: listed host ip=>$listed_host_ip".
|
|
"< is different from DNS ip=>$ip<\n";
|
|
$ip = $listed_host_ip;
|
|
}
|
|
|
|
# delete the server
|
|
my($status) = chronyc("delete $listed_host_ip\n");
|
|
|
|
if ($status) {
|
|
print "chronyc failed, status=$status\n";
|
|
exit 1;
|
|
}
|
|
#delete table entry
|
|
delete$host2ip{$host};
|
|
}
|
|
|
|
}
|
|
|
|
#update for each host who's dns ip address has changed
|
|
#delete the old server and add the new. update the record.
|
|
if ($opts{'u'}) {
|
|
my($command);
|
|
|
|
my(%prospective); # store new IP address we
|
|
#are thinking of changing.
|
|
|
|
Log('local0.info',
|
|
"Now searching for modified DNS entries.");
|
|
|
|
foreach $host (keys(%host2ip)) { #for each listed host
|
|
my($old_ip) = $host2ip{$host}[0]; #get old ip
|
|
$rest = $host2ip{$host}[1]; #extra params
|
|
|
|
$ip = gethostaddr($host); #get new ip from dns
|
|
#if error
|
|
if ( ! isIpAddr($ip) or ! isHostname($host) ) {
|
|
print "query failed: ", $ip, "host=$host\n";
|
|
|
|
Log('local0.err',"query failed: ". $ip . "host=$host");
|
|
|
|
exit 1;
|
|
}
|
|
|
|
next if($ip eq $old_ip); #if ip not changed, skip
|
|
|
|
Log('local0.info',"Ip address for $host has changed. Old IP address=".
|
|
"$old_ip, new IP address=$ip");
|
|
# add command to delete old host, add the new.
|
|
$command = $command . "delete $old_ip\n" .
|
|
"add server $ip $rest\n";
|
|
|
|
# we are now thinking about changing this host ip
|
|
$prospective{$host} = [$ip,$rest];
|
|
}
|
|
# submit all the accumulated chronyc commands if any.
|
|
if ($command) {
|
|
$status = chronyc($command);
|
|
if ($status) {
|
|
print "chronyc failed, status=$status\n";
|
|
Log('local0.err',"query failed: ". $ip . "host=$host");
|
|
exit 1;
|
|
}
|
|
} else { #if no commands exit
|
|
exit 0; #because no rewrite of file needed
|
|
}
|
|
|
|
#copy prospective modifications back into main table.
|
|
#we now know that all these mods were done with chronyc
|
|
foreach $host (keys(%prospective)) {
|
|
my($ip) = $prospective{$host}[0];
|
|
$rest = $prospective{$host}[1];
|
|
$host2ip{$host} = [$ip,$rest];
|
|
}
|
|
}
|
|
|
|
#starting for each entry we have read in from the old list
|
|
# add the server in chronyc
|
|
# this option is seldom used.
|
|
|
|
if ($opts{'s'}) {
|
|
my($command)="";
|
|
|
|
foreach $host (keys(%host2ip)) {
|
|
$command = $command . "add server $host2ip{$host}[0] ".
|
|
"$host2ip{$host}[1]\n";
|
|
}
|
|
my($status) = chronyc($command);
|
|
if ($status) {
|
|
print "chronyc failed, status=$status\n";
|
|
exit 1;
|
|
}
|
|
|
|
}
|
|
# write out the data file in format
|
|
#># HOSTNAME
|
|
#>server IPADDRESS extra parameters [offline]
|
|
# offline is omitted if -n switch is specified.
|
|
|
|
my(@value);
|
|
my($such);
|
|
{
|
|
# to start out we write to temporary file.
|
|
(my($writeout) , my($outname)) = mkstemp( "${listfile}.outXXXXXXX");
|
|
|
|
$outname or BadDie("can not open for $listfile");
|
|
|
|
|
|
# save the chrony.conf part!
|
|
# and write the DYNAMIC header
|
|
print $writeout @chronyDconf, $noedithead;
|
|
|
|
|
|
# for each entry
|
|
foreach $host (keys(%host2ip) ){
|
|
|
|
#write the record
|
|
|
|
# write the comment that indicates the hostname
|
|
# and the server command.
|
|
print $writeout
|
|
"\# $host\nserver $host2ip{$host}[0] $host2ip{$host}[1]${offlineS}\n" ;
|
|
|
|
print
|
|
"server $host2ip{$host}[0] $host2ip{$host}[1]${offlineS}\# $host\n"
|
|
if $pedebug;
|
|
|
|
}
|
|
|
|
#WRITE THE end of dnyamic marker comment
|
|
print $writeout $noeditheadend;
|
|
|
|
# close the output file which was a temporary file.
|
|
close($writeout) or BadDie("can not close $outname");
|
|
|
|
# we now begin a intracate dance to make the the temporary
|
|
# the main chrony.conf
|
|
#
|
|
# if there is a chrony.conf.BAK save it to a temporary.
|
|
# rename chrony.conf to chrony.conf.BAK
|
|
# rename the temporary to chrony.conf
|
|
# if there already was a chrony.conf.BAK, unlink the copy of this.
|
|
|
|
my($backname) = "$listfile\.BAK";
|
|
my($backplain) = ( -f $backname );
|
|
my($saveback);
|
|
#if chrony.conf.BAK exists rename to a temporary.
|
|
if ($backplain ) {
|
|
|
|
$saveback = mktemp("${backname}.bakXXXXXXX");
|
|
move($backname,$saveback) or
|
|
BadDie "unable to move $backname to $savename";
|
|
|
|
}
|
|
|
|
# rename old chrony.conf to chrony.conf.BAK
|
|
move($listfile,$backname) or
|
|
BadDie "unable to move $listfile to $backname";
|
|
|
|
# rename our output to chrony.conf
|
|
move($outname,$listfile) or
|
|
BadDie "unable to move $outname to $listfile";
|
|
|
|
#if there was a temporary chrony.conf.BAK that we saved to temp
|
|
#unlink it
|
|
unlink($saveback) or BadDie "unable to unlink $saveback" if($backplain);
|
|
|
|
}
|