#!/usr/bin/perl # # eponym.pl # Eponym is a DynDNS & ZoneEdit auto-updater. # # ### Trial Version. # ### Does not support ZoneEdit.com domains. # ### Does not support email notification. # # This program is the copyrighted work of Encodable Industries. # Redistribution is prohibited, and copying is permitted only # for backup purposes. You are free to modify the program for # your own use, but you may not distribute any modified copies # of it. # # Use of this program requires a one-time license fee. You can # order a license here: # # http://encodable.com/eponym/#license # # This software comes with no warranty. The author and many other # people have found it to be useful, and it is our hope that you # find it useful as well, but it comes with no guarantees. Under # no circumstances shall Encodable Industries be held liable in # any situation arising from your use of this program. We are # generally happy to provide support to all our users, but we can # make no guarantee of support. # # For more information about this program, visit the following pages: # # Homepage: http://encodable.com/eponym/ # For Help: http://encodable.com/contact/ my $version = "2.60t"; # v2.60: 20060204: # We now only run once by default. Pass --forever to loop forever # and sleep between update checks. This obseletes the --onetime # option. # # v2.50: 20060201: # Added support for multiple notification email addresses, and # added an option to email the server's IP address every N minutes, # even if the IP hasn't changed. Also added a new pref called # hostless_mode which, if set, allows the program to run without # any hostnames configured; it will just send notification emails # containing the IP address. # # We now mention how to install modules non-interactively when # displaying the module-missing error. # # Now if we're running on Windows and the prefs file isn't in the # current directory, we check the windows directory too. # # v2.4: 20050824: # Now maintained by Encodable Industries. # # v2.3: 20050324: # We now continue to run even if there are problems sending email, # with the assumption being that email notification is a secondary # feature and we should continue to try to update hostnames if # possible. # # v2.2: 20050314: # We now create the files eponym-current-pid.txt and # eponym-current-state.txt. These are useful to keep an eye on # what eponym's doing, to make sure it's running, etc, in case you # aren't using the email feature, and/or in case you're running it # detached from a terminal or "At System Startup" as a WinXP # scheduled task. # # Also, we now check for basic internet connectivity before doing # trying to resolve our dynamic hostnames or determine our own IP # address. This is so that if we aren't even connected to the net, # we can force a short timeout and retry, instead of incurring the # normal 15-minute timeout that occurs when we otherwise can't # determine our own IP even though we are online. # # v2.1: 20050304: # There is now a --onetime option that can be used to check if # your hostname(s) need updating (and update them if so) just # one time, and then exit, instead of looping forever. This # is so that you can run the script via cron job every X minutes, # and/or via "at" on Win32 systems, instead of just running it # all the time in the background. # # v2.0: 20050302: # We now support the updating of zoneedit.com dynamic hostnames. # Woot! See the prefs file for details. # # v1.3: 20050224: # Bugfix: for some reason, on Windows only, MIME::Lite->new()'s # "To" parameter doesn't accept the "name lastname " # format; it only accepts an email address. So that's "fixed." # # We now print instructions on how to install missing modules. # # v1.2: 20050223: # We now send status emails when a hostname is updated, # and when there is a problem updating a host. # # We now set a tcp timeout on DNS lookups; should have # probably always done this, though it never caused any # problems. # # When generating the authorization string, we now read # the password interactively and don't echo the chars to # the screen. # # v1.1: 20041118 # Renamed this program from "DNS Update" to "Eponym", # because there are too many programs/sites that are # named "dnsupdate" or some nearly-identical variant # thereof. # # v1.0.09: 20040917 # scottcarlson //at// yahoo //dot// com sent a patch to # use Net::DNS::Resolver instead of gethostbyname() in the # name_to_address function, because then people can do # things in /etc/hosts and they won't interfere with our # name resolutions. # # v1.0.08: 20040630 # added --force option to update hostname(s) even if they # don't need it. if this option is used, we don't loop, # just update and then quit. # # v1.0.07: 20040624 # added preferences for system, mx, and backmx, in order # to support all of the DynDNS.org options. also added # --offline as a command-line option. # # v1.0.06: 20031224 # we now intelligently determine our current working # directory on linux, so that when called from # somewhere outside of that directory, we still open # the correct logs, errorlog, and prefs file. if # running on a Windows platform, we simply use './' # as the cwd. # # v1.0.05: 20031016 # ip_reporter values can now have the http:// or # https:// or ftp:// on the front; if so, we'll # strip it off before connecting to the host. # # v1.0.04: 20031015 # reordered some of the code so that a user can run # the script with --getauth before filling in the # other values in the prefs file. # # also reworded / respaced some of the text, and added # more detail and examples to the preferences file. # # v1.0.03: 20030803 # preferences are now stored in a separate prefs file # server errors are now more prominently displayed # credentials are now encoded before storage # # v1.0.02: 20030801 # added wildcard option # # v1.0.01: 20030315 # added ability to update multiple hosts on one account use strict; use POSIX; for('LWP::UserAgent', 'IO::Socket', 'MIME::Base64', 'MIME::Lite', 'Term::ReadKey', 'Net::DNS') { eval "require $_"; if($@) { print qq`\nThe $_ module is required, but could not be found.` . qq`\nTo install it, open a command prompt and run the command "ppm"` . qq`\n(for Windows users) or "perl -MCPAN -e shell" (for Linux users).` . qq`\nThen you'll have either a "ppm>" prompt or a "cpan>" prompt.`; if(/MIME::Lite/) { print qq`\nWindows users, type:\n\n\tinstall MIME-Lite`; print qq`\n\nLinux users, type:\n\n\tinstall MIME::Lite`; } elsif(/Term::ReadKey/) { print qq`\nWindows users, type:\n\n\tinstall TermReadKey`; print qq`\n\nLinux users, type:\n\n\tinstall Term::ReadKey`; } elsif(/Net::DNS/) { print qq`\nWindows users, type:\n\n\tinstall Net-DNS`; print qq`\n\nLinux users, type:\n\n\tinstall Net::DNS`; } else { print qq`\nType:\n\n\tinstall $_`; } print qq`\n\n(Note: in case it can't find the module, use the search command` . qq`\nto search for part of the module name.)` . qq`\n` . qq`\nWhen it finishes, type "exit" and then run Eponym again.` . qq`\n` . qq`\n(You can also install modules non-interactively using the command` . qq`\n"ppm install ModuleName" on Windows, or` . qq`\n"perl -MCPAN -e 'install ModuleName'" on Linux.)` . qq`\n` . qq`\n[ For more help, see http://nodivisions.com/code/perl/modules/ ]` . qq`\n[ Or contact us at http://encodable.com/contact/ ]` . qq`\n`; exit; } } my (@hosts_to_update, %ip_reporters, %PREF, $extra_code, $running_mswindows) = (); my $scriptname = 'eponym'; my $curdir; if($^O =~ /MSWin32/) { $curdir = './'; $running_mswindows = 1; } else { $curdir = `dirname "$0"`; chomp $curdir; $curdir = $curdir . '/'; } my $prefs_file = $curdir . $scriptname . '_prefs.txt'; if( (! -e $prefs_file) && ($running_mswindows) ) { # try manually setting the current directory to c:\windows since that's where we install by default. $curdir = 'c:/windows/'; $prefs_file = $curdir . $scriptname . '_prefs.txt'; die "Error: could not find prefs file '$prefs_file'.\n" unless -e $prefs_file; } my $pid_file = $curdir . $scriptname . '-current-pid.txt'; my $state_file = $curdir . $scriptname . '-current-state.txt'; my $data_file = $curdir . $scriptname . '-current-data.txt'; load_prefs(); verify_prefs(); write_pid_file($pid_file); clear_file($state_file); my $here = $scriptname . '.pl'; my $error_log = $curdir . $scriptname . '_errors.log'; my $http_ua = "eponym/$version support\@encodable.com"; my ($header_dyndns) = (); if($PREF{'dyndns_authorization'} =~ /\w/) { $header_dyndns = HTTP::Headers->new(user_agent => $http_ua); $header_dyndns->authorization("Basic $PREF{'dyndns_authorization'}"); } my %options; for(@ARGV) { $options{$_} = 1; } # We only need one instance of the agent, so it's up here, before the loop. my $agent = LWP::UserAgent->new(); my $inettimeout = 10; # Sometimes when attempting to detect your IP address, we can't # do it. This might be because you are actually offline, or it # might be because one of the websites that reports your IP address # is down. In any case, we'll try to detect it max_retries times # before going back to sleep for detection_frequency minutes. my $max_retries = 10; if($PREF{'detection_frequency'} !~ /^\d+$/) { $PREF{'detection_frequency'} = 30; } elsif($PREF{'detection_frequency'} < 5) { $PREF{'detection_frequency'} = 5; } my $sleeptime = $PREF{'detection_frequency'} * 60; my $sleeptime_default = $sleeptime; my $num_reporters = scalar keys %ip_reporters; my $wildcard_option; if($PREF{'wildcard'} =~ /yes/i) { $wildcard_option = '&wildcard=ON'; } if($ARGV[0] eq '--getauth') { $| = 1; my $secret = $options{'--notsecret'} ? 0 : 1; print "Please type your DynDNS.org account's username: "; my $user = $secret ? Term::ReadKey::ReadLine(0) : ; chomp $user; my ($pw1, $pw2) = (1,2); while($pw1 ne $pw2) { Term::ReadKey::ReadMode('noecho') if $secret; print "Please type the password for that account: "; $pw1 = $secret ? Term::ReadKey::ReadLine(0) : ; print "\nPlease type the password again to verify: "; $pw2 = $secret ? Term::ReadKey::ReadLine(0) : ; Term::ReadKey::ReadMode(0) if $secret; chomp ($pw1, $pw2); print "\n\nERROR: passwords do not match. Please try again.\n\n" unless $pw1 eq $pw2; } print "\n\nYour authorization string is:\n\n" . MIME::Base64::encode("$user:$pw1", '' ) . "\n"; print "\nNow edit your ${scriptname}_prefs.txt file and put that string at the end of"; print "\nthe line that says:"; print "\n\n\tdyndns_authorization = "; print "\n\n"; exit; } if($num_reporters < 8) { die " Not enough IP address reporters. You need to search google.com for the phrase \"your ip address is\" (in quotes) and find at least 8 websites that report your IP address. There are thousands of sites that do. Since this script will be checking them frequently, I require each user to find their own sites, so that we don't overload a certain small set of servers. Once you get 8 of them, then edit the $prefs_file file and add those websites as ip_reporter values. "; } if($hosts_to_update[0] !~ /\w+:\w+/ && $PREF{hostless_mode} !~ /yes/i) { die " You need to edit the $prefs_file file and enter the hostname(s) that you wish to update. "; } if($#hosts_to_update == -1 && $PREF{hostless_mode} !~ /yes/i) { die "You haven't specified any hosts to update.\n"; } if($PREF{hostless_mode} !~ /yes/i) { for(@hosts_to_update) { if( (/^dyndns:/ && $PREF{'dyndns_authorization'} !~ /\w/) ) { die "\nYou need to edit the $prefs_file file and set your authorization" . "\nstring. To determine your authorization string, call this script" . "\nlike this:" . "\n\n\tperl $here --getauth" . "\n\n...or if that gives you problems (over OpenSSHWin it does), use this:" . "\n\n\tperl $here --getauth --notsecret" . "\n\n"; } } } print_func(qq` ---------------------------------------------------------------------------- Eponym: updates a dynamic DNS host with your non-static home IP address. NOTE: This is the trial version. The full version has more features NOTE: including support for zoneedit.com domain names and email NOTE: notification. See www.encodable.com/eponym/ for details. Detects your IP address every-so-often by visiting websites that report your IP to you. Actually, we visit 3 such sites from a pool of 10 or 15, and consider the detection "successful" if we get 3 sites reporting the same IP address for you. We then check what IP your dynamic DNS host currently resolves to, and if that doesn't match your current home IP, then we update the dynamic DNS database. This script can update multiple hosts on the same account, and it supports all the top-level-domain endings available on the DynDNS.org service, not just *.dyndns.org. The only normal-usage options are --offline, which is only available to "credited users" of DynDNS.org; --force, which will update your hostname(s) even if they don't need it, but only once; and --forever, which will check if your hostname(s) need updates (and do them if so) and then sleep for a few minutes until the next update check. Contact us at http://encodable.com/contact/ if you need any help, and see also http://encodable.com/eponym/ for updates. NOTE: the files eponym-current-pid.txt and -current-state.txt will tell you Eponym's PID and what Eponym is currently doing. NOTE: This is the trial version. The full version has more features NOTE: including support for zoneedit.com domain names and email NOTE: notification. See www.encodable.com/eponym/ for details. ---------------------------------------------------------------------------- `); exit if ($options{-h} || $options{'--h'} || $options{-help} || $options{'--help'}); print qq`Checking your IP address...\n\n`; # Main loop. while(1) { my (%reported_ips, $firstline, $dynhost_ip, $my_ip, @rest_of_log, $logged_etime, $logged_humantime); my ($last_updated_date, $last_updated_date_human); my $datetime = strftime("%Y%m%d-%H%M",localtime()); my $success = 0; my $retries = 1; # For detecting your home IP; this counts up to $max_retries. my $etime = time(); # 29 days is the most often you can update your hostname # without actually changing the IP... and you should do # this every 29 days because after 35 days with no update # they delete your account. my $twentynine_days_ago = $etime - (60 * 60 * 24 * 29); # If we can't connect to the internet, do a short 1-minute timeout here # and then restart the loop, to avoid doing the normal 15-minute timeout # when we can't determine our own IP address (because we're offline). unless(we_are_online_and_DNS_is_working()) { print_func("Going to sleep for 1 minute, then trying again.\n"); sleep 60; next; } clear_file($state_file); # Determine our IP address. We do this by asking 3 randomly-chosen # sites from our list, and making sure they all report the same # IP address for us. while( !($success) ) { my @three_hosts; # This while() ensures that we're checking 3 unique sites to determine our IP: while( $three_hosts[0] == $three_hosts[1] || $three_hosts[1] == $three_hosts[2] || $three_hosts[2] == $three_hosts[0] ) { @three_hosts = ( substr(rand($num_reporters), 0, 1), substr(rand($num_reporters), 0, 1), substr(rand($num_reporters), 0, 1) ); } for(@three_hosts) { my ($host) = ($ip_reporters{$_} =~ /(.*?)\//); my $sock = IO::Socket::INET->new( PeerAddr => $host, PeerPort => 80, Proto => "tcp", Timeout => $inettimeout ); if($sock) { $sock->autoflush(1); my $request = new HTTP::Request 'GET' => "http://$ip_reporters{$_}"; my $result = $agent->request($request); my $thepage = $result->content; # Some pages have links that are based on IP addresses instead of TLD names, # which messes up our detection... so remove any html tags, including links: $thepage =~ s/<.*?>//gs; if($thepage =~ /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/g) { $reported_ips{$_} = $1 unless $1 eq '127.0.0.1'; } } } for(@three_hosts) { print_func("You are $reported_ips{$_}, saith $ip_reporters{$_}.\n"); } if(( $reported_ips{$three_hosts[0]} eq $reported_ips{$three_hosts[1]} && $reported_ips{$three_hosts[1]} eq $reported_ips{$three_hosts[2]} ) && ( ($reported_ips{$three_hosts[0]} =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) && ($reported_ips{$three_hosts[1]} =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) && ($reported_ips{$three_hosts[2]} =~ /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) ) ) { $my_ip = $reported_ips{$three_hosts[0]}; print_func("\n$datetime: Detected your IP successfully.\n\n"); $success = 1; } else { $retries++; if($retries > $max_retries) { my $minutes = $sleeptime / 60; print_func("\n$datetime:\nFailed to detect your IP address after $max_retries tries.\n" . "Sleeping for $minutes minutes before trying again.\n\n"); sleep($sleeptime); clear_file($state_file); $retries = 0; next; } print_func("\nCouldn't detect your IP address with certainty; trying again, attempt $retries of $max_retries.\n\n"); $my_ip = "unknown"; $success = 0; } } if($PREF{hostless_mode} =~ /yes/i) { print_func(qq`Not updating any hosts, since we're in hostless_mode.\n`); next; } for(@hosts_to_update) { my ($service,$thehost) = (/^(.+):(.+)$/); my $optional_stuff; if($PREF{'mx'}) { $optional_stuff .= "&mx=$PREF{'mx'}"; } if($PREF{'backmx'}) { $optional_stuff .= "&backmx=$PREF{'backmx'}"; } if($options{'--offline'}) { $optional_stuff .= "&offline=YES"; } my ($uri, $dyn_service_host) = (); if($service eq 'dyndns') { $dyn_service_host = 'members.dyndns.org'; $uri = "nic/update?system=$PREF{'system'}&hostname=$thehost" . $optional_stuff . '&myip='; } else { die_func(qq`Invalid service name: "$service"\n`); } my $log = $curdir . $scriptname . '--' . $thehost . "--dynamic-ip-addresses.log"; my $last_updated_date_log = $curdir . $scriptname . '--' . $thehost . "--last-updated-date.log"; if(!(-e $log)) { open(OUT,">$log") or die_func("$0: couldn't create $log: $!, you must create it manually.\n"); close OUT; } if(!(-e $last_updated_date_log)) { open(OUT,">$last_updated_date_log") or die_func("$0: couldn't create $last_updated_date_log: $!, you must create it manually.\n"); close OUT; } # Open the last-updated log to see when we last changed our IP on the dyndns host: open(IN,"<$last_updated_date_log") or die_func("$0: couldn't open $last_updated_date_log: $!\n"); flock IN, 2; seek IN, 0, 0; $firstline = ; my @temp = split(/ /, $firstline); $last_updated_date = $temp[0]; $last_updated_date_human = $temp[1]; close IN or die_func("$0: couldn't close $last_updated_date_log: $!\n"); # Check the log to see if this is a new IP: open(IO,"+<$log") or die_func("$0: couldn't open $log: $!\n"); flock IO, 2; seek IO, 0, 0; $firstline = ; chomp $firstline; ($logged_etime, $logged_humantime, $dynhost_ip) = split(/ /, $firstline); @rest_of_log = ; seek IO, 0, 0; my $tries = 0; TryToResolveHostnameAgain: $dynhost_ip = name_to_address($thehost); if( ($dynhost_ip =~ /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/) && ($my_ip =~ /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/) ) { my $logfiles_exist = 0; if($last_updated_date =~ /\d+/) { $logfiles_exist = 1; } my $force_update_to_prevent_expiration = 0; if($logfiles_exist && ($last_updated_date < $twentynine_days_ago) && ($service eq 'dyndns')) { $force_update_to_prevent_expiration = 1; } my ($update_status,$msg) = (); if( ($dynhost_ip ne $my_ip) || ($force_update_to_prevent_expiration) || $options{'--offline'} || $options{'--force'} || !$logfiles_exist) { ##### ##### ##### Update $thehost now. ##### $msg = "\n$dynhost_ip is the current IP of $thehost.\n" . "$my_ip is your current IP.\n" . "The last update was $last_updated_date_human.\n"; print_func($msg); $update_status .= $msg; if($force_update_to_prevent_expiration) { $msg = "\nForcing an update now since it's been more than 29 days\nsince the last update, to prevent DynDNS.org from auto-expiring\nyour hostname.\n"; print_func($msg); $update_status .= $msg; } # If an update is necessary, do it now. my $response = (); if(($dynhost_ip ne $my_ip) || $force_update_to_prevent_expiration || $options{'--offline'} || $options{'--force'}) { my $extra = $service eq 'dyndns' ? "$my_ip$wildcard_option" : ''; my $theuri = "http://$dyn_service_host/$uri$extra"; $msg = "About to update $thehost by asking $dyn_service_host for\n$uri$extra\n\n"; print_func($msg); $update_status .= $msg; my ($request,$result) = (); if($service eq 'dyndns') { $request = HTTP::Request->new('GET', $theuri, $header_dyndns); $result = $agent->request($request); } $response = $result->content; chomp $response; $msg = "server-response: $response\n"; print_func($msg); $update_status .= $msg; } else # if none of those 4 items were true, then !$logfiles_exist is true. { $msg = "This is the first time you're updating this host, or your logs have been\n" . "deleted. Either way, the IP of $thehost needs no updating.\n\n"; print_func($msg); $update_status .= $msg; $response = 'eponym_firstrun_IP_needs_no_update'; } if($service eq 'dyndns') { if($response =~ /badauth|badsys|badagent|notfqdn|nohost|!donator|!yours|!active|abuse|numhost|dnserr|w(\d+)(h|m|s)|911/i) { if( ($1 =~ /\d+/) && ($2 =~ /h|m|s/i) ) { my $error_msg = '#' x 76 . "\n# ERROR while updating $thehost: $response" . "\n# If you don't know how to fix this, contact us at " . "\n# encodable.com/contact/ and we'll help you with it." . "\n# your ip: $my_ip host ip: $dynhost_ip.\n" . '#' x 76; $error_msg .= "\n\nNOTE: the error was not fatal; attempting to continue.\n"; my $number = $1; my $multiplier = 1; if($2 eq 'h') { $multiplier = 60*60; } if($2 eq 'm') { $multiplier = 60; } if($2 eq 's') { $multiplier = 1; } $sleeptime = $number * $multiplier; print_func($error_msg); # Append the "sleeping for..." string *after* printing the error_msg here, # because we print "sleeping for..." at the end of the loop. We're only # appending it here for the sake of the email. $error_msg .= "\n\nTemporary error; sleeping for $sleeptime seconds because the DynDNS.org error said so.\n"; } else { my $error_msg = '#' x 76 . "\n# ERROR while updating $thehost: $response" . "\n# If you don't know how to fix this, contact us at " . "\n# encodable.com/contact/ and we'll help you with it." . "\n# your ip: $my_ip host ip: $dynhost_ip.\n" . '#' x 76; do_failed_update_actions($thehost, $response, $error_msg); sleep 20; exit; } } else { if($response =~ /good/i) { do_successful_update_actions($thehost, $my_ip, $dynhost_ip, $etime, $datetime, $update_status, $response, $last_updated_date_log); } elsif($response =~ /nochg/i) { do_redundant_update_actions($thehost, $my_ip, $dynhost_ip, $etime, $datetime, $update_status, $response, $last_updated_date_log); } elsif($response =~ /eponym_firstrun_IP_needs_no_update/i) { do_firstrun_actions($thehost, $my_ip, $dynhost_ip, $etime, $datetime, $update_status, $response, $last_updated_date_log); } else { do_failed_update_actions($thehost, "Unknown error; server response: $response; your ip: $my_ip; host ip: $dynhost_ip"); sleep 20; exit; } $sleeptime = $sleeptime_default; # Normal update interval. } } else { die qq`Invalid service name: "$service"\n`; } eval $extra_code; # Also write this new IP address to the log file: print IO "$etime $datetime $my_ip\n"; # Write the old firstline as the new second line: print IO "$firstline\n"; } else { print_func("$datetime:\n$thehost needs no update. It's $dynhost_ip, you're $my_ip\n\n"); # Just update the datetime (currentip is the same as lastip): print IO "$etime $datetime $my_ip\n"; } } else { if($tries < 3) { $tries++; goto TryToResolveHostnameAgain; } my $error_msg = "There was a problem detecting one of these IP addresses:\n$thehost: $dynhost_ip\nyour IP: $my_ip\n\nUpdate skipped. Eponym will continue to run because this might be a\ntemporary network outage; however if you get this message lots of times\nthere is probably a problem.\n\n"; print_func($error_msg); # Also write the error to the log file: print IO "$etime $datetime could_not_detect_ip_for_$thehost your_ip_is_$my_ip\n"; # Write the old firstline as the new second line: print IO "$firstline\n"; } print IO @rest_of_log; truncate IO, tell IO; close IO or die_func("$0: couldn't close $log: $!\n"); } # end for(@hosts_to_update) } continue { if($options{'--force'}) { print_func("\nExiting since --force was used. Won't force-update hostnames repeatedly.\n"); exit; } elsif($options{'--forever'}) { print_func("Going to sleep for " . ($sleeptime/60) . " minutes; next update at " . strftime("%I:%M",localtime( time()+$sleeptime )) . ".\n\n" . "*" x 76 . "\n"); sleep($sleeptime); } else { print_func("\nExiting.\n"); exit; } } # end while(1) sub name_to_address($) { my $name = shift;; # local (@octets); # # local ($nam, $aliases, $addrtype, $length, $address) = # gethostbyname ($name); # # if (! length ($address)) { # $addy = "unknown"; # return $addy;} # # @octets = unpack ("CCCC", $address); # # $addy = join ('.', @octets[0..3]), "\n"; # # return $addy; # 20040917, scottcarlson //at// yahoo //dot// com: # Use Net::DNS::Resolver instead of gethostbyname() # because then people can do things in /etc/hosts # and they won't interfere with our name resolutions. my $res = Net::DNS::Resolver->new; $res->tcp_timeout(10); my $query = $res->search($name); if($query) { foreach my $rr ($query->answer) { next unless $rr->type eq "A"; return $rr->address; } } return "unknown"; } sub load_prefs() { my ($pref, $value); open(IN,"<$prefs_file") or die_func("$0: couldn't open $prefs_file: $!\n"); flock IN, 2; seek IN, 0, 0; while() { chomp; # Skip lines that are blank, and comments: if(/(^#|^\s+$)/) { next; } ($pref, $value) = split(/=/, $_, 2); next if $value !~ /\S/; # Strip leading and trailing spaces. for($pref, $value) { s/\s+$//g; s/^\s+//g; } # Special logic for these because they can have multiple values: if($pref eq 'host_to_update') { push @hosts_to_update, $value; } elsif($pref eq 'ip_reporter') { $value =~ s!^(https?|ftp)://!!i; # remove http:// or https:// or ftp:// from the front, if it's there $ip_reporters{(scalar keys %ip_reporters)} = $value; } elsif($pref eq 'extra_code') { $extra_code .= $value; } else { $PREF{$pref} = $value; } } close IN or die_func("$0: couldn't close $prefs_file: $!\n"); $PREF{num_minutes_between_regular_email_notifications} = 60 * 3 unless $PREF{num_minutes_between_regular_email_notifications} =~ /^\d+$/; # 3 hours by default. } sub verify_prefs() { if($PREF{'system'} !~ /^(dyndns|statdns|custom)$/) { print_func("Your \"system\" preference is set to something invalid. It must be\n" . "either dyndns, statdns, or custom, but it's currently set to:\n\n" . $PREF{'system'} . "\n\nQuitting now.\n"); sleep 20; exit; } if($PREF{'backmx'} !~ /^(YES|NO|)$/) { print_func("Your \"backmx\" preference is set to something invalid. It must be\n" . "either YES, NO, or blank, but it's currently set to:\n\n" . $PREF{'backmx'} . "\n\nQuitting now.\n"); sleep 20; exit; } } sub we_are_online_and_DNS_is_working { my $online = 0; for('www.google.com', 'www.microsoft.com', 'www.yahoo.com', 'www.cnn.com', 'www.aol.com', 'www.amazon.com', 'www.ebay.com') { my $sock = IO::Socket::INET->new( PeerAddr => $_, PeerPort => 80, Proto => "tcp", Timeout => "10" ); if($sock) { $sock->autoflush(1); close( $sock ); $online = 1; last; } } if(!$online) { my $datetime = strftime("%Y%m%d-%H:%M:%S",localtime()); print_func("\n$datetime\nWARNING: it looks like we're offline...\n\n"); } return $online; } sub do_successful_update_actions { my ($thehost, $my_ip, $dynhost_ip, $etime, $datetime, $update_status, $response, $last_updated_date_log) = @_; print_func("Updated the IP of $thehost to $my_ip successfully.\n\n"); open(IO2,"+<$last_updated_date_log") or die_func("$0: couldn't open $last_updated_date_log: $!\n"); flock IO2, 2; seek IO2, 0, 0; my @temp = ; seek IO2, 0, 0; print IO2 "$etime $datetime currentip=$my_ip lastip=$dynhost_ip response: $response\n"; print IO2 @temp; truncate IO2, tell IO2; close IO2 or die_func("$0: couldn't close $last_updated_date_log: $!\n"); } sub do_redundant_update_actions { my ($thehost, $my_ip, $dynhost_ip, $etime, $datetime, $update_status, $response, $last_updated_date_log) = @_; my $msg = "Redundant update: $thehost to $my_ip"; print_func("$msg\n\n"); open(IO2,"+<$last_updated_date_log") or die_func("$0: couldn't open $last_updated_date_log: $!\n"); flock IO2, 2; seek IO2, 0, 0; my @temp = ; seek IO2, 0, 0; print IO2 "$etime $datetime currentip=$my_ip lastip=$dynhost_ip response: $response\n"; print IO2 @temp; truncate IO2, tell IO2; close IO2 or die_func("$0: couldn't close $last_updated_date_log: $!\n"); } sub do_firstrun_actions { my ($thehost, $my_ip, $dynhost_ip, $etime, $datetime, $update_status, $response, $last_updated_date_log) = @_; my $msg = "Eponym first run: $thehost = $my_ip"; print_func("$msg\n\n"); open(IO2,"+<$last_updated_date_log") or die_func("$0: couldn't open $last_updated_date_log: $!\n"); flock IO2, 2; seek IO2, 0, 0; my @temp = ; seek IO2, 0, 0; print IO2 "$etime $datetime currentip=$my_ip lastip=$dynhost_ip response: $response\n"; print IO2 @temp; truncate IO2, tell IO2; close IO2 or die_func("$0: couldn't close $last_updated_date_log: $!\n"); } sub do_failed_update_actions { my ($thehost, $response, $error_msg) = @_; if(!(-e $error_log)) { open(OUT,">$error_log") or die_func("$0: couldn't create $error_log: $!, you must create it manually.\n"); close OUT; } open(OUT,">>$error_log") or die_func("$0: couldn't open $error_log for appending: $!\n"); flock OUT, 2; seek OUT, 0, 2; my $etime = time(); my $datetime = strftime("%Y%m%d-%H%M",localtime($etime)); my $more_error = "$etime $datetime Error while updating $thehost: "; print OUT $more_error . $response . "\n"; close OUT or die_func("$0: couldn't close $error_log: $!\n"); $error_msg .= "\n$more_error\n$response\n"; $error_msg .= "\nThis error could be a temporary server problem, so you might want" . "\nto try again in a little while. If the problem persists, visit" . "\nencodable.com/eponym/ for the latest version, or" . "\ncontact us at encodable.com/contact/ for help." . qq`\n` . qq`\nNOTE: Eponym is quitting now, so you'll need to take action` . qq`\n(either fix this problem, or just start Eponym again) or else` . qq`\nyour hostnames will not be updated anymore.` . qq`\n`; print_func($error_msg); } sub do_failed_update_warning { my ($thehost, $response, $error_msg) = @_; if(!(-e $error_log)) { open(OUT,">$error_log") or die_func("$0: couldn't create $error_log: $!, you must create it manually.\n"); close OUT; } open(OUT,">>$error_log") or die_func("$0: couldn't open $error_log for appending: $!\n"); flock OUT, 2; seek OUT, 0, 2; my $etime = time(); my $datetime = strftime("%Y%m%d-%H%M",localtime($etime)); my $more_error = "$etime $datetime Error while updating $thehost: "; print OUT $more_error . $response . "\n"; close OUT or die_func("$0: couldn't close $error_log: $!\n"); $error_msg .= "\n$more_error\n$response\n"; $error_msg .= "\nThis error could be a temporary server problem, so Eponym is not" . "\ngoing to exit; we'll keep trying. If the problem persists, visit" . "\nencodable.com/eponym/ for the latest version, or" . "\ncontact us at encodable.com/contact/ for help." . qq`\n`; print_func($error_msg); } sub write_pid_file($) { my $file = shift; my $datetime = strftime("%Y%m%d-%H:%M:%S",localtime(time)); open(OUT,">$file") or die_func("$0: couldn't open $file for writing: $!\n"); flock OUT, 2; seek OUT, 0, 0; print OUT "$datetime pid=$$\n"; close OUT or die_func("$0: couldn't close $file: $!\n"); } sub print_func($) { my $output = shift; print $output; open(APPEND,">>$state_file") or die "$0: couldn't open $state_file for appending: $!\n"; flock APPEND, 2; print APPEND $output; close APPEND or die "$0: couldn't close $state_file: $!\n"; } sub clear_file($) { my $file = shift; open(OUT,">$file") or die "$0: couldn't open $file for writing: $!\n"; flock OUT, 2; seek OUT, 0, 0; truncate OUT, tell OUT; close OUT or die "$0: couldn't close $file: $!\n"; } sub die_func($) { my $output = shift; my $death_msg = "\n\nEponym exited with an error.\n"; print_func($output . $death_msg); die ""; } sub log_eponym_data { my ($item, $value) = @_; if(! -e $data_file) { open(my $outfh,">$data_file") or die "$0: couldn't create $data_file: $!\n"; close $outfh or die "$0: couldn't close $data_file after creating it: $!\n"; } my $logged = (); open(my $iofh, "+<$data_file") or die "$0: couldn't open $data_file for R/W: $!\n"; flock $iofh, 2; seek $iofh, 0, 0; my @old_contents = <$iofh>; seek $iofh, 0, 0; for(@old_contents) { if(/^$item:/) { print $iofh "$item: $value\n"; $logged = 1; } else { print $iofh $_; } } print $iofh "$item: $value\n" unless $logged; truncate $iofh, tell $iofh; close $iofh or die "$0: couldn't close $data_file after R/W: $!\n"; } sub get_eponym_data { my ($item) = @_; if(! -e $data_file) { return; } my $value = (); open(my $infh, "<$data_file") or die "$0: couldn't open $data_file for reading: $!\n"; flock $infh, 1; seek $infh, 0, 0; while(<$infh>) { chomp; if(/^$item:\s*(.*)/) { $value = $1; last; } } close $infh or die "$0: couldn't close $data_file after reading: $!\n"; return $value; }