#!/usr/bin/perl -w
#----------------------------------------------------------------------
# Ipsec actions
# Copyright (C) 2015 John Crisp
#
# 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 or 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
#----------------------------------------------------------------------

# Note that we do not need to use the init ipsec script - we can start and
# stop directly using /usr/sbin/ipsec which will call the init script

use strict;
use warnings;
use esmith::ConfigDB;

my $configDB = esmith::ConfigDB->open or die("can't open Config DB");
my $ipsecDB = esmith::ConfigDB->open('ipsec_connections')
  or die("Ipsec Error - cant connect to ipsec database");

my $dbKey = 'ipsec';

# Check on access status - we'll use this later
# If status goes to disabled we should set this private

my $ipsec_access = $configDB->get_prop( $dbKey, 'access' ) || 'private';
print "Ipsec Information - IpsecAccessState: $ipsec_access\n";

# If the service is set disabled then make sure it is stopped
# Note that ipsec is not a service so we cannot use the normal service commands

if ( $configDB->get_prop( $dbKey, 'status' ) eq 'disabled' ) {

    # Do we check if it is already stopped ?
    # For now we stop it regardless

    print "Ipsec Information - ipsec disabled - Stopping ipsec \n";

    # First set ipsec access to private which disables firewall rule
    # Is this the correct syntax - what about die ?
    # This is problematic as masq templates are already expanded and may be wrong

    # Make sure access = private
    # No point in this unless we expand the masq template again
    
    #unless ( $ipsec_access eq 'private' ) {
    #    $configDB->set_prop( $dbKey, 'access', 'private' );
    #}

    my $myStopConnection = qx(/etc/rc.d/init.d/ipsec stop);
    die("Ipsec Error - Unable to launch ipsec stop : $!\n")

      if not defined $myStopConnection;
    die("Ipsec Error - Unable to stop ipsec( error code $?)\n") if $?;

    print "Ipsec Information - reset redirects";
    resetRedirects();

    exit 0;
}

# If the service is set to enabled AND running (then check the connections)

if ( $configDB->get_prop( $dbKey, 'status' ) eq 'enabled' ) {

    # Make sure access = public
    # No point in this unless we expand the masq template again
    
    #unless ( $ipsec_access eq 'public' ) {
    #    $configDB->set_prop( $dbKey, 'access', 'public' );
    #}

    my $status = (`ps ax | grep -v grep | grep pluto`);

    #If the service is running
    if ( $status =~ m/_plutorun/ ) {

        # Lets do some stuff
        print "Ipsec Information - ipsec is running !\n";

        # make sure reDirects are right
        setRedirects();

        # Load the connections
        my @connections = $ipsecDB->keys;

        foreach my $ipsecprop (@connections) {

            #Check the individual connection status
            my $ipsecstatus = $ipsecDB->get_prop( "$ipsecprop", 'status' )
              || "disabled";

            # What type of connection are we ?
            my $connection = $ipsecDB->get_prop( "$ipsecprop", 'auto' ) || '';

            # Lets check the last state and if it doesn't exist set it disabled
            if ( not defined( $ipsecDB->get_prop( $ipsecprop, 'PreviousState' ) ) ) {
                my $previpsecstatus = "disabled";
                $ipsecDB->set_prop( $ipsecprop, "PreviousState", $previpsecstatus );
            }

            # Now we should have it
            my $previpsecstatus = $ipsecDB->get_prop( $ipsecprop, 'PreviousState' );

            print "Ipsec Information - PrevState: $previpsecstatus CurrState: $ipsecstatus\n";

            # Lets reread secrets anyway
            print "Ipsec Information - Restart - ReReading Secrets\n";
            my $reread = qx(/usr/sbin/ipsec auto --rereadsecrets);

            die("Ipsec Error - Unable launch ipsec reread secrets : $!\n")
              if not defined $reread;
            die("Ipsec Error - Unable to reread ipsec secrets ( error code $?)\n")
              if $?;

            # If we are enabled
            if (    ( $previpsecstatus eq "enabled" )
                 && ( $ipsecstatus eq "enabled" ) ) {

                # Restart
                print "Ipsec Information - Restarting connection - $ipsecprop\n";

                # Have to use system here as replace usually returns 1280
                # Replace just rereads the config and does --delete --add
                system("/usr/sbin/ipsec auto --replace $ipsecprop");
                print "Ipsec Information - Restart system - replace return code: $?\n";

                # If connection = start then bring it up
                if ( $connection eq 'start' ) {
                    print "Ipsec Information - En - En - Auto --async --up $ipsecprop\n";

                    # If it is start rather than add we try and force it to come up
                    startConnection($ipsecprop);
                    print "Ipsec Information - En - En auto --up\n";
                    print "Ipsec Information - Restart system - up return code: $?\n";
                }

                # Set Previous status
                changeState( $ipsecprop, $ipsecstatus );

            }

            # If status is disabled then stop it
            elsif (    ( $previpsecstatus eq "disabled" )
                    && ( $ipsecstatus eq "disabled" ) ) {

                # Stop
                print "Ipsec Information - Stop connection - $ipsecprop\n";
                stopConnection($ipsecprop);

                # Set Previous status
                changeState( $dbKey, $ipsecstatus );
            }

            # If status was disabled and now enabled then start it
            elsif (    ( $previpsecstatus eq "disabled" )
                    && ( $ipsecstatus eq "enabled" ) ) {

                # Start
                print "Enabling connection $ipsecprop\n";

                # Have to use system here as replace usually returns 1280 and not 0
                system("/usr/sbin/ipsec auto --replace $ipsecprop");
                print "Ipsec Information - Restart system - return code: $?\n";

                if ( $connection eq 'start' ) {

                    # Have to use exec here as system waits for a return and if the connection
                    # does not come up it will just hang. So fire 'n forget
                    print "Ipsec Information - Dis- En -  - Auto --async --up $ipsecprop\n";

                    startConnection($ipsecprop);
                    print "Ipsec Information - Dis - En auto --up\n";
                    print "Ipsec Information - Restart system - up return code: $?\n";

                    #or die "exec failed!";
                }

                # Set Previous status
                changeState( $ipsecprop, $ipsecstatus );
            }

            # If status was enabled and now disabled then stop it
            elsif (    ( $previpsecstatus eq "enabled" )
                    && ( $ipsecstatus eq "disabled" ) ) {

                # Stop and remove - do we need to  ?
                print "Ipsec Information - Stopping connection $ipsecprop\n ";
                stopConnection($ipsecprop);

                # Set Previous status
                changeState( $ipsecprop, $ipsecstatus );
            }

            # Should never be here as it means the statuses are other than enabled or disabled
            else {
                print "Ipsec Error - Something went wrong with ipsec connection status\n";
            }

        }

    }

    # If it isn't running then start it up
    # Auto connections start themselves. Added connections wait
    else {
        print "Ipsec Information - Disable Reverse Path Filtering\n";
        setRedirects();

        # Make sure access = public
        unless ( $ipsec_access eq 'public' ) {
            $configDB->set_prop( $dbKey, 'access', 'public' );
        }

        print "Ipsec Information - ipsec enabled - Starting ipsec\n ";
        my $myStartConnection = qx(/etc/rc.d/init.d/ipsec start);
        die("Ipsec Error - Unable to launch ipsec start : $!\n ")
          if not defined $myStartConnection;
        die("Ipsec Error - Unable to launch ipsec start ( error code $?)\n ") if $?;

        exit 0;
    }

    exit 0;

}

