#!/usr/bin/env perl
# @(#) FreeDNS.pl  Update client for freedns.afraid.org performs Dynamic
#                  DNS updates as requested. Rev'd: 2014-11-10.
#
# Copyright (c) 2014 Graham Jenkins <grahjenk@cpan.org>. All rights reserved.
# This program is free software; you can redistribute it and/or modify it under
# the same terms as Perl itself.

use strict;
use warnings;
use File::Basename;
use LWP::Simple;
use vars qw($VERSION);
$VERSION = "1.05";

# Check usage, open configuration file
my $Minutes;
if ( ($#ARGV == 1) && ($ARGV[0] =~ m/^\d+$/) ) { $Minutes=shift }
if ( $#ARGV != 0 ) {
  die "Usage: ".basename($0)." [repeat-interval] config-file\n".
      " e.g.: ".basename($0)." 15 /usr/local/etc/FreeDNS.conf\n"
}
if ( ! open(CF,$ARGV[0]) ) { die "Can't read file: ".$ARGV[0]."\n" }

# Read and store host-tokens, then close the file
my %Token;
while (<CF>) {
  chomp;
  if ( length() < 2 ) { next }
  my ($t,$h)=split;
  if ( defined($h) && ($t !~ /^\043/) ) { $Token{$h}=$t }
}
close(CF);
if ( keys(%Token) < 1 ) { die "No valid records in file: ".$ARGV[0]."\n" }

# Use Syslog if we can
my $rc=eval { require Sys::Syslog; Sys::Syslog->import(); 1 };
if ($rc) { openlog(basename($0),"","user") }
else     { sub syslog {}                   }
 
# Assemble the client name, force an address update on first pass, enter loop
my ($ClNm,$OldAddr,$ChangeTime,$Status)=(basename($0)."-".$VERSION,"x",0,0); 
while (1) {
  # Get current address
  my $Result;
  if( defined($Result=get("http://ip.afraid.org")) &&
      ($Result=~m/^Detected IP : \d/)                 ) {
    my ($d1,$d2,$d3,$Address)=split /\s+/,$Result;
    syslog("info","Vers: $VERSION. Current address is: $Address");
    # Update each host-key record on first pass or if current address changed
    if ( $Address ne $OldAddr ) {
      foreach my $h ( keys(%Token) ) {
        syslog("info","Attempting update for $h");
        if( defined(my $Response=get(
          "http://freedns.afraid.org/dynamic/update.php?".$Token{$h})) ) {
          if( ($Response=~m/has not changed/) || ($Response=~m/^Updated/) ) {
            $OldAddr=$Address; $ChangeTime=time(); $Response=~s/^ERROR: //
          }
          else { $Status=1 }
          syslog("info",$Response)
        }
        else { $Status=1; syslog("info","No response from server!") }
      }
    }
  }
  else { $Status=1; syslog("info","Vers: $VERSION. Can't get current address!")}
  if ( defined($Minutes) ) {sleep(60*$Minutes)}
  else                     {exit($Status)     }
  # Force an address update on next pass if there hasn't been one for 7 days
  if ( ( time() - $ChangeTime ) > 7*24*3600 ) { $OldAddr="x" }
}

__END__

=head1 NAME

FreeDNS - update client for freedns.afraid.org

=head1 README

FreeDNS will update one or more designated DNS records at
freedns.afraid.org either once or periodically.

=head1 DESCRIPTION

C<FreeDNS> is a simple update client for the FreeDNS dynamic
DNS service. It will attempt an immediate update when called,
then optionally loop at designated intervals, attempting 
further updates when necessary.

=head1 USAGE

=over 6

FreeDNS [repeat-interval] config-file

=back

e.g.: FreeDNS 30 /usr/local/etc/FreeDNS.conf

The repeat-interval value (where present) must be
expressed as a positive integer number of minutes.

The config-file value must be the name of a configuration
file which contains one or more 'hash hostname' records.
Each record should appear on a separate line thus:

  ABC7PqRsTUvwXYZDEFgHijLLM9R8ST8uvWX76CC5DdZx lassie.us.to
  ABC7PqRsTUvwXYZDEFgHijLLM2R7ST8uvWX92AA3BbCz rex.mooo.com
   .. etc.

It is suggested that the configuration file should be
readable only by the intended program-user.
I<Sys::Syslog> will be used for logging if it's installed;
you might want to check your system's syslog configuration so
that you can view log messages.

=head1 SCRIPT CATEGORIES

Networking
UNIX/System_administration

=head1 AUTHOR

Graham Jenkins <grahjenk@cpan.org>

=head1 COPYRIGHT

Copyright (c) 2014 Graham Jenkins. All rights reserved.
This program is free software; you can redistribute it
and/or modify it under the same terms as Perl itself.

=cut
