#!/usr/bin/perl

###############################################################################
#
# Copyright 2000-2011 ClearFoundation
#
# 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.
#
###############################################################################

use strict;
use Net::Ping;
use POSIX;

$SIG{'HUP'}  = 'SignalReload';
$SIG{'INT'}  = 'SignalInterrupt';
$SIG{'TERM'} = 'SignalInterrupt';
$SIG{'USR1'} = 'SignalWebconfigWorkaround';
$SIG{'CHLD'} = sub { wait };

$ENV {'PATH'}  = '/sbin:/usr/bin:/bin';
$ENV {'SHELL'} = '/bin/sh';
delete @ENV {'ENV', 'BASH_ENV'};  # Perl security stuff - see "man perlsec"


###############################################################################
#
# Function Definitions
#
###############################################################################

# Core 
sub Daemonize();
sub SignalReload();
sub LogMsg($$$);
sub ReadConfig();
sub SignalInterrupt();
sub SleepHeartbeat();

# Network watch
sub CheckWanConnection();
sub CheckWanNetwork();
sub CheckIpChanges();
sub GetIp($$);
sub GetRoute($);
sub GetNetworkInfo();
sub GetNetworkConfig();
sub GetPingServers();
sub RestartNetwork();
sub RestartFirewall();
sub UpdateDyndns();
sub ValidateIp($);

# Automagic system fixes
sub CheckUpgrade();
sub CheckResolv(); # PPPoE ... sigh
sub CheckWhitelistConfig();
sub CheckNetworkConf();
sub CheckNetworkBakFiles();
sub CheckSecurityAudit();
sub UpdateWhitelist();


###############################################################################
#
# Constants
#
###############################################################################

my $DIR_IDS      = "/etc/snortsam.d";
my $DIR_STATE    = "/var/lib/syswatch";
my $DIR_SECAUD   = "/var/lib/suvlets/SecurityAudit/db";
my $DIR_NETWORK  = "/etc/sysconfig/network-scripts";
my $FILE_PID     = "/var/run/syswatch.pid";
my $FILE_LOG     = "/var/log/syswatch";
my $FILE_CFG     = "/etc/syswatch";
my $FILE_MULTIWAN = "/etc/clearos/multiwan.conf";
my $FILE_NETWORK = "/etc/clearos/network.conf";
my $FILE_DNS     = "/etc/resolv-peerdns.conf";
my $FILE_IDSCFG  = "/etc/snortsam.conf";
my $FILE_IDSLIST = "system-autowhitelist.conf";
my $FILE_STATE   = "$DIR_STATE/state";
my $FILE_SECAUD  = "$DIR_SECAUD/aide.db";
my $FILE_SECAUDN = "$DIR_SECAUD/aide.db.new";
my $CMD_GETIP   = "/usr/sbin/getip";
my $CMD_IDS      = "/etc/rc.d/init.d/snortsam condrestart >/dev/null 2>&1";
my $CMD_MD5SUM   = "/usr/bin/md5sum";
my $CMD_DYNDNS   = "/usr/sbin/dnsupdate";
my $CMD_VPNUPDATE = "/usr/sbin/vpnupdate";
my $CMD_TIMESYNC = "/usr/sbin/timesync";
my $CMD_WANUPDATE = "/usr/sbin/syswatch.local";
my $STATE_ENABLED = "enabled";
my $STATE_DISABLED = "disabled";
my $STATE_UNKNOWN = "unknown";
my $STATE_STARTUP = "startup";
my $STATE_ACTIVE = "active";
my $STATE_BACKUP = "backup";
my $TYPE_STATIC = "static";
my $TYPE_PPPOE = "pppoe";
my $TYPE_DHCP = "dhcp";
my $STATUS_OK = 0;
my $STATUS_WARN = 1;
my $STATUS_DOWN = 2;
my $STATUS_INIT = 3;
my $STATUS_BORKED = 20;
my $STATUS_CHANGED = 30;
my $STATUS_UNCHANGED = 31;
my $STATUS_NO = 40;
my $STATUS_YES = 41;

# Net::Ping has issues with -T (protocol) when using "syn" ("icmp" is fine)
my $PING_PROTO = "icmp";

###############################################################################
#
# Configuration Variables (globals)
#
###############################################################################

# Override these settings in /etc/syswatch.  Booleans: 0 = disabled, 1 = enabled
#------------------------------------------

my @config_ping_servers;
my %config;

$config{debug} = 0;  # debug 0 = no debug, 1 - some debug
$config{retries} = 5;  # number of ping retries before taking action
$config{interval} = 60;  # ping interval (in seconds)
$config{heartbeat} = 10;  # number of intervals between writing a "heartbeat" in the log file
$config{use_get_ip} = $STATE_ENABLED;     # use remote server to determine IP
$config{failed_interval} = 10;  # ping interval (in seconds) when 'net is down
$config{ping_servers_autodetect} = $STATE_ENABLED; 
$config{try_pinging_gateway} = $STATUS_YES;
$config{is_multiwan} = $STATE_DISABLED;

# FIXME: Alternate ping servers are hard-coded IPs.  We cannot use hostname
# since we want to avoid inevitable DNS issues.  However, the hard-coded IP
# addresses here should be replaced by some kind of update when connected.
$config{ping_server1_default} = "8.8.8.8";
$config{ping_server2_default} = "54.152.208.245";

# Real globals
#--------------

my %g_extifs;  # Interface data structure
my $g_dns_retry = 0;

# "Fake" globals
#---------------
# The following are really static local variables (not supported in Perl?),
# or variables in the main loop.

my $g_internet_update = $STATE_ENABLED;
my $g_ids_md5 = $STATE_STARTUP; # Should be local static variable
my $g_active_wans = $STATE_STARTUP; # Should be local static variable
my $g_heartbeatcount = 0; # Should be local static variable
my $g_first_dyndns = $STATUS_YES; # Should be local static variable
my $g_first_time = $STATUS_YES; # Should be local static variable
my $g_do_timesync = $STATUS_YES; # Should be local static variable


##############################################################################
#
# Let's update a few things on startup...
#
##############################################################################

LogMsg("info", "system", "syswatch started\n");
Daemonize();
CheckUpgrade();
CheckNetworkBakFiles();
ReadConfig();
GetNetworkConfig();
GetNetworkInfo();
GetPingServers();
CheckNetworkConf();
CheckSecurityAudit();

##############################################################################
#
# Main loop
#
##############################################################################


while (1) {

    #------------------------------------------------------------------------
    # WAN connection check
    #------------------------------------------------------------------------
    #
    # Ping remote servers
    #
    #-----------------------------------------------------------------------

    my $wan_connection = CheckWanConnection();


    #------------------------------------------------------------------------
    # WAN connection down
    #------------------------------------------------------------------------
    #
    # If a WAN connection is down
    #   + Try to bring up any interfaces that are down
    #   + Load the latest network information
    #
    #------------------------------------------------------------------------

    if ($wan_connection != $STATUS_OK) { 
        RestartNetwork();
        GetNetworkInfo();
    }

    # Re-check the connection status if a WAN IP has changed

    my $wan_ip_state = CheckIpChanges();

    if ($wan_ip_state == $STATUS_CHANGED) {
        $wan_connection = CheckWanConnection();
    }

    #------------------------------------------------------------------------
    # WAN changes check
    #------------------------------------------------------------------------
    #
    # If a WAN IP changes
    #  + Set the g_internet_update flag
    #  + Restart the firewall
    #  + Load the current network settings
    #  + Reset servers used for ping testing
    #
    #------------------------------------------------------------------------

    my $wan_network_state = CheckWanNetwork();

    if ( ($wan_ip_state == $STATUS_CHANGED) || ($wan_network_state == $STATUS_CHANGED)) {
        $g_internet_update = $STATE_ENABLED;
        RestartFirewall();
        GetNetworkConfig();
        GetNetworkInfo();
        GetPingServers();
        UpdateWhitelist();
    }


    #------------------------------------------------------------------------
    # Other Web Services update
    #------------------------------------------------------------------------
    #
    # When an IP changes, perform some updates... wait for Internet connection
    #
    #------------------------------------------------------------------------

    if (($g_internet_update eq $STATE_ENABLED) && ($wan_connection != $STATUS_DOWN)) {
        $g_internet_update = $STATE_DISABLED;
        CheckWhitelistConfig();
        UpdateDyndns();
    }

    SleepHeartbeat();
}


