#! /usr/bin/perl -w ###### # # (c) Mihai Bazon 2007, http://mihai.bazon.net/blog # This is free software under the same terms as Perl itself. # ###### use strict; use Socket; use Getopt::Long; use POSIX (); ####### CONFIG my @HOSTS = qw(); my $CHECK_INTERVAL = 300; # 5 min my $CHAIN = 'DYNDNS'; # iptables chain name my $IPTABLES = '/sbin/iptables'; my $PID_FILE = '/var/run/dyndns-firewall.pid'; my $LOG_FILE = '/var/log/dyndns-firewall.log'; ####### /CONFIG my $opt_kill = 0; my $opt_update = 0; my @opt_hosts; GetOptions( 'kill|k' => \$opt_kill, 'update|u' => \$opt_update, 'host|hosts|h=s' => \@opt_hosts, ); my $running_pid; if (-f $PID_FILE) { open PID, '<', $PID_FILE; local $/ = undef; $running_pid = ; close PID; } if ($opt_kill) { if ($running_pid) { if (!kill('TERM', $running_pid)) { warn "Couldn't kill process - removing stale PID file"; unlink $PID_FILE; } } else { warn "dyndns-firewall is not running (no PID file)"; } exit 0; } if ($running_pid) { if ($opt_update) { debug("Already running - forcing update"); kill 'USR1', $running_pid; exit 0; } else { die("dyndns-firewall.pl is already running. Use -k to kill it, use -u to force update (don't pass new hosts!)"); } } ## daemonize fork && exit; POSIX::setsid(); $SIG{HUP} = 'IGNORE'; fork && exit; chdir '/'; umask 0; open my $LOG, '>>', $LOG_FILE; open STDIN, '+>/dev/null'; open STDOUT, '+>&=', $LOG; open STDERR, '+>&=', $LOG; @HOSTS = split(/,/, join(',', @opt_hosts)); if (!@HOSTS) { die "No hosts defined."; } ## cleanup on exit $SIG{TERM} = $SIG{INT} = sub { my ($signal) = @_; debug("--- Terminated (SIG$signal), cleaning up"); iptables("-F $CHAIN"); iptables("-D INPUT -j $CHAIN"); iptables("-X $CHAIN"); unlink $PID_FILE; exit 0; }; ## update IPs on SIGUSR1 $SIG{USR1} = sub { debug("Got SIGUSR1, forcing update"); update_ips(); }; ## initialize: lookup IPs at startup and build %ip_hash my @ips = map { get_ip($_) } @HOSTS; my %ip_hash; @ip_hash{@HOSTS} = @ips; debug("*** Starting up, pid=$$"); open PID, '>', $PID_FILE; print PID $$; close PID; ## setup new chain iptables("-N $CHAIN"); iptables("-I INPUT 1 -j $CHAIN"); iptables("-F $CHAIN"); ## add initial ALLOW rules update_fw($_) foreach (@ips); while (1) { sleep $CHECK_INTERVAL; update_ips(); } ####### UTILITIES sub get_ip { inet_ntoa(inet_aton($_[0])); } sub update_fw { my ($newip, $oldip) = @_; if ($oldip) { iptables("-D $CHAIN -s $oldip -j ACCEPT"); } if ($newip) { iptables("-A $CHAIN -s $newip -j ACCEPT"); } } sub iptables { my $cmd = join(' ', "$IPTABLES", @_); debug("$cmd"); system $cmd; } sub update_ips { while (my ($host, $ip) = each %ip_hash) { my $current_ip = get_ip($host); if ($current_ip ne $ip) { update_fw($current_ip, $ip); $ip_hash{$host} = $current_ip; } } } sub debug { print STDERR scalar(localtime), " - ", @_, "\n"; } __END__