#### Subroutines here

sub changeState {

    #@_ contains $dbKey and $ipsecstatus
    $ipsecDB->set_prop( $_[0], 'PreviousState', $_[1] );
}

sub startConnection {
    system("/usr/sbin/ipsec auto --asynchronous --up $_[0]");
}

sub stopConnection {
    print "Ipsec Information - SubRoutine - stop connection $_[0]\n ";
    system("/usr/sbin/ipsec auto --down $_[0]");
    print "Ipsec Information - system down code: $?\n";
    system("/usr/sbin/ipsec auto --delete $_[0]");
    print "Ipsec Information - system delete code: $?\n";
}

sub setRedirects {

    # Big warning - this is a potential security issue
    # Make sure you read and understand what happens !
    # If I knew which specific interfaces to change we could reduce the lines here
    system("/sbin/sysctl -w net.ipv4.conf.all.send_redirects=0") == 0
      or die("Ipsec Error - A problem occurred with sysctl: $?");
    system("/sbin/sysctl -w net.ipv4.conf.default.send_redirects=0") == 0
      or die("Ipsec Error - A problem occurred with sysctl: $?");

    system("/sbin/sysctl -w net.ipv4.conf.all.accept_redirects=0") == 0
      or die("Ipsec Error - A problem occurred with sysctl: $?");
    system("/sbin/sysctl -w net.ipv4.conf.default.accept_redirects=0") == 0
      or die("Ipsec Error - A problem occurred with sysctl: $?");

    system("/sbin/sysctl -w net.ipv4.conf.default.rp_filter=0") == 0
      or die("Ipsec Error - A problem occurred with sysctl: $?");
    system("/sbin/sysctl -w net.ipv4.conf.all.rp_filter=0") == 0
      or die("Ipsec Error - A problem occurred with sysctl: $?");
    system("/sbin/sysctl -w net.ipv4.conf.eth0.rp_filter=0") == 0
      or die("Ipsec Error - A problem occurred with sysctl: $?");
    system("/sbin/sysctl -w net.ipv4.conf.eth1.rp_filter=0") == 0
      or die("Ipsec Error - A problem occurred with sysctl: $?");

    # On v8 this is set to 0 so we would need
    # system ("/sbin/sysctl -w net.core.xfrm_larval_drop=1") == 0 or die ("A problem occurred with sysctl: $?");

}

sub resetRedirects {

    #  /etc/syctl.conf is expanded on ipsec-update
    # This should reload the file - if ipsec is disabled it should reset to defaults
    # If ipsec is enabled it should disable rp_filtering
    system("/sbin/sysctl -p") == 0
      or die("Ipsec Error - A problem occurred with sysctl: $?");
}