###############################################################################
# F U N C T I O N S
###############################################################################

###############################################################################
#
# Daemonize: turns this into a daemon
#
###############################################################################

sub Daemonize() {

    my $anaconda = `/sbin/pidof loader`;
    chomp($anaconda);

    if ($anaconda) {
        LogMsg("warn", "system", "syswatch does not run in install environment");
        exit 1;
    }

    if (-e $FILE_PID) {
        my $isup = `/sbin/pidof syswatch`;
        chomp($isup);
        if ($isup) {
            LogMsg("warn", "system", "syswatch already running? $FILE_PID");
            exit 1;
        } else {
            LogMsg("warn", "system", "cleaning up old PID file");
            unlink($FILE_PID);
        }
    }

    if (fork()) { exit; }
    setsid();
    open(FILE_PID, ">$FILE_PID");
    printf FILE_PID "%d\n", getpid();
    close(FILE_PID);

    $0 = "syswatch";
}


###############################################################################
#
# SleepHeartbeat: 
#
###############################################################################

sub SleepHeartbeat() {

    # If there is a connection problem, "sleep" for a short period of time
    my $allok = $STATUS_OK;
    foreach my $extif (keys %g_extifs) {
        if ($g_extifs{$extif}->{status} != $STATUS_OK) {
            $allok = $STATUS_DOWN;
            LogMsg("debug", $extif, "using failed interval time (sec) - " . $config{failed_interval} . "\n") if $config{debug};
            last;
        }
    }

    if ($allok == $STATUS_OK) {
        sleep $config{interval};
    } else {
        sleep $config{failed_interval};
    }

    # Log heartbeat from time to time
    $g_heartbeatcount++;

    if ($g_heartbeatcount == $config{heartbeat}) {
        $g_heartbeatcount = 0;
        LogMsg("info", "system", "heartbeat...\n");
        UpdateDyndns() if ($g_dns_retry); # set when a previous dyndns update failed
    }
}


###############################################################################
#
# CheckWanConnection: Check to see if the Internet is up
#
###############################################################################

sub CheckWanConnection() {

    foreach my $extif (keys %g_extifs) {

        my $gwcheck = 0;
        my $ping_server1 = $g_extifs{$extif}->{ping_server1};
        my $ping_server2 = $g_extifs{$extif}->{ping_server2};

        # If we don't have an IP, bail right away
        #----------------------------------------

        if ($g_extifs{$extif}->{ip} eq $STATE_UNKNOWN) {
            LogMsg("info", $extif, "ping check - no IP available\n");
            $g_extifs{$extif}->{status} = $STATUS_DOWN;
            next;
        }

        # If we don't have a gateway, bail right away
        #--------------------------------------------

        if ($g_extifs{$extif}->{gateway} eq $STATE_UNKNOWN) {
            LogMsg("info", $extif, "ping check - no gateway found\n");
            $g_extifs{$extif}->{status} = $STATUS_DOWN;
            next;
        }

        # Initialize ping request, bail if we can't bind to interface
        #------------------------------------------------------------

        my $ping_object;
        eval {
            $ping_object = Net::Ping->new($PING_PROTO, 5, 1, $extif);
            $ping_object->bind($g_extifs{$extif}->{ip}); 
        };
        if ($@) {
            LogMsg("warn", $extif, "network interface disappeared - " . $g_extifs{$extif}->{ip} . "\n");
            $g_extifs{$extif}->{status} = $STATUS_DOWN;
            next;
        }

        # If connection is down, update ARP table (multiwan)
        #---------------------------------------------------

        if ($g_extifs{$extif}->{status} != $STATUS_OK) {
            LogMsg("debug", $extif, "updating ARP table\n") if $config{debug};
            if ($g_extifs{$extif}->{conntype} eq $TYPE_PPPOE) {
                LogMsg("debug", $extif, "skipping ARP nud on PPPoE\n") if $config{debug};
            } else {
                my $mygateway = $1 if ($g_extifs{$extif}->{gateway} =~ /^(.*)$/); # Untaint (already validated)
                my $mac = `/bin/cat /proc/net/arp | /bin/grep $mygateway | /bin/grep $extif | /bin/awk \'{ print \$4 }\'`;
                if (($mac =~ /^(.*)$/) && $mac){
                    $mac = $1;
                    $ping_server1 = $1 if ($ping_server1 =~ /^(.*)$/);  # Untaint (already validated)
                    $ping_server2 = $1 if ($ping_server2 =~ /^(.*)$/);  # Untaint (already validated)
                    if ($ping_server1 ne $g_extifs{$extif}->{gateway}) {
                        `/sbin/ip neigh replace $ping_server1 lladdr $mac nud permanent dev $extif 2>/dev/null`;
                        LogMsg("debug", $extif, "added ARP nud - $mac / $ping_server1\n") if $config{debug};
                    }
                    if ($ping_server2 ne $g_extifs{$extif}->{gateway}) {
                        `/sbin/ip neigh replace $ping_server2 lladdr $mac nud permanent dev $extif 2>/dev/null`;
                        LogMsg("debug", $extif, "added ARP nud - $mac / $ping_server2\n") if $config{debug};
                    }
                } else {
                    LogMsg("debug", $extif, "could not add ARP nud\n") if $config{debug};
                }
            }
        }

        # If connection is down, ping gateway first
        #------------------------------------------

        if (($g_extifs{$extif}->{status} != $STATUS_OK) && ($config{try_pinging_gateway} == $STATUS_YES)) {
            LogMsg("debug", $extif, "ping check on gateway - " . $g_extifs{$extif}->{gateway} . "\n") if $config{debug};
            $gwcheck = $ping_object->ping($g_extifs{$extif}->{gateway}, 2);
            if ($gwcheck == 0) {
                LogMsg("info", $extif, "ping check on gateway failed - " . $g_extifs{$extif}->{gateway} . "\n");
            } else {
                LogMsg("debug", $extif, "ping check on gateway passed\n") if $config{debug};
            }
        }

        # Sanity checks are done... start the ping check
        #-----------------------------------------------

        LogMsg("debug", $extif, "ping check started - " . $ping_server1 . "\n") if $config{debug};

        my $check = 0;
        eval { $check = $ping_object->ping($ping_server1, 2); };
    
        if ($check == 0) {
            LogMsg("info", $extif, "ping check on server #1 failed - " . $ping_server1 . "\n");
            sleep 3;
            $g_extifs{$extif}->{downcount1}++;
            eval { $check = $ping_object->ping($ping_server2, 2); };

            if ($check == 0) {
                $g_extifs{$extif}->{downcount2} = 0 if (! defined($g_extifs{$extif}->{downcount2}));
                $g_extifs{$extif}->{downcount2}++;
                LogMsg("info", $extif, "ping check on server #2 failed - " . $ping_server2 . "\n");
                LogMsg("debug", $extif, "ping check down count - " . $g_extifs{$extif}->{downcount2} . "\n") if $config{debug};
                if  ($g_extifs{$extif}->{downcount2} >= $config{retries}) {
                    if ($gwcheck != 0) {
                        LogMsg("warn", $extif, "upstream connection problems with your ISP\n");
                    } else {
                        LogMsg("warn", $extif, "connection is down\n");
                    }
                    $g_extifs{$extif}->{status} = $STATUS_DOWN;
                } else {
                    LogMsg("warn", $extif, "connection warning\n");
                    $g_extifs{$extif}->{status} = $STATUS_WARN;
                }
            } else {
                LogMsg("info", $extif, "ping check on server #2 passed - " . $ping_server2 . "\n");
                $g_extifs{$extif}->{downcount2} = 0;
                $g_extifs{$extif}->{status} = $STATUS_OK;
            }

            # A small minority of ISPs block ping on the next hop.  We track the number
            # of failed pings on the next hop with downcount1/downcount2.  If the count
            # gets above a certain number, we use a pointclark.net server.

            if ( ($g_extifs{$extif}->{downcount1} == 15) && ($g_extifs{$extif}->{downcount2} == 0) ) {
                if ($g_extifs{$extif}->{ping_server1} eq $g_extifs{$extif}->{gateway}) {
                    LogMsg("info", $extif, "ping blocked on gateway " . $g_extifs{$extif}->{ping_server1} . " - using alternate\n");
                    $config{try_pinging_gateway} = $STATUS_NO;
                } else {
                    LogMsg("info", $extif, "ping blocked on " . $g_extifs{$extif}->{ping_server1} . " - using alternate\n");
                }
                $g_extifs{$extif}->{ping_server1} = $config{ping_server1_default};
            }
    
        } else { 
            if ($g_extifs{$extif}->{downcount1} && ($g_extifs{$extif}->{downcount1} > 0)) {
                LogMsg("info", $extif, "ping check on server #1 passed - " . $ping_server1 . "\n");
            } else {
                LogMsg("debug", $extif, "ping check on server #1 passed - " . $ping_server1 . "\n") if $config{debug};
            }
            $g_extifs{$extif}->{downcount1} = 0;
            $g_extifs{$extif}->{downcount2} = 0;
            $g_extifs{$extif}->{status} = $STATUS_OK;
        }

        $ping_object->close();
    }

    # Log connection summary
    #-----------------------

    my $total_connections = keys %g_extifs;
    my $up_connections = 0;
    my $down_connections = 0;
    my $warn_connections = 0;
    foreach my $extif (keys %g_extifs) {
        $up_connections++ if ($g_extifs{$extif}->{status} == $STATUS_OK);
        $down_connections++ if ($g_extifs{$extif}->{status} == $STATUS_DOWN);
        $warn_connections++ if ($g_extifs{$extif}->{status} == $STATUS_WARN);
    }

    LogMsg("debug", "system", "connection summary (up:down:warn = total) - " . 
        $up_connections . ":" . $down_connections . ":" . $warn_connections . " = " . 
        $total_connections . "\n") if $config{debug};

    if ($up_connections == 0) {
        return $STATUS_DOWN;
    } elsif ($up_connections == $total_connections) {
        return $STATUS_OK;
    } else {
        return $STATUS_WARN;
    }
}


