#!/usr/bin/perl -w # # dnsupdate.pl - An automatic IP address update program for dynamic DNS. # Copyright (C) 2003, 2004 Masaki Suzuki. All rights reserved. # # 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. # # $Id: dnsupdate.pl,v 0.44 2004/06/24 00:45:22 masaki Exp $ require 5.004; use strict; $| = 1; my $NAME = $0; substr($NAME, 0, rindex( $0, '/') + 1) = ''; my $VERSION = sprintf("%d.%d", '$Revision: 0.44 $' =~ /(\d+)\.(\d+)/); my $DATE = sprintf("%s", '$Date: 2004/06/24 00:45:22 $' =~ /^\S+ (.+) \S+$/); my $CONFIG_FILE = '/etc/dnsupdate/dnsupdate.conf'; my $LOG_FILE = '/var/log/dnsupdate.log'; my $PID_FILE = '/var/run/dnsupdate.pid'; my $TRUE = 1; my $ERROR = 0; my $SUCCESS = 1; my ($add_addr, $del_addr, $add_alias, $del_alias, $config_file, $daemon, $interval, $debug, $force, $help, $key, $kill, $log, $log_file, $ttl, $quiet, $reload, $restart, $version, $pid_file, $saved_addr, %param, $domain, $cgi_host, $cgi_path, $nameserver, @addr, $key_file, $key_name, $secret); sub OptionError($); sub PrintHelp(); sub ReloadConfig(); sub TerminateDaemon(); sub ParseConfig(); sub SetPidFile(); sub SetInterval(); sub ReadSavedAddress(); sub GetCurrentAddress(); sub FindNameServer(); sub GetAddress(); sub CheckAddress(); sub ReadKey(); sub SetTTL(); sub UpdateRecord(); sub Abort(); sub PrintMessage($); while ($ARGV[0]) { my $arg = shift; if ($arg eq '--add' || $arg eq '--del') { my $option = $arg; $arg = shift; if (defined $arg && $arg =~ /^[^\-]/) { if ($arg =~ /^(\d{1,3}\.){3}\d{1,3}$/) { if ($option eq '--add') { $add_addr = $arg; } elsif ($option eq '--del') { $del_addr = $arg; } } else { OptionError("Option `$option' with an invalid IP address"); } } else { OptionError("Option `$option' with no IP address"); } next; } if ($arg eq '--addalias' || $arg eq '--delalias') { my $option = $arg; $arg = shift; if (defined $arg && $arg =~ /^[^\-]/) { $arg =~ s/^(.+[a-zA-Z0-9])\.+$/$1/; if ($arg =~ /^[a-zA-Z0-9\-]+\.([a-zA-Z0-9\-\.]+)$/) { if ($option eq '--addalias') { $add_alias = $arg; } elsif ($option eq '--delalias') { $del_alias = $arg; } } else { OptionError("Option `$option' with an invalid domain name"); } } else { OptionError("Option `$option' with no domain name"); } next; } if ($arg eq '-c' || $arg eq '--config') { my $option = $arg; $arg = shift; if (defined $arg && $arg =~ /^[^\-]/) { $config_file = $arg; } else { OptionError("Option `$option' with no config file"); } next; } if ($arg eq '-d' || $arg eq '--daemon') { my $option = $arg; $arg = shift; if (defined $arg) { if ($arg =~ /^[^\-]/) { if ($arg =~ /^\d+$/) { $interval = $arg; } else { OptionError("Option `$option' with an invalid update interval"); } } else { unshift @ARGV, $arg; } } $daemon = $TRUE; next; } ($arg eq '--debug' || $arg eq '--verbose') && ($debug = $TRUE) && next; ($arg eq '-f' || $arg eq '--force') && ($force = $TRUE) && next; ($arg eq '-h' || $arg eq '--help') && ($help = $TRUE) && last; if ($arg eq '-i' || $arg eq '--interval') { my $option = $arg; $arg = shift; if (defined $arg && $arg =~ /^[^\-]/) { if ($arg =~ /^\d+$/) { $interval = $arg; } else { OptionError("Option `$option' with an invalid update interval"); } } else { OptionError("Option `$option' with no update interval"); } next; } if ($arg eq '-k' || $arg eq '--key') { my $option = $arg; $arg = shift; if (defined $arg && $arg =~ /^[^\-]/) { $key = $arg; } else { OptionError("Option `$option' with no key file"); } next; } ($arg eq '--kill') && ($kill = $TRUE) && next; if ($arg eq '-l' || $arg eq '--log') { $arg = shift; if (defined $arg) { if ($arg =~ /^[^\-]/) { $log_file = $arg; } else { unshift @ARGV, $arg; } } $log = $TRUE; next; } ($arg eq '-q' || $arg eq '--quiet') && ($quiet = $TRUE) && next; ($arg eq '--reload') && ($reload = $TRUE) && next; ($arg eq '--restart') && ($restart = $TRUE) && next; if ($arg eq '-t' || $arg eq '--ttl') { my $option = $arg; $arg = shift; if (defined $arg && $arg =~ /^[^\-]/) { if ($arg =~ /^\d+$/) { $ttl = $arg; } else { OptionError("Option `$option' with an invalid ttl"); } } else { OptionError("Option `$option' with no ttl"); } next; } ($arg eq '-v' || $arg eq '--version') && ($version = $TRUE) && last; if ($arg =~ /^-/) { OptionError("Invalid option `$arg'"); } else { OptionError("Invalid argument `$arg'"); } } if ($help) { print "$NAME $VERSION ($DATE)\n"; PrintHelp(); exit; } if ($version) { print "$NAME $VERSION ($DATE)\n"; exit; } defined $config_file || ($config_file = $CONFIG_FILE); defined $log_file || ($log_file = $LOG_FILE); if ($reload) { PrintMessage("$NAME: reload"); ReloadConfig() || Abort(); exit; } if ($kill) { PrintMessage("$NAME: terminate"); TerminateDaemon() || Abort(); exit; } if ($restart) { PrintMessage("$NAME: restart"); TerminateDaemon() || Abort(); $daemon = $TRUE; undef $kill; } if ($daemon) { $log = $TRUE; $quiet = $TRUE; PrintMessage("$NAME $VERSION"); Abort() unless (ParseConfig() && SetPidFile()); if (-e $pid_file) { PrintMessage("ERROR: `$pid_file' already exist"); Abort(); } SetInterval(); Abort() unless ReadKey(); SetTTL(); my $pid; if ($pid = fork) { PrintMessage("Starting a daemon, ttl: $ttl, interval: $interval, pid: $pid"); unless (open(PID, ">$pid_file")) { PrintMessage("ERROR: Can't open `$pid_file' ($!)"); Abort(); } print PID $pid; close(PID); } elsif (defined $pid) { my $sleep; $SIG{'INT'} = $SIG{'KILL'} = $SIG{'TERM'} = sub { PrintMessage('Exiting a daemon'); unlink $pid_file || PrintMessage("ERROR: Can't remove `$pid_file' ($!)"); exit; }; $SIG{'HUP'} = sub { $reload = $TRUE; undef $param{'TTL'}; undef $param{'INTERVAL'}; ParseConfig() || PrintMessage('Use the current value(s)'); SetInterval(); ReadKey() || PrintMessage('Use the current key'); SetTTL(); PrintMessage("Reloaded the configurations, ttl: $ttl, interval: $interval"); $sleep = $interval; }; undef $add_alias; undef $del_alias; undef $force; while () { undef $add_addr; undef $del_addr; unless (defined $sleep) { $sleep = $interval; } else { sleep $sleep; } ReadSavedAddress() || next; GetCurrentAddress() || next; if ($saved_addr && $add_addr eq $saved_addr) { $debug && PrintMessage("No need to update `$param{'DOMAIN'}. A $saved_addr'"); next; } FindNameServer() || next; GetAddress() || next; CheckAddress() || next; UpdateRecord(); } } else { PrintMessage("ERROR: Can't fork daemon process ($!)"); Abort(); } } else { PrintMessage("$NAME $VERSION"); ParseConfig() && ReadKey() || Abort(); SetTTL(); if ($add_addr || $del_addr || $add_alias || $del_alias) { FindNameServer() || Abort(); } else { unless ($force) { ReadSavedAddress() || Abort(); } GetCurrentAddress() || Abort(); if ($saved_addr && $add_addr eq $saved_addr) { PrintMessage("No need to update `$param{'DOMAIN'}. A $saved_addr'"); exit; } FindNameServer() && GetAddress() && CheckAddress() || Abort(); } UpdateRecord(); } sub OptionError($) { die "$NAME: $_[0]\nTry `$NAME -h' or `$NAME --help' for more information\n"; } sub PrintHelp() { print <<"EOF"; Usage: $NAME [option]... Options: --add address Add an address record for the domain manually. --del address Delete an address record for the domain manually. --addalias alias Add an alias for the domain manually. --delalias alias Delete an alias for the domain manually. -c, --config config_file Set the configuration file. (the default is `$CONFIG_FILE') -d, --daemon [interval] Execute dnsupdate as a daemon. --debug, --verbose Display verbose messages for debugging. -f, --force Ignore the saved IP address and update. -h, --help Display this help and exit. -i, --interval interval Set update interval for daemon. It overrides the config parameter `INTERVAL'. (the default is 600 in seconds) -k, --key [key_name:]key_file Set key name and key file. It overrides the config parameters `KEY_NAME' and `KEY_FILE'. --kill Terminate a daemon. -l, --log [log_file] Do logging messages (to `log_file'). (the default log file is `$LOG_FILE') -q, --quiet Display nothing but stderr. --reload Reload configurations for the daemon running. --restart Restart a daemon. -t, --ttl ttl Set ttl. It overrides the config parameter `TTL'. (the defaults is 600 in seconds) -v, --version Display version information and exit. Please report bug to EOF } sub ReloadConfig() { ParseConfig() || return $ERROR; SetPidFile() || return $ERROR; if (-e $pid_file) { my $pid; open(PID, $pid_file) || PrintMessage("ERROR: Can't open `$pid_file' ($!)") && return $ERROR; $pid = ; close(PID); if ($pid && $pid =~ /^\d+$/) { my $HUP = 1; if(kill($HUP, $pid)) { PrintMessage("Reloading configurations, pid: $pid"); } else { PrintMessage("ERROR: Can't reload configurations, pid: $pid ($!)"); return $ERROR; } } else { PrintMessage("ERROR: Invalid pid in `$pid_file'"); return $ERROR; } } else { PrintMessage("ERROR: `$pid_file' does not exist"); return $ERROR; } $SUCCESS; } sub TerminateDaemon() { ParseConfig(); SetPidFile() || return $ERROR; if (-e $pid_file) { my $pid; open(PID, $pid_file) || PrintMessage("ERROR: Can't open `$pid_file' ($!)") && return $ERROR; $pid = ; close(PID); if ($pid && $pid =~ /^\d+$/) { my $TERM = 15; if(kill($TERM, $pid)) { PrintMessage("Terminated $NAME daemon, pid: $pid"); } else { PrintMessage("ERROR: Can't terminate $NAME daemon, pid: $pid ($!)"); return $ERROR; } } else { PrintMessage("ERROR: Invalid pid in `$pid_file'"); return $ERROR; } } else { PrintMessage("ERROR: `$pid_file' does not exist"); return $ERROR; } $SUCCESS; } sub ParseConfig() { my (@stat, @list, $param); unless (open(CONFIG, $config_file)) { PrintMessage("ERROR: Can't open `$config_file' ($!)"); return $ERROR; } @stat = stat $config_file; ($stat[2] &= 044) && PrintMessage("WARNING: `$config_file' is group/world readable"); $debug && PrintMessage("Reading `$config_file'"); while () { my ($key, $val); /^#/ && next; s/\s//g; $_ || next; ($key, $val) = split('=', $_); $param{$key} = $val; if ($debug && $param{$key}) { if ($key eq 'KEY_NAME') { PrintMessage("$key = ********"); } else { PrintMessage("$key = $val"); } } } close(CONFIG); return if $kill; @list = ('DOMAIN', 'CGI', 'ADDR_FILE', 'KEY_FILE'); $param = ''; foreach (@list) { unless (defined $param{$_}) { unless (defined $key && $_ eq 'KEY_FILE') { $param .= ", `$_'"; } next; } if ($_ eq 'DOMAIN') { $param{$_} =~ s/^(.+[a-zA-Z0-9])\.+$/$1/; if ($param{$_} =~ /^[a-zA-Z0-9\-]+\.([a-zA-Z0-9\-\.]+)$/) { $domain = $1; } else { $param .= ", `$_'"; } next; } if ($_ eq 'CGI') { $param{$_} =~ s/^http:\/\///i; if ($param{$_} =~ /^([a-zA-Z0-9\-\]+\.[a-zA-Z0-9\-\.:]+)(\/[a-zA-Z0-9_\-\.\/]+)$/) { $cgi_host = $1; $cgi_path = $2; } else { $param .= ", `$_'"; } } } if ($param) { $param =~ s/^, //; PrintMessage("ERROR: $param not set or invalid"); return $ERROR; } $SUCCESS; } sub SetPidFile() { if ($param{'PID_FILE'}) { if ($param{'PID_FILE'} =~ /^\//) { $pid_file = $param{'PID_FILE'}; } elsif ($param{'PID_FILE'} =~ /^[^\/]/) { $pid_file = "/var/run/$param{'PID_FILE'}"; } else { PrintMessage("ERROR: Invalid `PID_FILE' in `$config_file'"); return $ERROR; } } else { $pid_file = $PID_FILE; } $SUCCESS; } sub SetInterval() { my $INTERVAL = 600; return if (defined $interval && !$reload); if (defined $param{'INTERVAL'} && $param{'INTERVAL'} ne '') { if ($param{'INTERVAL'} =~ /^\d+$/) { $interval = $param{'INTERVAL'}; } else { PrintMessage("WARNING: Invalid `INTERVAL', use the default $INTERVAL"); $interval = $INTERVAL; } } elsif (!$reload) { $interval = $INTERVAL; } } sub ReadSavedAddress() { if (-e $param{'ADDR_FILE'}) { unless (open(ADDR, $param{'ADDR_FILE'})) { PrintMessage("ERROR: Can't open `$param{'ADDR_FILE'}' ($!)"); return $ERROR; } $saved_addr = ; close(ADDR); if ($saved_addr && $saved_addr =~ /^(\d{1,3}\.){3}\d{1,3}$/) { $debug && PrintMessage("saved address: $saved_addr"); } else { PrintMessage("ERROR: Invalid address data in `$param{'ADDR_FILE'}'"); return $ERROR; } } else { PrintMessage("WARNING: `$param{'ADDR_FILE'}' does not exist"); } $SUCCESS; } sub GetCurrentAddress() { use Socket; my $PORT = 80; my $TIMEOUT = 60; my ($port, $addr, $proto, $pack_addr, $oldfh); ($cgi_host, $port) = split(':', $cgi_host); if (defined $port) { unless ($port =~ /^\d+$/) { PrintMessage("WARNING: Invalid port for HTTP, use the default $PORT"); $port = $PORT; } } else { $port = $PORT; } $debug && PrintMessage("Resolving the cgi-host `$cgi_host'"); unless ($addr = inet_aton($cgi_host)) { PrintMessage("ERROR: Can't look up the cgi-host `$cgi_host' (No such host)"); return $ERROR; } $proto = getprotobyname('tcp'); unless (socket(SOCKET, PF_INET, SOCK_STREAM, $proto)) { PrintMessage("ERROR: Can't create socket ($!)"); return $ERROR; } $pack_addr = pack_sockaddr_in($port, $addr); $SIG{'ALRM'} = sub { return; }; alarm($TIMEOUT); $debug && PrintMessage("Connecting to `$cgi_host:$port'"); unless (connect(SOCKET, $pack_addr)) { close(SOCKET); alarm(0); PrintMessage("ERROR: Can't connect to `$cgi_host:$port' ($!)"); return $ERROR; } else { $oldfh = select(SOCKET); $| = 1; select($oldfh); $debug && PrintMessage("Sending HTTP request to `$cgi_host'"); print SOCKET "GET /$cgi_path HTTP/1.0\r\n", "User-Agent: $NAME/$VERSION\r\n", "\r\n"; $debug && PrintMessage("Receiving data from `$cgi_host'"); while (){ /^\r\n$/ && last; } $add_addr = ; close(SOCKET); alarm(0); if ($add_addr && $add_addr =~ /^(\d{1,3}\.){3}\d{1,3}$/) { $debug && PrintMessage("current address: $add_addr"); return $SUCCESS; } else { PrintMessage("ERROR: Can't get a current address (cgi error)"); return $ERROR; } } } sub FindNameServer() { use Net::DNS; my ($res, $query, @nameservers); $res = Net::DNS::Resolver->new; $debug && PrintMessage("Finding the nameservers for `$domain'"); unless ($query = $res->query($domain, 'NS')) { my $errorstring = $res->errorstring; PrintMessage("ERROR: Can't find nameservers for `$domain' ($errorstring)"); return $ERROR; } foreach ($query->answer) { next unless $_->type eq 'NS'; my $ns = $_->nsdname; $debug && PrintMessage("server: $ns"); push @nameservers, $ns; } $res->recurse(0); $debug && PrintMessage("Finding the primary master nameserver for `$domain'"); foreach (@nameservers) { unless ($res->nameservers($_)) { if ($debug) { my $errorstring = $res->errorstring; PrintMessage("Can't look up server `$_' ($errorstring)"); } next; } unless ($query = $res->query($domain, 'SOA')) { if ($debug) { my $errorstring = $res->errorstring; PrintMessage("Can't query, server: $_, domain: $domain, type: SOA ($errorstring)"); } next; } #($query->answer)[0]->print; $nameserver = ($query->answer)[0]->mname; last; } if ($nameserver) { $debug && PrintMessage("primary master namesever: $nameserver"); return $SUCCESS; } else { PrintMessage("Can't find the primary master nameserver for `$domain'"); return $ERROR; } } sub GetAddress() { use Net::DNS; my ($res, $query); $res = Net::DNS::Resolver->new; $res->recurse(0); unless($res->nameservers($nameserver)) { my $errorstring = $res->errorstring; PrintMessage("ERROR: Can't look up server `$nameserver' ($errorstring)"); return $ERROR; } $debug && PrintMessage("Finding CNAME record(s) for `$param{'DOMAIN'}'"); if ($query = $res->query($param{'DOMAIN'}, 'CNAME')) { foreach ($query->answer) { next unless $_->type eq 'CNAME'; my $cname = $_->cname; PrintMessage("ERROR: `$param{'DOMAIN'}' is an alias of `$cname'"); return $ERROR; } } else { my $errorstring = $res->errorstring; if ($errorstring eq 'NOERROR') { $debug && PrintMessage("No CNAME record for `$param{'DOMAIN'}'"); } elsif ($errorstring eq 'NXDOMAIN') { PrintMessage("WARNING: `$param{'DOMAIN'}' does not exist"); return $SUCCESS; } else { PrintMessage("ERROR: Can't query, server: $nameserver, domain: $param{'DOMAIN'}, type: CNAME ($errorstring)"); return $ERROR; } } $debug && PrintMessage("Finding A record(s) for `$param{'DOMAIN'}'"); if ($query = $res->query($param{'DOMAIN'}, 'A')) { foreach ($query->answer) { next unless $_->type eq 'A'; my $addr = $_->address; $debug && PrintMessage("address: $addr"); push @addr, $addr; } } else { my $errorstring = $res->errorstring; if ($errorstring eq 'NOERROR') { PrintMessage("WARNING: No A record for `$param{'DOMAIN'}'"); } else { PrintMessage("ERROR: Can't query, server: $nameserver, domain: $param{'DOMAIN'}, type: A ($errorstring)"); return $ERROR; } } $SUCCESS; } sub CheckAddress() { if ($force) { if ($addr[1]) { PrintMessage("ERROR: Can't update, because multiple A records for `$param{'DOMAIN'}' exist"); return $ERROR; } elsif ($addr[0]) { $del_addr = $addr[0]; } return $SUCCESS; } elsif (@addr) { if ($saved_addr) { foreach(@addr) { if ($_ eq $saved_addr) { $del_addr = $_; last; } } unless ($del_addr) { PrintMessage("ERROR: Can't update, because the last updated A record for `$param{'DOMAIN'}' is not found"); return $ERROR; } } else { PrintMessage("ERROR: Can't update, because one or more A records for `$param{'DOMAIN'}' have already exist"); return $ERROR; } } $SUCCESS; } sub ReadKey() { if (defined $key) { if ($key =~ /^(\S+):(\S+)$/) { $key_name = $1; $key_file = $2; } else { $key_file = $key; } } defined $key_file || ($key_file = $param{'KEY_FILE'}); unless (open(KEY, $key_file)) { PrintMessage("ERROR: Can't open `$key_file' ($!)"); return $ERROR; } my @stat = stat $key_file; ($stat[2] &= 044) && PrintMessage("WARNING: `$key_file' is group/world readable"); my $string = ; close(KEY); if (defined $string) { if ($string =~ /^(\S+)\. IN KEY \d+ \d+ \d+ (.+)$/) { $key_name = $1; $secret = $2; } else { unless (defined $key_name) { if (defined $param{'KEY_NAME'}) { $key_name = $param{'KEY_NAME'}; } else { PrintMessage("ERROR: Configuration parameter `KEY_NAME' is not set"); return $ERROR; } } $secret = $string; } $SUCCESS; } else { PrintMessage("ERROR: No key in `$key_file'"); $ERROR; } } sub SetTTL() { my $TTL = 600; return if (defined $ttl && !$reload); if (defined $param{'TTL'} && $param{'TTL'} ne '') { if ($param{'TTL'} =~ /^\d+$/) { $ttl = $param{'TTL'}; } else { PrintMessage("WARNING: Invalid `TTL', use the default $TTL"); $ttl = $TTL; } } elsif (!$reload) { $ttl = $TTL; } } sub UpdateRecord() { use Net::DNS; my ($update, $res, $reply); $update = Net::DNS::Update->new($domain); $debug && PrintMessage("Sending update requests to `$nameserver'"); if ($del_addr) { $debug && PrintMessage("prereq yxrrset `$param{'DOMAIN'}. A $del_addr'"); $update->push('pre', yxrrset("$param{'DOMAIN'}. A $del_addr")); $debug && PrintMessage("update delete `$param{'DOMAIN'}. A $del_addr'"); #$update->push('update', rr_del("$param{'DOMAIN'}. A $del_addr")); } if ($add_addr) { $debug && PrintMessage("prereq nxrrset `$param{'DOMAIN'}. CNAME'"); $update->push('pre', nxrrset("$param{'DOMAIN'}. CNAME")); $debug && PrintMessage("update add `$param{'DOMAIN'}. $ttl A $add_addr'"); #$update->push('update', rr_add("$param{'DOMAIN'}. $ttl A $add_addr")); } if ($del_alias) { $debug && PrintMessage("prereq yxrrset `$del_alias. CNAME $param{'DOMAIN'}.'"); $update->push('pre', yxrrset("$del_alias. CNAME $param{'DOMAIN'}.")); $debug && PrintMessage("update delete `$del_alias. CNAME $param{'DOMAIN'}.'"); $update->push('update', rr_del("$del_alias. CNAME $param{'DOMAIN'}.")); } if ($add_alias) { $debug && PrintMessage("prereq yxrrset `$param{'DOMAIN'}. A'"); $update->push('pre', yxrrset("$param{'DOMAIN'}. A")); $debug && PrintMessage("prereq nxrrset `$add_alias. A'"); $update->push('pre', nxrrset("$add_alias. A")); $debug && PrintMessage("prereq nxrrset `$add_alias. CNAME'"); $update->push('pre', nxrrset("$add_alias. CNAME")); $debug && PrintMessage("update add `$add_alias. $ttl CNAME $param{'DOMAIN'}.'"); $update->push('update', rr_add("$add_alias. $ttl CNAME $param{'DOMAIN'}.")); } $update->sign_tsig($key_name, $secret); $res = Net::DNS::Resolver->new; $res->nameservers($nameserver); $reply = $res->send($update); $debug && PrintMessage("Receiving response from `$nameserver'"); if ($reply) { my $rcode = $reply->header->rcode; if ($rcode eq 'NOERROR') { if ($del_addr) { PrintMessage("Update succeeded: delete `$param{'DOMAIN'}. A $del_addr'"); } if ($add_addr) { PrintMessage("Update succeeded: add `$param{'DOMAIN'}. $ttl A $add_addr'"); } if ($del_alias) { PrintMessage("Update succeeded: delete `$del_alias. CNAME $param{'DOMAIN'}.'"); } if ($add_alias) { PrintMessage("Update succeeded: add '$add_alias. $ttl CNAME $param{'DOMAIN'}.'"); } if ($add_addr) { unless (open(ADDR, ">$param{'ADDR_FILE'}")) { PrintMessage("ERROR: Can't open `$param{'ADDR_FILE'}' ($!)"); return $ERROR; } print ADDR $add_addr; close(ADDR); } } else { PrintMessage("Update failed: $rcode"); } } else { my $errorstring = $res->errorstring; PrintMessage("Update failed: $errorstring"); } $SUCCESS; } sub Abort() { if ($log) { $quiet = $TRUE; PrintMessage('Aborted with an error'); } die "Aborted with an error\n"; } sub PrintMessage($) { if ($log) { open(LOG, ">>$log_file") || die "Can't open `$log_file' ($!)\n"; my $date = localtime(); $date =~ s/^\S+ (.+) \S+$/$1/; print LOG "$date $_[0]\n"; close(LOG); } print "$_[0]\n" unless $quiet; $SUCCESS; }