###############################################################################
#
# RestartFirewall: Restart the firewall
#
###############################################################################

sub RestartFirewall() {
    LogMsg("info", "system", "restarting firewall\n");
    `/etc/rc.d/init.d/firewall restart 2>/dev/null`; 
}

###############################################################################
#
# RestartNetwork: Restart the Internet connection
#
###############################################################################

sub RestartNetwork() {

    foreach my $extif (keys %g_extifs) {

        # Bail if network status is ok
        #-----------------------------

        next if ($g_extifs{$extif}->{status} != $STATUS_DOWN);

        # Bail if gateway IP is still ping-able
        #--------------------------------------

        if ($g_extifs{$extif}->{gateway} ne $STATE_UNKNOWN) {
            my $ping_object;
            eval { 
                $ping_object = Net::Ping->new("icmp", 5, 64, $extif);
                $ping_object->bind($g_extifs{$extif}->{ip}); 
            };
            if (! $@) {
                my $gwcheck = $ping_object->ping($g_extifs{$extif}->{gateway}, 2);
                if ($gwcheck != 0) {
                    LogMsg("debug", $extif, "ping check on gateway passed - skipping network restart\n") if $config{debug};
                    next;
                }
            }
        }

        # Reconnect for DHCP
        #-------------------
    
        if ($g_extifs{$extif}->{conntype} eq $TYPE_DHCP) {
            LogMsg("info", $extif, "restarting DHCP connection\n");
            system("/sbin/ifdown $extif >/dev/null 2>/dev/null");
            system("/sbin/ifup $extif >/dev/null 2>/dev/null");
    
    
        # Reconnect for PPPoE
        #--------------------
    
        } elsif ($g_extifs{$extif}->{conntype} eq $TYPE_PPPOE) {

            # PPPOEKLUDGE: See how simple static and DHCP connections are?  Not so with PPPoE.
            #
            # KLUDGE 1: PPPoE will sometimes leave a hanging ppp0 interface.  On 
            # a box with a single PPPoE interface, we bring down everything just
            # to make sure ppp1 does not cause grief.  We do not have this
            # luxury with multi-WAN/multi-PPPoE connections. 
            #
            # KLUDGE 2: PPPoE seems to like putting a 0.0.0.0 name server into
            # /etc/resolv-peerdns.conf sometimes.  Not good.  We have a workaround to
            # detect this issue.

            LogMsg("info", $extif, "restarting PPPoE connection\n");
            system("/sbin/ifdown $extif >/dev/null 2>&1");
            sleep 5;

            my $locked_on_dev = `/bin/ps axwww | /bin/grep "pppoe.*$extif" | /bin/grep -v grep | /bin/awk '{ print \$1 }'`;

            if ($locked_on_dev) {
                LogMsg("info", $extif, "Old PPPoE connection locked $extif\n");
                # Untaint
                $locked_on_dev =~ s/\s/ /g;
                $locked_on_dev = $1 if ($locked_on_dev =~ /^([\d ]*)$/);
                system("/bin/kill $locked_on_dev >/dev/null 2>&1");
            }

            sleep 2;
            system("/sbin/ifup $extif >/dev/null 2>&1");
            sleep 25; # Give PPPoE some time

            CheckResolv(); # see kludge notes above
    
        # Reconnect for static
        #---------------------
    
        } elsif ($g_extifs{$extif}->{conntype} eq $TYPE_STATIC) {
            if (defined($g_extifs{$extif}->{is_wifi}) && ($g_extifs{$extif}->{is_wifi} eq $STATE_ENABLED)) {
                LogMsg("info", $extif, "restarting static wireless connection\n");
                system("/sbin/ifdown $extif >/dev/null 2>/dev/null");
                system("/sbin/ifup $extif >/dev/null 2>/dev/null");
            } else {
                LogMsg("info", $extif, "waiting for static IP reconnect\n");
            }
        } 
    }
}


###############################################################################
#
# CheckWanNetwork: Handle routing and WAN state based on current status
#
###############################################################################

sub CheckWanNetwork() {

    my $active_wans = ""; # All WAN interfaces that can reach the Internet
    my $backup_wans = ""; # Backup WAN interfaces that can reach the Internet
    my $nonbackup_wans = ""; # Non-backup WAN interfaces that can reach the Internet
    my $firewall_wans = "";  # List of WANs for the firewall
    my $wan_state = $STATUS_UNCHANGED;

    foreach my $extif (keys %g_extifs) {
        # Lots of debug logging for now
        if (($g_extifs{$extif}->{status} == $STATUS_OK) || ($g_extifs{$extif}->{status} == $STATUS_WARN)) {
            my $logline = $g_extifs{$extif}->{status} == $STATUS_OK ? "up" : "unstable";
            if (defined($g_extifs{$extif}->{backup}) && ($g_extifs{$extif}->{backup} eq $STATE_ENABLED)) {
                LogMsg("debug", $extif, "backup network is $logline\n") if $config{debug};
                $backup_wans = "$extif $backup_wans";
            } else {
                LogMsg("debug", $extif, "network is $logline\n") if $config{debug};
                $nonbackup_wans = "$extif $nonbackup_wans";
            }
            $active_wans = "$extif $active_wans";
        } elsif ($g_extifs{$extif}->{status} == $STATUS_DOWN) {
            LogMsg("debug", $extif, "network is down\n") if $config{debug};
        } else {
            LogMsg("debug", $extif, "network is in unknown state\n") if $config{debug};
        }
    }

    chop($active_wans);
    chop($backup_wans);
    chop($nonbackup_wans);

    if ((!$nonbackup_wans) && (!$backup_wans)) {
        LogMsg("debug", "system", "no WANS available for multiwan\n") if $config{debug};
    } elsif ((!$nonbackup_wans) && $backup_wans) {
        LogMsg("debug", "system", "backup WAN(s) now in use - $backup_wans\n") if $config{debug};
        $firewall_wans = $backup_wans;
    } else {
        $firewall_wans = $nonbackup_wans;
    }

    if (($g_active_wans eq $STATE_STARTUP) && ($active_wans eq "")) {
        LogMsg("info", "system", "WAN network is not up\n");

        if (! -e $FILE_STATE) {
            if (open(FILE_STATE, ">$FILE_STATE")) {
                print FILE_STATE "SYSWATCH_WANIF=\"\"\n";
                print FILE_STATE "SYSWATCH_WANOK=\"\"\n";
                close(FILE_STATE);
            } else {
                LogMsg("warn", "system", "failed to open state file $FILE_STATE - $!\n");
            }

            if (-x $CMD_WANUPDATE) {
                LogMsg("info", "system", "running custom failover event handler\n");
                system($CMD_WANUPDATE);
            }
        }

        return $STATUS_UNCHANGED;
    } elsif ($g_active_wans ne $active_wans) {
        $wan_state = $STATUS_CHANGED;
        my $active_wans_print = $active_wans;
        my $g_active_wans_print = $g_active_wans;

        $active_wans_print = "none" if (!$active_wans);
        $g_active_wans_print = "none" if (!$g_active_wans);
        LogMsg("info", "system", "changing active WAN list - $active_wans_print (was $g_active_wans_print)\n");
        if ($firewall_wans) {
            LogMsg("info", "system", "current WANs in use - $firewall_wans\n");
        } else {
            LogMsg("info", "system", "current WANs in use - none\n");
        }

        if (open(FILE_STATE, ">$FILE_STATE")) {
            print FILE_STATE "SYSWATCH_WANIF=\"$firewall_wans\"\n";
            print FILE_STATE "SYSWATCH_WANOK=\"$active_wans\"\n";
            close(FILE_STATE);
            $g_active_wans = $active_wans;
        } else {
            LogMsg("warn", "system", "failed to open state file $FILE_STATE - $!\n");
        }

        if (-x $CMD_WANUPDATE) {
            LogMsg("info", "system", "running custom failover event handler\n");
            system($CMD_WANUPDATE . " " . $firewall_wans);
        }
    }

    # Check to see if default route is still valid
    # Check to see if default route still exists in multi-WAN

    if ((keys %g_extifs) >= 2) {

        my $gatewaycheck = `/sbin/ip route | /bin/grep ^default | /bin/awk \'{ print \$5 }\'`;
        chomp($gatewaycheck);
        if ($gatewaycheck) {
            $_ = $firewall_wans;
            if (/$gatewaycheck/) {
                LogMsg("debug", "system", "default route still valid - $gatewaycheck\n") if $config{debug};
                return $wan_state;
            } else {
                LogMsg("warn", "system", "default route is not active... removing - $gatewaycheck\n");
                `/sbin/ip route del default >/dev/null 2>&1`;
                RestartFirewall();
            }
        }

        # We now have a blank default gateway.

        if ($firewall_wans) {
            my $gatewaydev = $firewall_wans;
            $gatewaydev =~ s/\s.*//;
            LogMsg("info", "system", "default route vanished - using $gatewaydev\n");
            my $defaultroute = GetRoute($gatewaydev);
            if ($defaultroute eq $STATE_UNKNOWN) {
                LogMsg("warn", "system", "no default gateway found\n");
            } else {
                LogMsg("info", "system", "setting default route to $defaultroute\n");
                $defaultroute = $1 if ($defaultroute =~ /^(.*)$/);  # Untaint (GetRoute validates it)
                `/sbin/ip route add default via $defaultroute`;
                RestartFirewall();
            }
        }
    }

    return $wan_state;
}


###############################################################################
#
# UpdateDyndns: Update the DNS record via the SDN
#
###############################################################################

sub UpdateDyndns() {
    $g_dns_retry = 0;

    if (! -e "$CMD_DYNDNS") {
        LogMsg("debug", "system", "dynamic DNS client not installed\n") if $config{debug};
        return;
    }

    my $force = "";
    $force = "force" if ($g_first_dyndns == $STATUS_YES);

    my $dns = `$CMD_DYNDNS $force`;

    chomp($dns);

    if ($dns =~ /exit_code.0/) {
        system($CMD_VPNUPDATE) if (-e $CMD_VPNUPDATE);
        LogMsg("info", "system", "dynamic DNS updated\n");
        $g_first_dyndns = $STATUS_NO if ($g_first_dyndns == $STATUS_YES);

    # Host key does not exist at pointclark.net... no need to retry
    #--------------------------------------------------------------

    } elsif ($dns =~ /exit_code.20/) {
        LogMsg("info", "system", "dynamic DNS not updated - not registered\n");

    # All other errors (database, connection...) are logged
    # and we'll try again on the next call to SleepHeartbeat().
    #---------------------------------------------------------

    } else {
        LogMsg("warn", "system", "dynamic DNS update failed - see system log\n");
        LogMsg("info", "system", "DNS update will try again on next heartbeat\n");
        $g_dns_retry = 1;
    }
}


###############################################################################
#
# GetPingServers
#
###############################################################################

sub GetPingServers() {

    # We let the user override the test servers (in /etc/syswatch).
    #-----------------------------------------------------------------------

    foreach my $extif (keys %g_extifs) {
        if ($config{ping_servers_autodetect} eq $STATE_DISABLED) {
            LogMsg("debug", $extif, "ping server specified in configuration file\n") if $config{debug};
            $g_extifs{$extif}->{ping_server1} = $config_ping_servers[0];
            $g_extifs{$extif}->{ping_server2} = $config_ping_servers[1];
        } elsif ((keys %g_extifs) >= 2) {
            LogMsg("debug", $extif, "ping server for multi-wan mode for $extif\n") if $config{debug};
            $g_extifs{$extif}->{ping_server1} = $config{ping_server1_default};
            $g_extifs{$extif}->{ping_server2} = $config{ping_server2_default}; 
        } else {
            LogMsg("debug", $extif, "ping server automatically detected\n") if $config{debug};
            my $defaultroute = GetRoute($extif);
            if ($defaultroute eq $STATE_UNKNOWN) {
                LogMsg("warn", $extif, "no default gateway found\n");
                $g_extifs{$extif}->{ping_server1} = $config{ping_server1_default} 
            } else {
                $g_extifs{$extif}->{ping_server1} = $defaultroute;
            }

            $g_extifs{$extif}->{ping_server2} = $config{ping_server1_default};
        }

        LogMsg("debug", $extif, "ping server #1 - " . $g_extifs{$extif}->{ping_server1} . "\n") if $config{debug};
        LogMsg("debug", $extif, "ping server #2 - " . $g_extifs{$extif}->{ping_server2} . "\n") if $config{debug};
    }
}


###############################################################################
#
# GetRoute: Get route for a given network interface
#
###############################################################################

sub GetRoute($) {
    my $extif = $_[0];
    my $gateway = $STATE_UNKNOWN;

    if ($g_extifs{$extif}->{ip} eq $STATE_UNKNOWN) {
        $gateway = $STATE_UNKNOWN;   # No IP ... don't bother looking for the gateway
    } elsif ($g_extifs{$extif}->{conntype} eq $TYPE_STATIC) {
        $gateway = $g_extifs{$extif}->{gateway};
    } elsif ($g_extifs{$extif}->{conntype} eq $TYPE_PPPOE) {
        $gateway = `LANG=en_US /sbin/ip addr ls $extif 2>/dev/null | /bin/grep peer | /bin/awk \'{ print \$4 }\' | /bin/sed 's/\\/.*//'`;
    } elsif ($g_extifs{$extif}->{conntype} eq $TYPE_DHCP) {
        if (-e "/var/lib/dhclient/$extif.routers") {
            $gateway = `/bin/cat /var/lib/dhclient/$extif.routers 2>/dev/null`;
        } else {
            LogMsg("debug", $extif, "network - gateway not found in DHCP state file\n") if $config{debug};
            $gateway = `/sbin/route -n | /bin/grep "^0.0.0.0[[:space:]]" | /bin/grep $extif | /bin/awk {\'print \$2\'}`;
            chomp($gateway);
            if ((!$gateway) || ($gateway eq "0.0.0.0") || ($gateway eq "Gateway")) {
                $gateway = $STATE_UNKNOWN;
            } else {
                LogMsg("debug", $extif, "network - gateway not found in DHCP state file\n") if $config{debug};
            }
        }
    } else {
        LogMsg("debug", $extif, "network - unable to lookup route\n") if $config{debug};
    }
    chomp($gateway);

    if (ValidateIp($gateway) == $STATUS_BORKED) {
        return $STATE_UNKNOWN;
    } else {
        return $gateway;
    }
}


###############################################################################
#
# GetIp: Try to get the IP address of our Internet connection
#
###############################################################################

sub GetIp($$) {
    my $extif = $_[0];
    my $type = $_[1];
    my $ip = $STATE_UNKNOWN;

	# Use IP referrer if appropriate
	#-------------------------------

    if ( (defined($g_extifs{$extif}->{status}) && ($g_extifs{$extif}->{status} == $STATUS_OK)) &&
         (defined($g_extifs{$extif}->{use_get_ip}) && ($g_extifs{$extif}->{use_get_ip} eq $STATE_ENABLED)) &&
         ($config{use_get_ip} eq $STATE_ENABLED)
        ) {

        $ip = $g_extifs{$extif}->{ip} if ($g_extifs{$extif}->{ip}); # default previous IP
        my $detected_ip = `$CMD_GETIP $extif`;

		if (length($detected_ip) > 0) {
			LogMsg("debug", $extif, "detected IP address: $detected_ip\n") if $config{debug};
			$ip = $detected_ip;
		} else {
			LogMsg("debug", $extif, "detected IP address was not found\n") if $config{debug};
		}

    # Use ip addr information to detect IP information
    #-------------------------------------------------

    } else {
        my $ip_addr = `LANG=en_US /sbin/ip addr show dev $extif label $extif scope global 2>/dev/null`;
        my $match = ($type eq "IPv4") ? "inet" : "inet6";

        if ($ip_addr) {
            my @lines = split(/\n/, $ip_addr);
            foreach my $line (@lines) {
                if ($line =~ /\s*$match\s+([^\s\/]*)/) {
                    $ip = $1;
                }
            }
        }
    }

    # Untaint hack - ValidateIp does the real validation
    $ip = $1 if ($ip =~ /^(.*)$/);

    if (ValidateIp($ip) == $STATUS_BORKED) {
        LogMsg("debug", $extif, "unable to determine IP address\n") if $config{debug};
        $ip = $STATE_UNKNOWN;
    }

    return $ip;
}


###############################################################################
#
# CheckIpChanges: Check to see if the IP has changed.  
#
###############################################################################

sub CheckIpChanges() {

    my $isnew = $STATUS_UNCHANGED;

    foreach my $extif (keys %g_extifs) {
        LogMsg("debug", $extif, "checking IP address for changes\n") if $config{debug};

        if (
            ($config{is_multiwan} eq $STATE_DISABLED) &&
            (($config{use_get_ip} eq $STATE_ENABLED) ||
            (defined($g_extifs{$extif}->{use_get_ip}) && ($g_extifs{$extif}->{use_get_ip} eq $STATE_ENABLED)))
            ) {
            if (!defined($g_extifs{$extif}->{use_get_ip_count})) {
                LogMsg("debug", $extif, "initializing get_ip count\n") if $config{debug};
                $g_extifs{$extif}->{use_get_ip_count} = 0;
            } elsif ($g_extifs{$extif}->{use_get_ip_count} == 10) {
                LogMsg("debug", $extif, "allowing get_ip request\n") if $config{debug};
                $g_extifs{$extif}->{use_get_ip_count} = 0;
            } else {
                LogMsg("debug", $extif, "skipping check on get_ip request - count $g_extifs{$extif}->{use_get_ip_count}\n") if $config{debug};
                $g_extifs{$extif}->{use_get_ip_count}++;
                next;
            }
        }

        my $ip = GetIp($extif, 'IPv4');
        my $ip_v6 = GetIp($extif, 'IPv6');
        my $oldip;
    
        if (open(DIR_STATE, "$DIR_STATE/$extif")) {
            $oldip = <DIR_STATE>;
            chomp($oldip);
            close(DIR_STATE);
        } else {
            if (open(DIR_STATE, ">$DIR_STATE/$extif")) {
                LogMsg("info", $extif, "creating IP cache - $ip\n");
                print DIR_STATE "$ip\n";
                close(DIR_STATE);
            } else {
                LogMsg("warn", $extif, "failed to open IP cache file $DIR_STATE/$extif - $!\n");
            }
        }
    
        if (($oldip) && ($ip) && ($ip ne $oldip)) {
            if ($ip eq $STATE_UNKNOWN) {
                LogMsg("info", $extif, "setting IP address cache to unknown\n");
            } else {
                LogMsg("info", $extif, "new IP address detected - $ip\n");
            }
            $isnew = $STATUS_CHANGED;
            if (open(DIR_STATE, ">$DIR_STATE/$extif")) {
                LogMsg("debug", $extif, "updating IP cache file\n") if $config{debug};
                print DIR_STATE "$ip\n";
                close(DIR_STATE);
            } else {
                LogMsg("warn", $extif, "failed to open IP cache file $DIR_STATE/$extif - $!\n");
            }
        }
    }

    return $isnew;
}

###############################################################################
#
# CheckNetworkBakFiles: Removes annoying .bak network configuration files
#
###############################################################################

sub CheckNetworkBakFiles() {
    my @ifaces = ('eth0', 'eth1');
    foreach my $iface (@ifaces) {
        if (-e "/etc/sysconfig/network-scripts/ifcfg-$iface.bak") {
            LogMsg("info", "system", "removing system generated backup file ifcfg-$iface.bak\n");
            system("/bin/rm -f /etc/sysconfig/network-scripts/ifcfg-$iface.bak") 
        }
    }
}

###############################################################################
#
# CheckNetworkConf: Sanity checks network configuration
#
###############################################################################

sub CheckNetworkConf() {
    if (!open(NETCONF, $FILE_NETWORK)) {
        return;
    }

    my @interfacesraw;

    while (<NETCONF>) {
        if ((/^EXTIF/) || (/^LANIF/) || (/^DMZIF/)) {
            s/"//g;
            s/.*=//;
            chomp();
            my @interfaces = split;
            foreach my $configuredif (@interfaces) {
                push(@interfacesraw, $configuredif);
            }
        }
    }

    close(NETCONF);

    foreach my $configuredif (@interfacesraw) {
        LogMsg("debug", "system", "checking interface role - $configuredif\n") if $config{debug};
        if (!defined($g_extifs{$configuredif}) || !defined($g_extifs{$configuredif}->{config})) {
            LogMsg("debug", "system", "network configuration has undefined interface - $configuredif\n") if $config{debug};
            # RemoveNetworkRole($configuredif);
        } elsif (defined($g_extifs{$configuredif}) && defined($g_extifs{$configuredif}->{onboot}) && ($g_extifs{$configuredif}->{onboot} eq $STATE_DISABLED)) {
            LogMsg("info", "system", "network configuration has inactive interface - $configuredif\n");
        }
    }
}

# TODO: this is no longer used... at least for now.  Remove at some point.
sub RemoveNetworkRole($) {
    my $interface = $_[0];

    if (!open(NETCONF, $FILE_NETWORK)) {
        LogMsg("warn", "system", "failed to open file $FILE_NETWORK: $!\n");
        return;
    }

    if (!open(NETCONFNEW, ">$FILE_NETWORK.new")) {
        LogMsg("warn", "system", "failed to open file $FILE_NETWORK.new: $!\n");
        return;
    }

    while (<NETCONF>) {
        if ((/^EXTIF/) || (/^LANIF/) || (/^DMZIF/)) {
            s/$interface //g;
            s/ $interface//g;
            s/$interface//g;
            print NETCONFNEW $_;
        } else {
            print NETCONFNEW $_;
        }
    }

    close(NETCONFNEW);
    close(NETCONF);

    system("/bin/mv $FILE_NETWORK.new $FILE_NETWORK");
}


###############################################################################
#
# CheckSecurityAudit: Temporary workaround for security audit
#
###############################################################################

sub CheckSecurityAudit() {
    if (-d $DIR_SECAUD) {
        if ((! -e $FILE_SECAUD) || (-z $FILE_SECAUD)) {
            LogMsg("info", "system", "updating security audit database\n");
            system("echo \"\@\@begin_db\" > $FILE_SECAUD");
            system("echo \"\@\@db_spec name lname attr perm uid gid size mtime ctime inode lcount md5 sha1\" >> $FILE_SECAUD");
            system("echo \"\@\@end_db\" >> $FILE_SECAUD");
            system("/bin/chmod 660 $FILE_SECAUD");
            system("/bin/chown root:suva $FILE_SECAUD");
        }

        if (! -e $FILE_SECAUDN) {
            LogMsg("info", "system", "initializing security audit database\n");
            system("/bin/touch $FILE_SECAUDN");
        }

        system("/bin/chmod 660 $FILE_SECAUDN");
        system("/bin/chown root:suva $FILE_SECAUDN");
    }
}

###############################################################################
#
# CheckUpgrade: Workaround for upgrades (temporary)
#
###############################################################################

sub CheckUpgrade() {
    if (-d "/etc/postfix") {
        system("/bin/touch /etc/postfix/transport") if (! -e "/etc/postfix/transport");
        if ((-e "/usr/sbin/postmap") && (! -e "/etc/postfix/transport.db")) {
            system("/usr/sbin/postmap /etc/postfix/transport") 
        }
    }

    if ((-e "/usr/sbin/eziod") && (! -e "/etc/rc1.d/K60eziod")) {
        LogMsg("info", "system", "enabling eziod\n");
        system("/sbin/chkconfig --add eziod");
    }
}


###############################################################################
#
# CheckResolv
#
###############################################################################

sub CheckResolv() {

    # This is a last minute hack to deal with a mysterious 
    # "nameserver 0.0.0.0" in /etc/resolv-peerdns.conf.

    my $fixit = 0;

    if (open(RESOLV, "/etc/resolv-peerdns.conf")) {
        while (<RESOLV>) {
            if (/nameserver.*0\.0\.0\.0/) {
                $fixit = 1;        
            }
        }
        close(RESOLV);

        if ($fixit) {
            LogMsg("info", "system", "fixing broken /etc/resolv-peerdns.conf\n");
            system("/bin/grep -v \"nameserver.*0\.0\.0\.0\" /etc/resolv-peerdns.conf > /etc/resolv-peerdns.conf.fixed");
            system("/bin/mv /etc/resolv-peerdns.conf.fixed /etc/resolv-peerdns.conf");
            system("/bin/rm -f /etc/resolv-peerdns.conf.save") if (-e "/etc/resolv-peerdns.conf.save");
        }
    }
}


###############################################################################
#
# CheckWhitelistConfig
# 
###############################################################################

sub CheckWhitelistConfig() {

    # Bail if DNS server list is missing
    #-----------------------------------

    return if (! -e $FILE_DNS);

    # Check for auto-whitelist config
    #--------------------------------

    return if (! -e $FILE_IDSCFG);

    if (! open(CONFIG, "$FILE_IDSCFG")) {
        LogMsg("warn", "system", "failed to open file  $FILE_IDSCFG: $!\n");
        return;
    }

    # Check to see if our auto-whitelist is already configured
    #---------------------------------------------------------

    my $match = 0;
    while (<CONFIG>) {
        $match = 1 if (/^include\s+$DIR_IDS\/$FILE_IDSLIST/);
    }
    close(CONFIG);

    if ($match) {
        LogMsg("debug", "system", "intrusion prevention already configured\n") if $config{debug};
        return;
    }

    # Add our configuration
    #----------------------

    if (! open(CONFIG, ">>$FILE_IDSCFG")) {
        LogMsg("warn", "system", "failed to open file  $FILE_IDSCFG: $!\n");
        return;
    }

    LogMsg("info", "system", "configuring intrusion prevention auto-whitelist\n");
    print CONFIG "include $DIR_IDS/$FILE_IDSLIST\n";
    close(CONFIG);
}


###############################################################################
#
# UpdateWhitelist
# 
###############################################################################

sub UpdateWhitelist() {

    return if (! -e $FILE_IDSCFG);
    
    # Bail if DNS server list is missing
    #-----------------------------------

    if (! -e $FILE_DNS) {
        LogMsg("debug", "system", "skipped intrusion prevention whitelist\n") if $config{debug};
        return;
    }

    # Bail if intrusion detection config directory is missing
    #--------------------------------------------------------

    if (! -d $DIR_IDS) {
        LogMsg("debug", "system", "skipped intrusion prevention whitelist\n") if $config{debug};
        return;
    }

    # Bail if md5sum is missing
    #--------------------------

    if (! -x $CMD_MD5SUM) {
        LogMsg("debug", "system", "unable to generate md5sum for intrusion prevention whitelist\n") if $config{debug};
        return;
    }

    # Create whitelist file
    #----------------------

    LogMsg("info", "system", "updating intrusion prevention whitelist\n");

    unlink("$DIR_IDS/$FILE_IDSLIST") if (-e "$DIR_IDS/$FILE_IDSLIST");
    if (! open(WHITELIST, ">$DIR_IDS/$FILE_IDSLIST")) {
        LogMsg("warn", "system", "could not write $DIR_IDS/$FILE_IDSLIST: $!\n");
        return;
    }

    if (! open(RESOLV, $FILE_DNS)) {
        LogMsg("warn", "system", "could not read $FILE_DNS: $!\n");
        return;
    }

    
    print WHITELIST "# This file is auto-generated by syswatch\n";

    # Add ping servers
    #------------------

    my %serverlist;
    foreach my $extif (keys %g_extifs) {
        $serverlist{$g_extifs{$extif}->{ping_server1}} = "yes";
        $serverlist{$g_extifs{$extif}->{ping_server2}} = "yes";
    }
    
    print WHITELIST "# Ping servers\n";
    foreach my $server (keys %serverlist) {
        print WHITELIST "dontblock $server\n";
        LogMsg("info", "system", "adding ping server $server\n");
    }

    # Add DNS servers to whitelist
    #-----------------------------    

    print WHITELIST "# DNS servers\n";
    while(<RESOLV>) {
        if (/^nameserver/) {
            my $nameserver = $_;
            $nameserver =~ s/^nameserver\s+//g;
            chomp($nameserver);
            print WHITELIST "dontblock $nameserver\n";
            LogMsg("info", "system", "adding DNS server $nameserver\n");
        }
    }

    # Add local IPs to whitelist
    #---------------------------

    my $ifconfig = `LANG=en_US /sbin/ifconfig 2>/dev/null`;

    if ($ifconfig) {
        my @lines = split(/\n/, $ifconfig);
        print WHITELIST "# System IP addresses\n";
        foreach my $line (@lines) {
            next if (!($line =~ /inet addr:/));
            next if ($line =~ /inet addr:.*127.0.0.1/);

            $line =~ s/^[^:]+:\s*//;
            $line =~ s/\s+.*//;
            $line =~ s/\s//g;
            print WHITELIST "dontblock $line\n";
        }
    }

    close(WHITELIST);
    close(RESOLV);

    # Restart intrusion prevention system if file has changed (see note above)
    #--------------------------------------------------------

    my $newmd5 = `$CMD_MD5SUM "$DIR_IDS/$FILE_IDSLIST"`;
    $newmd5 =~ s/\s+.*//;
    chomp($newmd5);
    LogMsg("debug", "system", "intrusion prevention whitelist md5sum (old/new) $g_ids_md5/$newmd5\n") if $config{debug};
    if ($newmd5 ne $g_ids_md5) {
        LogMsg("info", "system", "reloading intrusion prevention system\n");
        $g_ids_md5 = $newmd5;
        system($CMD_IDS);
    }
}


###############################################################################
#
# LogMsg: Log a message
#
###############################################################################

sub LogMsg($$$) {
    my $date = localtime;

    if (open(INFO, ">>$FILE_LOG")) {
        my $summary = sprintf("$date %5s: %7s - %s", $_[0], $_[1], $_[2]);
        print INFO $summary;
        close INFO;
    }
}


##############################################################################
#
# GetNetworkConfig: Grab network interface configuration
#
##############################################################################

sub GetNetworkConfig()  {
    my $log_it = $config{debug};
    my $log_level = "debug";

    if ($g_first_time == $STATUS_YES) {
        $log_level = "info";
        $log_it = 1;
    }

    LogMsg("info", $log_level, "loading network configuration\n") if $log_it;

    if (!opendir(DIR_NETWORK, "$DIR_NETWORK")) {
        LogMsg("warn", $log_level, "loading network configuration failed $DIR_NETWORK\n") if $log_it;
        return;
    }

    my @raw_list = readdir(DIR_NETWORK);
    closedir(DIR_NETWORK);

    # First run: look for DEVICE= parameter
    #--------------------------------------

    foreach my $eth_file (@raw_list) {
        next if (!($eth_file =~ /^ifcfg-/));
        next if ($eth_file =~ /^ifcfg-lo/);

        my $extif = $eth_file;
        $extif =~ s/ifcfg-//;

        if (open(ETH_FILE, "$DIR_NETWORK/$eth_file")) {
            while (<ETH_FILE>) {
                if (/^DEVICE=/) {
                    ($extif) = /^DEVICE=(.*)/; 
                }
            }

            close(ETH_FILE);
        }

        if (defined($g_extifs{$extif})) {
            $g_extifs{$extif}->{config} = $eth_file;
        }
    }

    # Second run: load configuration
    #--------------------------------

    foreach my $extif (keys %g_extifs) {

        if (open(ETH_FILE, "$DIR_NETWORK/" . $g_extifs{$extif}->{config})) {
            # defaults
            $g_extifs{$extif}->{conntype} = $TYPE_STATIC;
            $g_extifs{$extif}->{is_wifi} = $STATE_DISABLED;
            $g_extifs{$extif}->{onboot} = $STATE_DISABLED;

            while (<ETH_FILE>) {
                if (/^GATEWAY=/) {
                    my ($gateway) = /^GATEWAY=(.*)/; 
                    $gateway =~ s/\"//g; 
                    $g_extifs{$extif}->{gateway} = $gateway;
                } elsif (/^PPPOE_TIMEOUT/) {
                    $g_extifs{$extif}->{conntype} = $TYPE_PPPOE;
                } elsif (/^BOOTPROTO=.*dhcp/i) {
                    $g_extifs{$extif}->{conntype} = $TYPE_DHCP;
                } elsif (/^ESSID=/i) {
                    $g_extifs{$extif}->{is_wifi} = $STATE_ENABLED;
                } elsif (/^ONBOOT=.*yes/i) {
                    $g_extifs{$extif}->{onboot} = $STATE_ENABLED;
                }
            }

            close(ETH_FILE);
        }
    }

    foreach my $extif (keys %g_extifs) {
        LogMsg("info", $log_level, "network configuration for $extif -  config: " . $g_extifs{$extif}->{config} . "\n") if $log_it;;
        LogMsg("info", $log_level, "network configuration for $extif -  onboot: " . $g_extifs{$extif}->{onboot} . "\n") if $log_it;;
        LogMsg("info", $log_level, "network configuration for $extif -    type: " . $g_extifs{$extif}->{conntype} . "\n") if $log_it;;
        LogMsg("info", $log_level, "network configuration for $extif -    wifi: " . $g_extifs{$extif}->{is_wifi} . "\n") if $log_it;;

        if (defined($g_extifs{$extif}->{gateway})) {
            LogMsg("info", $log_level, "network configuration for $extif - gateway: " . $g_extifs{$extif}->{gateway} . "\n") if $log_it;;
        }
    }
}


##############################################################################
#
# GetNetworkInfo: Grab interface definitions
# 
# g_extif
#  - interface_name (eth0)
#     - ip
#     - status
#     - conntype
#     - use_get_ip
#     - is_wifi
#
##############################################################################

sub GetNetworkInfo()  {

    my $log_it = $config{debug};
    my $log_level = "debug";

    if ($g_first_time == $STATUS_YES) {
        $log_level = "info";
        $log_it = 1;
    }

    foreach my $extif (keys %g_extifs) {

        # Do not reload working interfaces
        #---------------------------------

        if (defined($g_extifs{$extif}->{status}) && ($g_extifs{$extif}->{status} eq $STATUS_OK)) {
            LogMsg($log_level, $extif, "network - ok\n") if $log_it;
            next;
        }

        # Grab IP information (if available)
        #-----------------------------------

        my $ip = GetIp($extif, 'IPv4');
        my $ipv6 = GetIp($extif, 'IPv6');
        $g_extifs{$extif}->{ip} = $ip;
        $g_extifs{$extif}->{ipv6} = $ipv6;
        LogMsg($log_level, $extif, "network - IP address - $ip\n") if $log_it;
        if ($ipv6 ne $STATE_UNKNOWN) {
            LogMsg($log_level, $extif, "network - IPv6 address - $ipv6\n") if $log_it
        }

        # Grab gateway information (if available)
        #----------------------------------------

        my $gateway = GetRoute($extif);
        $g_extifs{$extif}->{gateway} = $gateway;
        LogMsg($log_level, $extif, "network - gateway - " . $g_extifs{$extif}->{gateway} . "\n") if $log_it;

        # Detect public/private network
        #------------------------------

        if ($ip eq $STATE_UNKNOWN) {
            $g_extifs{$extif}->{use_get_ip} = $STATE_DISABLED;
            LogMsg($log_level, $extif, "network - type - to be determined\n") if $log_it;
        } elsif (($ip =~ /^192\.168/) || ($ip =~ /^172\.16/) || ($ip =~ /^10\./)) {
            $g_extifs{$extif}->{use_get_ip} = $STATE_ENABLED;
            LogMsg($log_level, $extif, "network - type - private IP range\n") if $log_it;
        } else {
            $g_extifs{$extif}->{use_get_ip} = $STATE_DISABLED;
            LogMsg($log_level, $extif, "network - type - public IP range\n") if $log_it;
        }

        # Set status to down on startup
        #------------------------------

        if (!defined($g_extifs{$extif}->{status})) {
            $g_extifs{$extif}->{status} = $STATUS_DOWN;
            $g_extifs{$extif}->{downcount2} = 99;
            # TODO: use some kind of "init" flag instead of 99 hack above
        }
    }

    $g_first_time = $STATUS_NO;

    return 0;
}


##############################################################################
#
# ReadConfig: Grab configuration file info
#
##############################################################################

sub ReadConfig() {
    my $pingserverlist;

    my $config_interval;
    my $config_retries;
    my $config_heartbeat;

    #====================================================================
    # Read main config - /etc/syswatch
    #====================================================================

    if (open(FILE_CFG, "$FILE_CFG")) {
        while (<FILE_CFG>) {
            if (/^retries=/)        { ($config_retries) = /^retries=(.*)/; }
            if (/^heartbeat=/)      { ($config_heartbeat) = /^heartbeat=(.*)/; }
            if (/^interval=/)       { ($config_interval) = /^interval=(.*)/; }
            if (/^use_get_ip=/)     { ($config{use_get_ip}) = /^use_get_ip=(.*)/; }
            if (/^debug=/)          { ($config{debug}) = /^debug=(.*)/; }
            if (/^failed_interval=/) { ($config{failed_interval}) = /^failed_interval=(.*)/; }
            if (/^ping_servers=/) { 
                $config{ping_servers_autodetect} = $STATE_DISABLED;
                ($pingserverlist) = /^ping_servers=(.*)/; 
                @config_ping_servers = split(/[,\s]+/, $pingserverlist);
            }
            if (/^try_pinging_gateway=no/) { ($config{try_pinging_gateway}) = $STATUS_NO; }
        }
        close(FILE_CFG);
    }

    #====================================================================
    # Read network config
    #====================================================================

    # Open network configuration and grab a list of WAN interfaces
    #--------------------------------------------------------------

    my @ext_interfaces;

    if (! open(FILE_NETWORK, "$FILE_NETWORK")) {
        LogMsg("info", "config", "network configuration not available ($FILE_NETWORK)\n");
    } else {
        while (<FILE_NETWORK>) {
            if (/^EXTIF=/) {
                s/"//g;
                s/.*=//;
                chomp();
                @ext_interfaces = split;
            }
        }
        close(FILE_NETWORK);
    }

    # Multi-WAN backup interfaces
    #----------------------------

    my @backup_interfaces;

    if (! open(FILE_MULTIWAN, "$FILE_MULTIWAN")) {
        LogMsg("debug", "config", "failed to open file $FILE_MULTIWAN: $!\n") if $config{debug};
    } else {
        while (<FILE_MULTIWAN>) {
            if (/^EXTIF_BACKUP=/) {
                s/"//g;
                s/.*=//;
                chomp();
                @backup_interfaces = split;
            }
        }
        close(FILE_MULTIWAN);
    }

    # WAN interface: validate, determine connection type, find IP
    #------------------------------------------------------------

    foreach my $backupif (@backup_interfaces) {
        if (!($backupif =~ /^([a-z0-9\.]+)$/)) {
            LogMsg("warn", "config", "invalid backup WAN interface - $backupif\n");
        } else {
            $backupif = $1; # Untainted
            $g_extifs{$backupif}->{backup} = $STATE_ENABLED;
        }
    }

    foreach my $extif (@ext_interfaces) {
        if (!($extif =~ /^([a-z0-9\.]+)$/)) {
            LogMsg("warn", "config", "invalid WAN interface - $extif\n");
        } else {
            $extif = $1; # Untainted
            $g_extifs{$extif}->{init} = "true";
        }
    }

    #====================================================================
    # Set sane defaults based on number of WANS
    #====================================================================

    # Set sane defaults (1-WAN vs Multi-WAN)
    #--------------------------------------

    my $numwans = keys %g_extifs;

    if (defined($config_interval)) {
        $config{interval} = $config_interval;
    } else {
        $config{interval} = 20 if ($numwans >= 2);
    }

    if (defined($config_retries)) {
        $config{retries} = $config_retries;
    } else {
        $config{retries} = 3 if ($numwans >= 2);
    }

    if (defined($config_heartbeat)) {
        $config{heartbeat} = $config_heartbeat;
    } else {
        $config{heartbeat} = 15 if ($numwans >= 2);
    }

    # Sanity check - disable IP referrer lookups on multi-WAN systems
    #----------------------------------------------------------------

    if ($numwans >= 2)  {
        $config{is_multiwan} = $STATE_ENABLED;
        $config{use_get_ip} = $STATE_DISABLED;
        LogMsg("info", "config", "IP referrer disabled in multi-WAN\n");
    }

    #====================================================================
    # See if getip IP referrer tool is installed
    #====================================================================

    if (-e $CMD_GETIP) {
        LogMsg("info", "config", "IP referrer tool is installed\n");
    } else {
        $config{use_get_ip} = $STATE_DISABLED;
        LogMsg("info", "config", "IP referrer tool is not installed\n");
	}

    #====================================================================
    # Log our configuration
    #====================================================================

    LogMsg("info", "config", "debug level - " . $config{debug} . "\n");
    LogMsg("info", "config", "retries - " . $config{retries} . "\n");
    LogMsg("info", "config", "heartbeat - " . $config{heartbeat} . "\n");
    LogMsg("info", "config", "interval - " . $config{interval} . " seconds\n");
    LogMsg("info", "config", "offline interval - " . $config{failed_interval} . " seconds\n");
    LogMsg("info", "config", "referrer IP detection - " . $config{use_get_ip} . "\n");
    LogMsg("info", "config", "ping server auto-detect - " . $config{ping_servers_autodetect} . "\n");

    if ($config{try_pinging_gateway} eq $STATUS_YES) {
        LogMsg("info", "config", "try pinging gateway - yes\n");
    } else {
        LogMsg("info", "config", "try pinging gateway - no\n");
    }

    if ($config{ping_servers_autodetect} eq $STATE_DISABLED) {
        foreach my $server (@config_ping_servers) {
            LogMsg("info", "config", "ping server - $server\n");
        }
    }

    LogMsg("info", "config", "number of external networks - $numwans\n");
    foreach my $extif (keys %g_extifs) {
        if (defined($g_extifs{$extif}->{backup}) && ($g_extifs{$extif}->{backup} eq $STATE_ENABLED)) {
            LogMsg("info", "config", "monitoring external network (backup only) - $extif\n");
        } else {
            LogMsg("info", "config", "monitoring external network - $extif\n");
        }
    }
}


###############################################################################
#
# ValidateIp: validate an IP address
#
###############################################################################

sub ValidateIp($) {
    my $ip = $_[0];

    return $STATUS_BORKED if (!$ip);

    # IPv4 check
    if ($ip =~ /\./) {
        my (@parts) = split(/\./, $ip);

        return $STATUS_BORKED if ($#parts != 3);

        foreach my $part (@parts) {
            return $STATUS_BORKED if ($part =~ /\D/);
            return $STATUS_BORKED if (($part > 255) || ($part < 0));
        }
    # IPv6 check
    } elsif ($ip =~ /\:/) {
        return $STATUS_BORKED if (!($ip =~ /^[0-9A-Fa-f:]+$/));
    } else {
        return $STATUS_BORKED;
    }

    return $STATUS_OK;
}


###############################################################################
#
# SignalInterrupt: Signal catcher
#
###############################################################################

sub SignalInterrupt() {
    unlink($FILE_PID);
    LogMsg("info", "system", "syswatch terminated\n");
    system("/bin/rm -f $FILE_STATE") if (-e $FILE_STATE);

    exit 0;
}

sub SignalReload() {
    LogMsg("info", "system", "resetting syswatch\n");
    ReadConfig();
    GetPingServers();
}

sub SignalWebconfigWorkaround() {
    LogMsg("info", "system", "webconfig startup workaround requested\n");
    system("/sbin/service webconfig condrestart");
}
