#!/usr/bin/perl
#
# Module: vpn-config.pl
#
# **** License ****
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 as
# published by the Free Software Foundation.
#
# 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.
#
# This code was originally developed by Vyatta, Inc.
# Portions created by Vyatta are Copyright (C) 2006, 2007, 2008, 2009 Vyatta, Inc.
# All Rights Reserved.
#
# Authors: Justin Fletcher, Marat Nepomnyashy
# Date: 2007
# Description: Start Openswan VPN based on verified configuration
#
# **** End License ****
#

use strict;
use lib "/opt/vyatta/share/perl5";

use constant IKELIFETIME_DEFAULT => 28800;    # 8 hours
use constant ESPLIFETIME_DEFAULT => 3600;     # 1 hour
use constant REKEYMARGIN_DEFAULT => 540;      # 9 minutes
use constant REKEYFUZZ_DEFAULT   => 100;
use constant INVALID_LOCAL_IP    => 254;
use constant VPN_MAX_PROPOSALS   => 10;

use Vyatta::TypeChecker;
use Vyatta::VPN::Util;
use Getopt::Long;
use Vyatta::Misc;
use Vyatta::VPN::vtiIntf;

my $config_file;
my $secrets_file;
my $init_script;
GetOptions(
    "config_file=s"   => \$config_file,
    "secrets_file=s"  => \$secrets_file,
    "init_script=s"   => \$init_script
);
my $CA_CERT_PATH = '/etc/ipsec.d/cacerts';
my $CRL_PATH = '/etc/ipsec.d/crls';
my $SERVER_CERT_PATH = '/etc/ipsec.d/certs';
my $SERVER_KEY_PATH = '/etc/ipsec.d/private';
my $LOGFILE = '/var/log/vyatta/ipsec.log';
my $STRONGSWAN_INTF_CONFIG = '/etc/strongswan.d/interfaces_use.conf';

my $vpn_cfg_err   = "VPN configuration error:";
my $clustering_ip = 0;
my $dhcp_if = 0;
my $genout;
my $genout_secrets;
my $interfaces_use;
my %key_file_list;
my %public_keys;

$genout         .= "# generated by $0\n\n";
$genout_secrets .= "# generated by $0\n\n";
$interfaces_use .= "# generated by $0\n\n";

#
# Prepare Vyatta::Config object
#
use Vyatta::Config;
my $vc    = new Vyatta::Config();
my $vcVPN = new Vyatta::Config();
$vcVPN->setLevel('vpn');

# check to see if the config has changed.
# if it has not then exit
my $ipsecstatus = $vcVPN->isChanged('ipsec');
my $rsastatus = $vcVPN->isChanged('rsa-keys');
if (!$ipsecstatus && !$rsastatus) {
    exit 0;
}
if ($vcVPN->exists('ipsec')) {

    #
    # Check that ESP groups have been specified
    #
    my @esp_groups = $vcVPN->listNodes('ipsec esp-group');
    if (@esp_groups == 0) {

        #print STDERR "$vpn_cfg_err No ESP groups configured." .
        #             " At least one ESP group required.\n";
        # XXX for now this will be checked below for site-to-site peer

    } else {
        foreach my $esp_group (@esp_groups) {
            my @esp_group_proposals =$vcVPN->listNodes("ipsec esp-group $esp_group proposal");
            if (@esp_group_proposals == 0) {
                vpn_die(["vpn","ipsec","esp-group",$esp_group,"proposal"],"$vpn_cfg_err No proposals configured for ESP group \"$esp_group\"".
                         ".  At least one proposal required.\n");
            } elsif (@esp_group_proposals > VPN_MAX_PROPOSALS) {
                vpn_die(["vpn","ipsec","esp-group",$esp_group],"$vpn_cfg_err A total of ". @esp_group_proposals.
                         " proposals have been configured for ESP group \"$esp_group\".". " The maximum proposals allowed for an ESP group is ". VPN_MAX_PROPOSALS . "\n");
            } else {
                foreach my $esp_group_proposal (@esp_group_proposals) {
                    my $esp_group_proposal_encryption = $vcVPN->returnValue("ipsec esp-group $esp_group proposal $esp_group_proposal encryption");
                    if (!defined($esp_group_proposal_encryption)
                      || $esp_group_proposal_encryption eq "")
                    {
                        vpn_die(["vpn","ipsec","esp-group",$esp_group,"proposal",$esp_group_proposal,"encryption"],"$vpn_cfg_err No encryption specified for ESP group ".
                                 "\"$esp_group\" proposal $esp_group_proposal.\n");

                    }
                    my $esp_group_proposal_hash = $vcVPN->returnValue("ipsec esp-group $esp_group proposal $esp_group_proposal hash");
                    if (!defined($esp_group_proposal_hash)
                      || $esp_group_proposal_hash eq "") {
                        vpn_die(["vpn","ipsec","esp-group",$esp_group,"proposal",$esp_group_proposal,"hash"],"$vpn_cfg_err No hash specified for ESP group \"$esp_group\"".
                                 " proposal $esp_group_proposal.\n");
                    }
                }
            }
        }
    }

    #
    # Check that IKE groups have been specified
    #
    my @ike_groups = $vcVPN->listNodes('ipsec ike-group');
    if (@ike_groups == 0) {
        #print STDERR "$vpn_cfg_err No IKE groups configured." .
        #             "  At least one IKE group required.\n";
        # XXX for now this will be checked below for site-to-site peer
    } else {
        foreach my $ike_group (@ike_groups) {
            my @ike_group_proposals =$vcVPN->listNodes("ipsec ike-group $ike_group proposal");
            if (@ike_group_proposals == 0) {
                vpn_die(["vpn","ipsec","ike-group",$ike_group, "proposal"],"$vpn_cfg_err No proposals configured for IKE group \"$ike_group\"".
                         ".  At least one proposal required.\n");
            } elsif (@ike_group_proposals > VPN_MAX_PROPOSALS) {
                vpn_die(["vpn","ipsec","ike-group",$ike_group],"$vpn_cfg_err A total of ". @ike_group_proposals.
                         " proposals have been configured for IKE group \"$ike_group\".". " The maximum proposals allowed for an IKE group is ". VPN_MAX_PROPOSALS . "\n");
            } else {
                foreach my $ike_group_proposal (@ike_group_proposals) {
                    my $ike_group_proposal_encryption = $vcVPN->returnValue("ipsec ike-group $ike_group proposal $ike_group_proposal encryption");
                    if (!defined($ike_group_proposal_encryption)
                        || $ike_group_proposal_encryption eq "")
                    {
                        vpn_die(["vpn","ike-group",$ike_group,"proposal",$ike_group_proposal,"encryption"],"$vpn_cfg_err No encryption specified for IKE group ".
                                 "\"$ike_group\" proposal $ike_group_proposal.\n");
                    }
                    my $ike_group_proposal_hash = $vcVPN->returnValue("ipsec ike-group $ike_group proposal $ike_group_proposal hash");
                    if (!defined($ike_group_proposal_hash)
                      || $ike_group_proposal_hash eq "") {
                        vpn_die(["vpn","ike-group",$ike_group,"proposal",$ike_group_proposal,"hash"],"$vpn_cfg_err No hash specified for IKE group \"$ike_group\"".
                                 " proposal $ike_group_proposal.\n");
                    }
                }
            }
        }
    }

    #
    # Check the local key file
    # Note: $local_key_file will be used later when reading the keys
    #
    my $running_local_key_file = rsa_get_local_key_file();
    my $local_key_file         = $vcVPN->returnValue('rsa-keys local-key file');
    if (!defined($local_key_file)) {
        $local_key_file = LOCAL_KEY_FILE_DEFAULT;
    }
    if ($local_key_file ne $running_local_key_file) {

        # Sanity check the usr specified local_key_file
        #
        # 1). Must start with "/"
        # 2). Only allow alpha-numeric, ".", "-", "_", or "/".
        # 3). Don't allow "//"
        # 4). Verify that it's not a directory
        #
        if ($local_key_file !~ /^\//) {
            vpn_die(["vpn", "rsa-keys", "local-key", "file"],"$vpn_cfg_err Invalid local RSA key file path \"$local_key_file\".". "  Does not start with a '/'.\n");
        }
        if ($local_key_file =~ /[^a-zA-Z0-9\.\-\_\/]/g) {
            vpn_die(["vpn", "rsa-keys", "local-key", "file"],"$vpn_cfg_err Invalid local RSA key file path \"$local_key_file\".".
                     " Contains a character that is not alpha-numeric and not '.', '-', '_', '/'.\n");
        }
        if ($local_key_file =~ /\/\//g) {
            vpn_die(["vpn", "rsa-keys", "local-key", "file"],"$vpn_cfg_err Invalid local RSA key file path \"$local_key_file\".". " Contains string \"//\".\n");
        }
        if (-d $local_key_file) {
            vpn_die(["vpn", "rsa-keys", "local-key", "file"],"$vpn_cfg_err Invalid local RSA key file path \"$local_key_file\".". " Path is a directory rather than a file.\n");
        }

        if (-r $running_local_key_file && !(-e $local_key_file)) {
            vpn_debug "cp $running_local_key_file $local_key_file";
            my ($dirpath) = ($local_key_file =~ m#^(.*/)?.*#s);
            my $rc = system("mkdir -p $dirpath");
            if ($rc != 0) {
                vpn_die(["vpn", "rsa-keys", "local-key", "file"],"$vpn_cfg_err Could not copy previous local RSA key file ".
                         "\"$running_local_key_file\" to new local RSA key file ". "\"$local_key_file\".  Could not mkdir [$dirpath] $!\n");
            } else {
                $rc = system("cp $running_local_key_file $local_key_file");
                if ($rc != 0) {
                    vpn_die(["vpn", "rsa-keys", "local-key", "file"],"$vpn_cfg_err Could not copy previous local RSA key file ".
                             "\"$running_local_key_file\" to new local RSA key file ". "\"$local_key_file\".  $!\n");
                }
            }
        }
    }

    #
    # Configuration of system wide options
    #
    $genout .= "config setup\n";


    #
    # Interfaces
    #
    my @interfaces = $vcVPN->returnValues('ipsec ipsec-interfaces interface');
    if (scalar(@interfaces) > 0) {

        $interfaces_use .= "charon {\n\tinterfaces_use = ";
        foreach my $interface (@interfaces) {
            if (!(-d "/sys/class/net/$interface")) {
                print "Warning: unable to configure non-existent interface\n";
            }
            $interfaces_use .= "$interface, ";
        }
        $interfaces_use .= "\n}";

        my $counter = 0;
        $genout .= "\t";
        if (hasLocalWildcard($vcVPN, 0)) {
            $genout .= 'interfaces="';
            foreach my $interface (@interfaces) {
                next if !-d "/sys/class/net/$interface";
                next if scalar(getIP($interface)) < 1;
                $genout .= "ipsec$counter=$interface ";
                ++$counter;
            }
            $genout .= '%defaultroute"';
        }
        $genout .= "\n";

    } else {
         $interfaces_use .= "";

    }
    #
    # NAT traversal
    #
    my $nat_traversal = $vcVPN->returnValue('ipsec nat-traversal');
    if (defined($nat_traversal)) {
        if ($nat_traversal eq 'enable') {
            $genout .= "\tnat_traversal=yes\n";
        } elsif ($nat_traversal eq 'disable') {
            $genout .= "\tnat_traversal=no\n";
        } elsif ($nat_traversal ne '') {
            vpn_die(["vpn", "ipsec", "nat-traversal"],"$vpn_cfg_err Invalid value \"$nat_traversal\" specified for ".
                     "'nat-traversal'.  Only \"enable\" or \"disable\" accepted.\n");
        }
    }

    #
    # NAT networks
    #
    my @nat_networks = $vcVPN->listNodes('ipsec nat-networks allowed-network');
    if (@nat_networks > 0) {
        my $first_nat_net = 1;
        foreach my $nat_network (@nat_networks) {
            if ($first_nat_net) {
                $genout .= "\tvirtual_private=\"\%v4:$nat_network";
                $first_nat_net = 0;
            } else {
                $genout .= ",\%v4:$nat_network";
            }

            my @nat_network_excludes = $vcVPN->returnValues("ipsec nat-networks allowed-network $nat_network exclude");
            foreach my $nat_network_exclude (@nat_network_excludes) {
                $genout .= ",\%v4:\!$nat_network_exclude";
            }
        }
        $genout .= "\"\n";
    }

    #
    # Disable uniqueids
    #
    if ($vcVPN->exists('ipsec disable-uniqreqids')) {
        $genout .= "\tuniqueids=no\n";
    }

    $genout .= "\n";
    $genout .= "conn %default\n";
    $genout .= "\tkeyexchange=ikev1\n\n";

    #
    # Connection configurations
    #
    my @peers = $vcVPN->listNodes('ipsec site-to-site peer');
    if (@peers == 0 && !($vcVPN->exists('l2tp')) && !($vcVPN->exists('ipsec profile'))) {
        print "VPN Warning: IPSec configured but no site-to-site peers or l2tp". " remote-users configured\n";
    }
    my $prev_peer = "";
    vtiIntf::discoverVtiIntfs();
    foreach my $peer (@peers) {
        my $peer_ike_group =$vcVPN->returnValue("ipsec site-to-site peer $peer ike-group");
        if (!defined($peer_ike_group) || $peer_ike_group eq '') {
            vpn_die(["vpn","ipsec","site-to-site","peer",$peer,"ike-group"],"$vpn_cfg_err No IKE group specified for peer \"$peer\".\n");
        } elsif (!$vcVPN->exists("ipsec ike-group $peer_ike_group")) {
            vpn_die(["vpn","ipsec","site-to-site","peer",$peer,"ike-group"],"$vpn_cfg_err The IKE group \"$peer_ike_group\" specified for peer ".
                     "\"$peer\" has not been configured.\n");
        }

        my $lip = $vcVPN->returnValue("ipsec site-to-site peer $peer local-address");

        my $peer_proto =  ((is_ip_v4_or_v6($peer) && (is_ip_v4_or_v6($peer) == 6))) ? 6 : 4;
        my $conn_proto = 4;

        # Check peer and local-address consistency
        if ($peer_proto == 6) {
            if (defined($lip)) {
                if (is_ip_v4_or_v6($lip) && (is_ip_v4_or_v6($lip) == 6)) {
                    $conn_proto = 6;
                } else {

                    # Protocols don't match
                    if (isIpAddress($lip)) {
                        vpn_die(["vpn", "ipsec", "site-to-site", "peer", $peer, "local-address"],"$vpn_cfg_err The 'local-address' and peer protocols do not match");

                        # %defaultroute for IPv6 IKEv1 is not supported by StrongS/WAN
                    } elsif($lip eq 'any') {
                        vpn_die(["vpn", "ipsec", "site-to-site", "peer", $peer, "local-address"],"$vpn_cfg_err 'any' local address is not supported for IPv6 peers");
                    }
                }
            } else {
                vpn_die(["vpn", "ipsec", "site-to-site", "peer", $peer],"$vpn_cfg_err 'local-address' is required for IPv6 peers");
            }
        }

        my $dhcp_iface = $vcVPN->returnValue("ipsec site-to-site peer $peer dhcp-interface");
        if (defined($lip) && defined($dhcp_iface)){
            vpn_die(["vpn","ipsec","site-to-site","peer",$peer],"$vpn_cfg_err Only one of local-address or dhcp-interface may be defined");
        }
        if (defined($dhcp_iface)){
            $dhcp_if = $dhcp_if + 1;
            $lip = get_dhcp_addr($dhcp_iface, $peer);
        }

        # Get the local ID
        my $authid = undef;
        my $authidfromcert = undef;
        if ($vcVPN->exists("ipsec site-to-site peer $peer authentication use-x509-id")) {
            if ($vcVPN->exists("ipsec site-to-site peer $peer authentication id")) {
                vpn_die(["vpn", "ipsec", "site-to-site", "peer", $peer], "Manually set peer id and use-x509-id are mutually exclusive");
            }
            else {
                $authidfromcert = 1;
            }
        }
        else {
            $authid =$vcVPN->returnValue("ipsec site-to-site peer $peer authentication id");
        }

        my $authremoteid = $vcVPN->returnValue("ipsec site-to-site peer $peer authentication remote-id");
        if ((!defined($lip) || $lip eq "") && (!defined($dhcp_iface) || $dhcp_iface eq "")) {
            vpn_die(["vpn","ipsec","site-to-site","peer",$peer,"local-address"],"$vpn_cfg_err No local-address specified for peer \"$peer\"\n");
        } elsif ($lip ne 'any') {

            # not 'any' special case.
            # check interface addresses.
            if (!Vyatta::Misc::isIPinInterfaces($vc, $lip, @interfaces)) {
                vpn_log("The local-address $lip of peer \"$peer\" has not been configured in any of the ipsec-interfaces.\n");
                if (Vyatta::Misc::isClusterIP($vc, $lip)) {

                    # Verified that dealing with a cluster IP.
                    $clustering_ip = 1;
                } elsif (!defined($dhcp_iface)) {
                    print "Warning: Local address $lip specified for peer \"$peer\"\n";
                    print "is not configured on any of the ipsec-interfaces and is not the\n";
                    print "clustering address.  IPsec must be re-started after address\n";
                    print "has been configured.\n";
                    print "\n";
                }
            }
        }

        #
        # Default ESP group
        #
        my $def_esp_group = $vcVPN->returnValue("ipsec site-to-site peer $peer default-esp-group");
        $def_esp_group = '' if !defined($def_esp_group);
        if (!$vcVPN->exists("ipsec esp-group $def_esp_group")) {
            vpn_die(["vpn","ipsec","site-to-site","peer",$peer,"default-esp-group"],"$vpn_cfg_err The ESP group \"$def_esp_group\" specified ".
                     "for peer \"$peer\" has not been configured.\n");
        }

        #
        # Name connection by peer and tunnel
        #
        my $isVti = 0;
        my @tunnels = $vcVPN->listNodes("ipsec site-to-site peer $peer tunnel");
        if (@tunnels == 0) {
            #
            # Check if this is VTI
            #
            if ($vcVPN->exists("ipsec site-to-site peer $peer vti")) {
                $isVti = 1;
                @tunnels = (@tunnels, "vti");
            }
            if (@tunnels == 0) {
                vpn_die(["vpn", "ipsec", "site-to-site","peer",$peer,"tunnel"],"$vpn_cfg_err No tunnels configured for peer \"$peer\".  At least".
                         " one tunnel required per peer.\n");
            }
        } else {
            #
            # Check if this is VTI
            #
            if ($vcVPN->exists("ipsec site-to-site peer $peer vti")) {
                vpn_die(["vpn", "ipsec", "site-to-site","peer",$peer,"vti"],"$vpn_cfg_err Both Vti and tunnel(s) configured for peer \"$peer\n");
            }
        }

        foreach my $tunnel (@tunnels) {

            my $needs_passthrough = 'false';
            my $tunKeyword;
            if ($isVti == 1) {
                $tunKeyword = 'vti';
            } else {
                $tunKeyword = 'tunnel '."$tunnel";
            }

            #
            # Add support for tunnel disable.
            #
            if ($vcVPN->exists("ipsec site-to-site peer $peer $tunKeyword disable")){
                next;
            }

            my $peer_tunnel_esp_group = $vcVPN->returnValue("ipsec site-to-site peer $peer $tunKeyword esp-group");
            $peer_tunnel_esp_group = '' if (!defined($peer_tunnel_esp_group));
            if (  (!defined($peer_tunnel_esp_group) || $peer_tunnel_esp_group eq '')
                &&(!defined($def_esp_group) || $def_esp_group eq ''))
            {
                vpn_die(["vpn","ipsec","site-to-site","peer",$peer,$tunKeyword,"esp-group"],"$vpn_cfg_err No ESP group specified for peer \"$peer\" ". "$tunKeyword.\n");
            } elsif (!$vcVPN->exists("ipsec esp-group $peer_tunnel_esp_group")) {
                vpn_die(["vpn","ipsec","site-to-site","peer",$peer,$tunKeyword,"esp-group"],"$vpn_cfg_err The ESP group \"$peer_tunnel_esp_group\" specified ".
                         "for peer \"$peer\" $tunKeyword has not been configured.\n");
            }

            my $conn_head = "\nconn peer-$peer-tunnel-$tunnel\n";
            $conn_head =~ s/ peer-@/ peer-/;
            $genout .= $conn_head;

            # Support for dhcp-interfaces
            # The comment dhcp-interface will be used by the dhclient hook to do connection updates.
            if (defined($dhcp_iface)){
                $genout .= "\t\#dhcp-interface=$dhcp_iface\n";
                $lip = get_dhcp_addr($dhcp_iface);
            }

            #
            # Assign left and right to local and remote interfaces
            #
            if (defined($lip)) {
                if ($lip eq 'any') {
                    if ($isVti) {
                        vpn_die(["vpn","ipsec","site-to-site","peer",$peer,$tunKeyword,"local-address"],"$vpn_cfg_err The local interface must be specified ".
                                 "for peer \"$peer\" $tunKeyword.\n");
                    }
                    $genout .= "\tleft=%defaultroute\n";
                } else {
                    $genout .= "\tleft=$lip\n";
                }
                if (defined($authidfromcert)) {
                    $genout .= "\tleftid=%fromcert\n";
                }
                else {
                    $genout .= "\tleftid=\"$authid\"\n" if defined $authid;
                }
            }

            # @SM Todo: must have explicit settings for VTI.
            my $any_peer = 0;
            my $right;
            my $rightid = undef;
            if ($peer =~ /^\@/) {

                # peer is an "ID"
                $rightid  = $peer;
                $any_peer = 1;
            }

            # If "remote-id" is set, use it even if the peer is an "ID".
            # This is mainly for the case when actual ID (e.g. certificate CN) can't be used
            # as peer node name.
            if ($authremoteid) {
                $rightid = $authremoteid;
            }

            if (   ($peer eq 'any')
                or ($peer eq '0.0.0.0')
                or $any_peer == 1)
            {
                if ($isVti) {
                    vpn_die(["vpn","ipsec","site-to-site","peer",$peer],"$vpn_cfg_err The peer \"$peer\" is invalid, an ip address must be specified for $tunKeyword.\n");
                }
                $right    = '%any';
                $any_peer = 1;
            } else {
                $right = $peer;
            }
            $genout .= "\tright=$right\n";
            $genout .= "\trightid=\"$rightid\"\n" if (defined($rightid));
            if ($any_peer) {
                $genout .= "\trekey=no\n";
            }

            #
            # Write tunnel configuration
            #
            my $leftsubnet = $vcVPN->returnValue("ipsec site-to-site peer $peer $tunKeyword local prefix");
            if ((defined($leftsubnet) && $leftsubnet eq 'any') || $isVti == 1) {
                $leftsubnet = '0.0.0.0/0';
            }

            my $remotesubnet = $vcVPN->returnValue("ipsec site-to-site peer $peer $tunKeyword remote prefix");
            if ($isVti) {
                $remotesubnet = 'any';
            }

            # Check local and remote prefix protocol consistency
            my $leftsubnet_proto = is_ip_v4_or_v6($leftsubnet);
            my $remotesubnet_proto = is_ip_v4_or_v6($remotesubnet);
            if (!$isVti && ($leftsubnet_proto != $remotesubnet_proto)) {
                vpn_die(["vpn", "ipsec", "site-to-site", "peer", $peer, "tunnel", $tunnel],"$vpn_cfg_err The 'remote prefix' and 'local prefix' protocols "."do not match");
            }

            my $rightsubnet;
            my $allow_nat_networks = $vcVPN->returnValue("ipsec site-to-site peer $peer $tunKeyword allow-nat-networks");
            my $allow_public_networks = $vcVPN->returnValue("ipsec site-to-site peer $peer $tunKeyword allow-public-networks");

            if (defined($allow_nat_networks) && $allow_nat_networks eq 'enable') {
                if (defined($remotesubnet) && $remotesubnet ne "") {
                    vpn_die(["vpn","ipsec","site-to-site","peer",$peer,"tunnel", $tunnel],"$vpn_cfg_err The 'remote-subnet' has been specified while ".
                             "'allow-nat-networks' has been enabled for peer \"$peer\" tunnel ". "$tunnel.  Both not allowed at once.\n");
                }

                my @allowed_network =$vcVPN->listNodes('ipsec nat-networks allowed-network');
                if (@allowed_network == 0) {
                    vpn_die(["vpn","ipsec","site-to-site","peer",$peer,"tunnel", $tunnel],"$vpn_cfg_err While 'allow-nat-networks' has been enabled for peer".
                             " \"$peer\" $tunKeyword, no global allowed NAT networks have". " been configured.\n");
                }

                $rightsubnet = "vhost:%priv";
                if (defined($allow_public_networks)
                    && $allow_public_networks eq "enable")
                {
                    if (defined($remotesubnet) && $remotesubnet ne "") {
                        vpn_die(["vpn","ipsec","site-to-site","peer",$peer,"tunnel", $tunnel],"$vpn_cfg_err The 'remote-subnet' has been specified while ".
                                 "'allow-public-networks' has been enabled for peer \"$peer\" ". "$tunKeyword.  Both not allowed at once.\n");
                    }
                    $rightsubnet .= ",%no";
                }
            } else {
                $rightsubnet = $remotesubnet;
                if (defined($rightsubnet) && $rightsubnet eq 'any') {
                    $rightsubnet = '0.0.0.0/0';
                }
            }
            if (defined($rightsubnet)) {
                my $protocol = $vcVPN->returnValue("ipsec site-to-site peer $peer $tunKeyword protocol");
                my $rport = $vcVPN->returnValue("ipsec site-to-site peer $peer $tunKeyword remote port");

                if ($protocol eq 'all') {
                  $protocol = '%any';
                }
                if (defined($rport)){
                  if (is_tcp_udp($protocol)){
                    if ($rport eq 'all') {
                      $rport = '%any';
                    }
                  }
                  else {
                    vpn_die(["vpn","ipsec","site-to-site","peer",$peer, "tunnel", $tunnel, "remote", "port"],
                            "$vpn_cfg_err remote port can only be defined when protocol is tcp, udp, or undefined.\n");
                  }
                }
                else {
                  $rport = '%any';
                }
                if (defined($protocol)) {
                  $genout .= "\trightsubnet=$rightsubnet\[$protocol/$rport\]\n";
                }
                else {
                  $genout .= "\trightsubnet=$rightsubnet\n";
                }
            }

            if (defined($leftsubnet)) {
                my $protocol = $vcVPN->returnValue("ipsec site-to-site peer $peer $tunKeyword protocol");
                my $lport = $vcVPN->returnValue("ipsec site-to-site peer $peer $tunKeyword local port");

                if ($protocol eq 'all') {
                  $protocol = '%any';
                }

                if (defined($lport)){
                  if (is_tcp_udp($protocol)){
                    if ($lport eq 'all') {
                      $lport = '%any';
                    }

                  }
                  else {
                    vpn_die(["vpn","ipsec","site-to-site","peer",$peer, "tunnel", $tunnel, "remote", "port"],
                            "$vpn_cfg_err remote port can only be defined when protocol is tcp, udp, or undefined.\n");
                  }

                }
                else {
                  $lport = '%any';
                }

                if (defined($protocol)) {
                  $genout .= "\tleftsubnet=$leftsubnet\[$protocol/$lport\]\n";
                }
                else {
                  $genout .= "\tleftsubnet=$leftsubnet\n";
                }
            }

            #
            # check if passthrough connection is needed
            # needed when remote-subnet encompasses local-subnet
            #
            if (!$isVti && defined $leftsubnet && defined $rightsubnet) {

                # validate that these values are ipv4net
                my $valid_leftsubnet = 'false';
                my $valid_rightsubnet = 'false';

                $valid_leftsubnet = 'true' if validateType('ipv4net', $leftsubnet, 'quiet');
                $valid_rightsubnet = 'true' if validateType('ipv4net', $rightsubnet, 'quiet');

                if ($valid_leftsubnet eq 'true' && $valid_rightsubnet eq 'true') {

                    my $localsubnet_object = new NetAddr::IP($leftsubnet);
                    my $remotesubnet_object = new NetAddr::IP($rightsubnet);
                    if ($remotesubnet_object == $localsubnet_object) {
                        vpn_die(["vpn","ipsec","site-to-site","peer",$peer],"$vpn_cfg_err local prefix and remote prefix cannot be the same.\n");
                    }
                    my $check_local_route = qx(ip route show table 254 $localsubnet_object && ip route show table local $localsubnet_object);
                    if (!$check_local_route){
                        print "Warning: local prefix $localsubnet_object specified for peer \"$peer\"\n";
                        print "is not configured on any interfaces\n";
                    }
                    if ($remotesubnet_object->contains($localsubnet_object)) {
                        $needs_passthrough = 'true';
                    }
                }
            }

            #
            # Write IKE configuration from group
            #
            my $ikelifetime = IKELIFETIME_DEFAULT;
            $genout .= "\tike=";
            my $ike_group =$vcVPN->returnValue("ipsec site-to-site peer $peer ike-group");
            if (defined($ike_group) && $ike_group ne '') {
                my @ike_proposals =$vcVPN->listNodes("ipsec ike-group $ike_group proposal");

                my $first_ike_proposal = 1;
                foreach my $ike_proposal (@ike_proposals) {

                    #
                    # Get encryption, hash & Diffie-Hellman  key size
                    #
                    my $encryption = $vcVPN->returnValue("ipsec ike-group $ike_group proposal $ike_proposal encryption");
                    my $hash = $vcVPN->returnValue("ipsec ike-group $ike_group proposal $ike_proposal hash");
                    my $dh_group = $vcVPN->returnValue("ipsec ike-group $ike_group proposal $ike_proposal dh-group");

                    #
                    # Write separator if not first proposal
                    #
                    if ($first_ike_proposal) {
                        $first_ike_proposal = 0;
                    } else {
                        $genout .= ",";
                    }

                    #
                    # Write values
                    #
                    if (defined($encryption) && defined($hash)) {
                        $genout .= "$encryption-$hash";
                        if (defined($dh_group)) {
                            my $cipher_out = get_dh_cipher_result($dh_group);
                            if ($cipher_out eq 'unknown') {
                                vpn_die(["vpn","ipsec","site-to-site","peer",$peer,"tunnel", $tunnel],"$vpn_cfg_err Invalid 'dh-group' $dh_group specified for ".
                                         "peer \"$peer\" $tunKeyword.  Only 2, 5, or 14 through 26 accepted.\n");
                            } else {
                                $genout .= "-$cipher_out";
                            }
                        }
                    }
                }
                $genout .= "!\n";

                #
                # Get IKE version setting
                #
                my $key_exchange = $vcVPN->returnValue("ipsec ike-group $ike_group key-exchange");
                if (defined($key_exchange)) {
                    if ($key_exchange eq 'ikev1') {
                        $genout .= "\tkeyexchange=ikev1\n";
                    }
                    if ($key_exchange eq 'ikev2') {
                        $genout .= "\tkeyexchange=ikev2\n";
                    }
                }else {
                    $genout .= "\tkeyexchange=ikev1\n";
                }

                #
                # Get ikev2-reauth configuration
                #
                if ((defined($key_exchange)) && ($key_exchange eq 'ikev2')) {
                    my $ikev2_tunnel_reauth = $vcVPN->returnValue("ipsec site-to-site peer $peer ikev2-reauth");

                    if ((defined($ikev2_tunnel_reauth)) && ($ikev2_tunnel_reauth ne 'inherit')) {
                        $genout .= "\treauth=$ikev2_tunnel_reauth\n";
                    } else {
                        my $ikev2_group_reauth = $vcVPN->returnValue("ipsec ike-group $ike_group ikev2-reauth");
                        if (defined($ikev2_group_reauth)) {
                            $genout .= "\treauth=$ikev2_group_reauth\n";
                        } else {
                            $genout .= "\treauth=no\n";
                        }
                    }

                }

                #
                # Allow the user to disable MOBIKE for IKEv2 connections
                #
                my $mob_ike = $vcVPN->returnValue("ipsec ike-group $ike_group mobike");

                if (defined($mob_ike)) {
                    if (defined($key_exchange) && $key_exchange eq 'ikev2') {
                        if ($mob_ike eq 'enable') {
                            $genout .= "\tmobike=yes\n";
                        }
                        if ($mob_ike eq 'disable') {
                            $genout .= "\tmobike=no\n";
                        }
                    }else {
                        vpn_die(["vpn","ipsec","ike-group", $ike_group, "mobike"],"$vpn_cfg_err MOBIKE is only valid for IKEv2 configurations.\n");
                    }
                }

                #
                # Allow the user to specify aggressive mode for IKEv1 connections
                #
                my $aggressive_mode = $vcVPN->returnValue("ipsec ike-group $ike_group mode");

                if (defined($aggressive_mode)) {
                  if (defined($key_exchange) && $key_exchange eq 'ikev2') {
                    vpn_die(["vpn","ipsec","ike-group", $ike_group, "mode"], "$vpn_cfg_err Selection of Main/Aggressive modes is only valid for IKEv1 configurations");
                  }
                  if ($aggressive_mode eq 'aggressive') {
                    $genout .= "\taggressive=yes\n";
                  } else {
                    $genout .= "\taggressive=no\n";
                  }
                }
                my $t_ikelifetime =$vcVPN->returnValue("ipsec ike-group $ike_group lifetime");
                if (defined($t_ikelifetime) && $t_ikelifetime ne '') {
                    $ikelifetime = $t_ikelifetime;
                }
                $genout .= "\tikelifetime=$ikelifetime" . "s\n";

                #
                # Check for Dead Peer Detection DPD
                #
                my $dpd_interval = $vcVPN->returnValue("ipsec ike-group $ike_group dead-peer-detection interval");
                my $dpd_timeout = $vcVPN->returnValue("ipsec ike-group $ike_group dead-peer-detection timeout");
                my $dpd_action = $vcVPN->returnValue("ipsec ike-group $ike_group dead-peer-detection action");
                if (   defined($dpd_interval)
                    && defined($dpd_timeout)
                    && defined($dpd_action))
                {
                    $genout .= "\tdpddelay=$dpd_interval" . "s\n";
                    $genout .= "\tdpdtimeout=$dpd_timeout" . "s\n";
                    $genout .= "\tdpdaction=$dpd_action\n";
                }

                #
                # Check for closeaction
                #
                my $close_act = $vcVPN->returnValue("ipsec ike-group $ike_group close-action");
                if (defined($close_act)) {
                    $genout .= "\tcloseaction=$close_act\n";
                }

                #
                # Allow the user for force UDP encapsulation for the ESP
                # payload.
                #
                my $forceencaps = $vcVPN->returnValue("ipsec site-to-site peer $peer force-encapsulation");
                if (defined($forceencaps)) {
                    if ($forceencaps eq 'enable') {
                        $genout .= "\tforceencaps=yes\n";
                    } else {
                        $genout .= "\tforceencaps=no\n";
                    }
                }
            }

            #
            # Write ESP configuration from group
            #
            my $esplifetime = ESPLIFETIME_DEFAULT;
            $genout .= "\tesp=";
            my $esp_group = $vcVPN->returnValue("ipsec site-to-site peer $peer $tunKeyword esp-group");
            if (!defined($esp_group) || $esp_group eq ''){
                $esp_group = $vcVPN->returnValue("ipsec site-to-site peer $peer default-esp-group");
            }
            if (defined($esp_group) && $esp_group ne '') {
                my @esp_proposals =$vcVPN->listNodes("ipsec esp-group $esp_group proposal");
                my $first_esp_proposal = 1;
                foreach my $esp_proposal (@esp_proposals) {

                    #
                    # Get encryption, hash and PFS group settings
                    #
                    my $encryption = $vcVPN->returnValue("ipsec esp-group $esp_group proposal $esp_proposal encryption");
                    my $hash = $vcVPN->returnValue("ipsec esp-group $esp_group proposal $esp_proposal hash");
                    my $pfs = $vcVPN->returnValue("ipsec esp-group $esp_group pfs");

                    #
                    # Write separator if not first proposal
                    #
                    if ($first_esp_proposal) {
                        $first_esp_proposal = 0;
                    } else {
                        $genout .= ",";
                    }
                    if (defined($pfs)) {
                        if ($pfs eq 'enable') {
                            # Get list of IKE proposals
                            my @ike_proposals = $vcVPN->listNodes("ipsec ike-group $ike_group proposal");
                            # Get the first IKE group's dh-group and use that as our PFS setting
                            my $default_pfs = $vcVPN->returnValue("ipsec ike-group $ike_group proposal $ike_proposals[0] dh-group");
                            $pfs = get_dh_cipher_result($default_pfs);
                        } elsif ($pfs eq 'disable') {
                            undef $pfs;
                        } else {
                            $pfs = get_dh_cipher_result($pfs);
                        }
                    }

                    #
                    # Write values
                    #
                    if (defined($encryption) && defined($hash)) {
                        $genout .= "$encryption-$hash";
                        if (defined($pfs)) {
                            $genout .= "-$pfs";
                        }
                    }
                }
                $genout .= "!\n";

                my $t_esplifetime =$vcVPN->returnValue("ipsec esp-group $esp_group lifetime");
                if (defined($t_esplifetime) && $t_esplifetime ne '') {
                    $esplifetime = $t_esplifetime;
                }
                $genout .= "\tkeylife=$esplifetime" . "s\n";

                my $lower_lifetime = $ikelifetime;
                if ($esplifetime < $ikelifetime) {
                    $lower_lifetime = $esplifetime;
                }

                #
                # The lifetime values need to be greater than:
                #   rekeymargin*(100+rekeyfuzz)/100
                #
                my $rekeymargin = REKEYMARGIN_DEFAULT;
                if ($lower_lifetime <= (2 * $rekeymargin)) {
                    $rekeymargin = int($lower_lifetime / 2) - 1;
                }
                $genout .= "\trekeymargin=$rekeymargin" . "s\n";

                #
                # Mode (tunnel or transport)
                #
                my $espmode = $vcVPN->returnValue("ipsec esp-group $esp_group mode");
                if (!defined($espmode) || $espmode eq '') {
                    $espmode = "tunnel";
                }
                if ($espmode eq "transport") {
                    if (defined $leftsubnet or defined $rightsubnet) {
                        vpn_die(["vpn","ipsec","site-to-site","peer",$peer,"$tunKeyword"],"$vpn_cfg_err Can not use local-subnet or remote-subnet when ".
                                 "using transport mode\n");
                    }
                    if ($isVti == 1) {
                        vpn_die(["vpn","ipsec","site-to-site","peer",$peer,"$tunKeyword"],"$vpn_cfg_err Can not use transport mode for \"$peer\" with vti\n");
                    }
                    # Processing protocol selector for a tunnel
                    my $protocol = $vcVPN->returnValue("ipsec site-to-site peer $peer $tunKeyword protocol");
                    if (defined($protocol)) {
                        # Replace 'all' with the proper variant for strongSwan
                        if ($protocol eq 'all') {
                            $protocol = '%any';
                        }
                        # Transport mode with protocol selector can be used only together with left|rightsubnet
                        # Thus, we need to be sure that it is possible to generate their values from left/right
                        my $left_ip = new NetAddr::IP $vcVPN->returnValue("ipsec site-to-site peer $peer local-address");
                        if ($left_ip->addr eq '0.0.0.0') {
                            vpn_die(["vpn","ipsec","site-to-site","peer",$peer,"$tunKeyword"],"$vpn_cfg_err It is not possible to use transport mode ESP ".
                            "group and protocol selector without predefined static \"local-address\"\n");
                        }
                        my $right_ip = new NetAddr::IP $peer;
                        if ($right_ip->addr eq '0.0.0.0') {
                            vpn_die(["vpn","ipsec","site-to-site","peer",$peer,"$tunKeyword"],"$vpn_cfg_err It is not possible to use transport mode ESP ".
                            "group and protocol selector together with a peer without predefined IP address\n");
                        }
                        # Generate a config for using with the protocol selector
                        $genout .= "\tleftsubnet=$left_ip\[$protocol\]\n";
                        $genout .= "\trightsubnet=$right_ip\[$protocol\]\n";
                    }
                }
                $genout .= "\ttype=$espmode\n";

                #
                # Compression
                #
                my $compression =$vcVPN->returnValue("ipsec esp-group $esp_group compression");
                if (defined($compression)) {
                    if ($compression eq 'enable') {
                        $genout .= "\tcompress=yes\n";
                    } else {
                        $genout .= "\tcompress=no\n";
                    }
                }
            }

            #
            # Authentication mode
            #
            #
            # Write shared secrets to ipsec.secrets
            #
            my $auth_mode = $vcVPN->returnValue("ipsec site-to-site peer $peer authentication mode");
            if (!defined($auth_mode) || $auth_mode eq '') {
                vpn_die(["vpn","ipsec","site-to-site","peer",$peer,"authentication"],"$vpn_cfg_err No authentication mode for peer \"$peer\" specified.\n");
            } elsif (defined($auth_mode) && ($auth_mode eq 'pre-shared-secret')) {
                my $psk = $vcVPN->returnValue("ipsec site-to-site peer $peer authentication pre-shared-secret");
                my $orig_psk = $vcVPN->returnOrigValue("ipsec site-to-site peer $peer authentication pre-shared-secret");
                $orig_psk = "" if (!defined($orig_psk));
                if ($psk ne $orig_psk && $orig_psk ne ""){
                    print "WARNING: The pre-shared-secret will not be updated until the next re-keying interval\n";
                    print "To force the key change use: 'reset vpn ipsec-peer'\n";
                }
                if (!defined($psk) || $psk eq '') {
                    vpn_die(["vpn","ipsec","site-to-site","peer",$peer,"authentication"],"$vpn_cfg_err No 'pre-shared-secret' specified for peer \"$peer\"".
                             " while 'pre-shared-secret' authentication mode is specified.\n");
                }

                my $right;
                if (   ($peer eq 'any')
                    or ($peer eq '0.0.0.0')
                    or ($peer =~ /^\@/))
                {
                    $right = '%any';
                } else {
                    $right = $peer;
                }
                my $index1 = (defined($authid))       ? "$authid"       : $lip;
                my $index2 = (defined($authremoteid)) ? "$authremoteid" : $right;
                if ($lip eq '0.0.0.0'&&!defined($dhcp_iface)) {
                    if ($index1 =~ m/^@/) {
                        # In main mode PSK, the responder needs to look up the secret
                        # before the Peer's ID payload has been decoded, so the ID used
                        # will be the IP address. Note that this'll work with aggressive
                        # mode PSK but starting VC6, we use strongswan which doesn't
                        # support aggressive mode. More info on reported bug :
                        # http://bugzilla.vyatta.com/show_bug.cgi?id=5500
                        vpn_die(["vpn","ipsec","site-to-site","peer","0.0.0.0","authentication"],"$vpn_cfg_err cannot use authentication id with pre-shared-secret".
                                 " when local-address is 0.0.0.0\n");
                    }
                    # when local-address is dynamic then only the following generic form works
                    $genout_secrets .= ": PSK \"$psk\"\n";
                } else {
                    if (not($prev_peer eq $peer)){
                        if (defined($dhcp_iface) && $lip eq ''){
                            $genout_secrets .= "# $right ";
                        } else {
                            $genout_secrets .= "$lip $right ";
                        }
                        if (defined($authid)){
                            $genout_secrets .= "$authid ";
                        }
                        if (defined($authremoteid)) {
                            $genout_secrets .= "$authremoteid ";
                        }

                        # tag the secrets lines with 3 entries so the op mode command can
                        # deal with them properly. (LEFT means localid, RIGHT means remoteid)
                        if ((!defined($authid)) && (defined($authremoteid))) {
                            $genout_secrets .= ": PSK \"$psk\" #RIGHT# ";
                        } elsif ((defined($authid)) && (!defined($authremoteid))) {
                            $genout_secrets .= ": PSK \"$psk\" #LEFT# ";
                        } else {
                            $genout_secrets .= ": PSK \"$psk\" ";
                        }
                        if (defined($dhcp_iface)){
                            $genout_secrets .= "#dhcp-interface=$dhcp_iface#\n";
                        } else {
                            $genout_secrets .= "\n";
                        }
                    }
                    $prev_peer = $peer;
                }
                $genout         .= "\tauthby=secret\n";
            } elsif (defined($auth_mode) && $auth_mode eq 'x509') {
                $genout .= get_x509($peer);
                $genout_secrets .= get_x509_secret($peer);
            } elsif (defined($auth_mode) && $auth_mode eq 'rsa') {

                unless (-r $local_key_file) {
                    if (-e $local_key_file) {
                        vpn_die(["vpn","ipsec","site-to-site","peer",$peer,"authentication"],"$vpn_cfg_err Invalid local RSA key file path ".
                                 "\"$local_key_file\".  Filesystem read permission absent.\n");
                    } else {
                        vpn_die(["vpn","ipsec","site-to-site","peer",$peer,"authentication"],"$vpn_cfg_err Invalid local RSA key file path \"$local_key_file\".".
                                 " File absent.  Use the 'vpn rsa-key generate' command to create.\n");
                    }
                }

                $genout .= "\tauthby=rsasig\n";
                my $local_key = rsa_get_local_pubkey($local_key_file);
                if (!defined($local_key) || $local_key eq "") {
                    vpn_die(["vpn","ipsec","site-to-site","peer",$peer,"authentication"],"$vpn_cfg_err Unable to determine local public key from local key".
                             " file \"$local_key_file\" for peer \"$peer\".\n");
                } else {
                    if (!defined($public_keys{localhost})) {
                        $public_keys{localhost} = $local_key;
                        $genout .= "\tleftsigkey=localhost.pub\n";
                    }
                }

                my $rsa_key_name = $vcVPN->returnValue("ipsec site-to-site peer $peer authentication rsa-key-name");
                if (!defined($rsa_key_name) || $rsa_key_name eq "") {
                    vpn_die(["vpn","ipsec","site-to-site","peer",$peer,"authentication","rsa-key-name"],"$vpn_cfg_err No 'rsa-key-name' specified for peer \"$peer\"".
                             " while 'rsa' authentication mode is specified.\n");
                } else {
                    my $remote_key =$vcVPN->returnValue("rsa-keys rsa-key-name $rsa_key_name rsa-key");
                    if (!defined($remote_key) || $remote_key eq "") {
                        vpn_die(["vpn","ipsec","site-to-site","peer",$peer,"authentication"],"$vpn_cfg_err No remote key configured for rsa key name ".
                                 "\"$rsa_key_name\" that is specified for peer \"$peer\".\n");
                    } else {
                        if (!defined($public_keys{$rsa_key_name})) {
                            $public_keys{$rsa_key_name} = $remote_key;
                            $genout .= "\trightsigkey=$rsa_key_name.pub\n";
                        }
                    }
                }
                # Prevent duplicate includes for rsa keys.
                if (!defined($key_file_list{$local_key_file})) {
                    $key_file_list{$local_key_file} = 1;
                    $genout_secrets .= ": RSA $local_key_file\n";
                }
            } else {
                vpn_die(["vpn","ipsec","site-to-site","peer",$peer,"authentication"],"$vpn_cfg_err Unknown authentication mode \"$auth_mode\" for peer ".
                         "\"$peer\" specified.\n");
            }

            #
            # Mark setting for vti.
            # and up/down script hook.
            #
            if ($isVti) {
                my $mark = vtiIntf::isVtimarkpresent($peer, $lip);
                if (!defined($mark) || $mark eq '') {
                    vpn_die(["vpn","ipsec","site-to-site","peer",$peer,"vti"],"$vpn_cfg_err No mark specified for peer \"$peer\" vti\n");
                } else {
                    $genout .= "\tmark=$mark\n";
                }

                # up/down script hook.
                my $tunName = $vcVPN->returnValue("ipsec site-to-site peer $peer vti bind");
                if (!defined($tunName)) {
                    vpn_die(["vpn","ipsec","site-to-site","peer",$peer,"vti","bind"],"$vpn_cfg_err No interface bind specified for peer \"$peer\" vti\n");
                }
                $genout .= "\tleftupdown=\"/usr/lib/ipsec/vti-up-down $tunName\"\n";
                if (defined($dhcp_iface)){
                    $dhcp_if = $dhcp_if + 1;
                }
            }

            #
            # Start automatically
            #
            if ($any_peer) {
                $genout .= "\tauto=add\n";
                $genout .= "\tkeyingtries=%forever\n";
            } else {
                my $conntype = $vcVPN->returnValue("ipsec site-to-site peer $peer connection-type");
                if (defined($conntype)){
                    if ($conntype eq "initiate"){
                        $genout .= "\tauto=start\n";
                        $genout .= "\tkeyingtries=%forever\n";
                    } elsif ($conntype eq "respond"){
                        $genout .= "\tauto=route\n";
                        ##  We want to act as a responder. Ideally we do not want to ever
                        ##  be a initiator, but we can't avoid it if SPD entries are installed
                        ##  to protect selected traffic from going out unencrypted. The best we
                        ##  can do is to not try to attempt keying forever.
                        $genout .= "\tkeyingtries=1\n";
                    }
                }else{
                    $genout .= "\tauto=start\n";
                }
            }
            $conn_head =~ s/\n//;
            $genout .= "#$conn_head";    # to identify end of connection definition
            # used by clear vpn op-mode command

            if ($needs_passthrough eq 'true') {

                # CREATE A PASSTHROUGH CONNECTION
                my $passthrough_conn_head = "\nconn passthrough-peer-$peer-tunnel-$tunnel\n";
                $passthrough_conn_head =~ s/ peer-@/ peer-/;
                $genout .= $passthrough_conn_head;
                if ($lip eq '0.0.0.0') {
                    $genout .= "\tleft=%defaultroute\n";
                } else {
                    $genout .= "\tleft=$lip\n";
                }
                $genout .= "\tright=$right\n";
                $genout .= "\tleftsubnet=$leftsubnet\n";
                $genout .= "\trightsubnet=$leftsubnet\n";
                $genout .= "\ttype=passthrough\n";
                $genout .= "\tauthby=never\n";
                $genout .= "\tauto=route\n";
                $passthrough_conn_head =~ s/\n//;
                $genout .= "#$passthrough_conn_head";

            }
        }
    }

    #
    # Include a custom configuration file
    #
    my $custom_include = $vcVPN->returnValue("ipsec include-ipsec-conf");
	my $custom_secrets = $vcVPN->returnValue("ipsec include-ipsec-secrets");
    if (defined($custom_include)) {
        if ( ! -e $custom_include ) {
            vpn_die(["vpn","ipsec","include-ipsec-conf"],"$vpn_cfg_err The specified file for inclusion inside ipsec.conf does not exist.");
        }
		$genout .= "\ninclude $custom_include\n";
    }
    if (defined($custom_secrets)) {
        if ( ! -e $custom_secrets) {
            vpn_die(["vpn","ipsec","include-ipsec-secrets"],"$vpn_cfg_err The specified file for inclusion inside ipsec.secrets does not exist.");
        }
        $genout_secrets .= "\ninclude $custom_secrets\n";
    }

} else {

    #
    # remove any previous config lines, so that when "clear vpn ipsec-process"
    # is called it won't find the vyatta keyword and therefore will not try
    # to start the ipsec process.
    #
    $genout = '';
    $genout         .= "# No VPN configuration exists.\n";
    $genout_secrets .= "# No VPN configuration exists.\n";
}

if (!(defined($config_file)&& ($config_file ne '')&& defined($secrets_file)&& ($secrets_file ne ''))){
    print "Regular config file output would be:\n\n$genout\n\n";
    print "Secrets config file output would be:\n\n$genout_secrets\n\n";
    exit(0);
}

if (   $vcVPN->isDeleted('.')
    || !$vcVPN->exists('.')
    || $vcVPN->isDeleted('ipsec')
    || !$vcVPN->exists('ipsec'))
{
    if (Vyatta::Misc::isClusterIP($vc, 'ipsec')) {
        vpn_die(["vpn","ipsec"],"VPN commit error.  Cluster service is referencing ipsec config.\n");
    }
    if (is_vpn_running()) {
        vpn_exec('ipsec stop >&/dev/null', 'stop ipsec');
    }
    if (!enableICMP('1')) {
        vpn_die(["vpn","ipsec"],"VPN commit error.  Unable to re-enable ICMP redirects.\n");
    }
    write_config($genout, $interfaces_use, $STRONGSWAN_INTF_CONFIG, $config_file, $genout_secrets, $secrets_file, $dhcp_if, %public_keys);
} else {
    if (!enableICMP('0')) {
        vpn_die(["vpn","ipsec"],"VPN commit error.  Unable to disable ICMP redirects.\n");
    }

    write_config($genout, $interfaces_use, $STRONGSWAN_INTF_CONFIG, $config_file, $genout_secrets, $secrets_file, $dhcp_if, %public_keys);

    # Assumming that if there was a local IP missmatch and clustering is enabled,
    # then the clustering scripts will take care of starting the VPN daemon.
    if ($clustering_ip) {

        # If the local-address is provided by clustering, then just write out the configuration,
        # but do not start the VPN daemon

        vpn_log("Wrote out configuration to files '$config_file' and '$secrets_file'.  VPN/ipsec daemons not started due to clustering.\n");
        print "Clustering configured - not restarting ipsec\n";
    } else {
        my $update_interval = $vcVPN->returnValue("ipsec auto-update");
        my $update_interval_orig = $vcVPN->returnOrigValue("ipsec auto-update");
        $update_interval_orig = 0 if !defined($update_interval_orig);
        if (is_vpn_running()) {
            if (defined($update_interval) && ($update_interval != $update_interval_orig)){
                print "Re-starting IPsec daemon to activate new auto-update interval...\n";
                vpn_exec('ipsec restart --auto-update '.$update_interval.' >&/dev/null','re-starting ipsec with updated auto-update interval $update_interval');
            } elsif (!defined($update_interval) && ($update_interval_orig != 0)){
                print "Re-starting IPsec daemon to deactivate auto-update...\n";
                vpn_exec('ipsec restart >&/dev/null', 're-starting ipsec');
            }else {

                # Grab the list of old peers and tunnels
                my @tunnel_cfg_new = $vcVPN->listNodes('ipsec site-to-site peer');
                my @tunnel_cfg_old = $vcVPN->listOrigNodes('ipsec site-to-site peer');
                my @old_tunnels;
                my %seen;
                @seen{@tunnel_cfg_new} = ();

                # Find the old tunnels in previous configuration
                foreach my $tunnel (@tunnel_cfg_old) {
                    push (@old_tunnels, $tunnel) unless exists $seen{$tunnel};
                }

                # Issue an ipsec down on the old tunnel since charon doesn't clean up
                # connections removed from ipsec.conf
                foreach my $old_peer (@old_tunnels) {
                    my @tunnels = $vcVPN->listOrigNodes("ipsec site-to-site peer $old_peer tunnel");
                    foreach my $tunnel (@tunnels)
                    {
                        vpn_exec("ipsec down peer-$old_peer-tunnel-$tunnel", "Cleaning up site-to-site peer $old_peer at tunnel $tunnel");
                    }
                }
                # Check if returnValues equals returnOrigValues for ipsec-interfaces and restart if they are not equal
                my $equals = 1;
                my @working_interfaces = $vcVPN->returnValues("ipsec ipsec-interfaces interface");
                my @active_interfaces = $vcVPN->returnOrigValues("ipsec ipsec-interfaces interface");
                if (scalar(@working_interfaces) != scalar(@active_interfaces)){
                    $equals = 0;
                }
                else {
                    my @sorted_working = sort @working_interfaces;
                    my @sorted_active = sort @active_interfaces;
                    foreach (my $i = 0; $i < @sorted_working; $i++) {
                        if ($sorted_working[$i] ne $sorted_active[$i]) {
                            $equals = 0;
                            last;
                        }
                    }
                }
                if ($equals == 0) {
                    vpn_exec('ipsec restart >&/dev/null', 're-starting ipsec');
                }else {
                    vpn_exec('ipsec rereadall >&/dev/null', 're-read secrets and certs');
                    vpn_exec('ipsec reload >&/dev/null', 'reload changes to ipsec.conf');
                }
            }
        } else {
            if (!defined($update_interval)) {
                vpn_exec('ipsec start >&/dev/null', 'start ipsec');
            } else {
                vpn_exec('ipsec start --auto-update '.$update_interval.' >&/dev/null','start ipsec with auto-update $update_interval');
            }
        }

        # Activate any debugging options by
        # calling ipsec stroke loglevel <source> <level>
        my @logmodes = $vcVPN->returnValues('ipsec logging log-modes');
        my @oldmodes = $vcVPN->returnOrigValues('ipsec logging log-modes');
        my $charonloglevel = $vcVPN->returnValue('ipsec logging log-level');
        # Clean up any logging modes if present
        if (@oldmodes > 0) {
            foreach my $mode (@oldmodes) {
                vpn_exec("ipsec stroke loglevel $mode 0", "Deactivating log source $mode");
            }
        }
        # Finally activate our new logger configuration
        if (@logmodes > 0) {
            foreach my $mode (@logmodes) {
                vpn_exec("ipsec stroke loglevel $mode $charonloglevel", "Stroking log source $mode to loglevel $charonloglevel");
            }
        }
    }
}

#
# Return success
#
exit 0;

sub vpn_die {
    my (@path,$msg) = @_;
    Vyatta::Config::outputError(@path, $msg);
    exit 1;
}

sub write_config {
    my ($genout, $interfaces_use, $STRONGSWAN_INTF_CONFIG, $config_file, $genout_secrets, $secrets_file, $dhcp_if, %public_keys) = @_;

    open my $output_config, '>', $config_file
        or die "Can't open $config_file: $!";
    print ${output_config} $genout;
    close $output_config;


    open my $strong_config, '>', $STRONGSWAN_INTF_CONFIG
        or die "Can't open $STRONGSWAN_INTF_CONFIG: $!";
    print ${strong_config} $interfaces_use;
    close $strong_config;

    my @lines = split("\n", $genout_secrets);
    my @any = grep(/%any/, @lines);
    if (scalar(@any) > 0) {
        my @noany = grep(!/%any/, @lines);
        @lines = (@noany, @any);
        $genout_secrets = join("\n", @lines);
        $genout_secrets .= "\n";
    }

    open my $output_secrets, '>', $secrets_file
        or die "Can't open $secrets_file: $!";
    print ${output_secrets} $genout_secrets;
    close $output_secrets;
    dhcp_hook($dhcp_if);

    for my $name (keys %public_keys) {
        my $output_path = "/etc/ipsec.d/certs/$name.pub";
        open my $output_file, '>', $output_path
            or die "Can't open $output_path: $!";
        print ${output_file} rsa_convert_pubkey_pem($public_keys{$name});
        close $output_file;
    }
}

sub vpn_exec {
    my ($command, $desc) = @_;

    open my $logf, '>>', $LOGFILE
        or die "Can't open $LOGFILE: $!";

    use POSIX;
    my $timestamp = strftime("%Y-%m-%d %H:%M.%S", localtime);

    print ${logf} "$timestamp\nExecuting: $command\nDescription: $desc\n";

    my $cmd_out = qx($command);
    my $rval    = ($? >> 8);
    print ${logf} "Output:\n$cmd_out\n---\n";
    print ${logf} "Return code: $rval\n";
    if ($rval) {
        if ($command =~ /^ipsec.*--asynchronous$/
            && ($rval == 104 || $rval == 29))
        {
            print ${logf} "OK when bringing up VPN connection\n";
        } else {
            #
            # We use to consider the commit failed if we got a error
            # from the call to ipsec, but this causes the configuration
            # to not get included in the running config.  Now that
            # we support dynamic interface/address (e.g. dhcp, pppoe)
            # we want a valid config to get committed even if the
            # interface doesn't exist yet.  That way we can use
            # "clear vpn ipsec-process" to bring up the tunnel once
            # the interface is instantiated.  For pppoe we will add
            # a script to /etc/ppp/ip-up.d to bring up the vpn
            # tunnel.
            #
            print ${logf} "VPN commit error.  Unable to $desc, received error code $?\n";
            #
            # code 768 is for a syntax error in the secrets file
            # this happens when a dhcp interface is configured
            # but no address is assigned yet.
            # only the line that has the syntax error is not loaded
            # So we can safely ignore this error since our code generates
            # secrets file.
            #
            if ($? ne '768'){
                print "Warning: unable to [$desc], received error code $?\n";
                print "$cmd_out\n";
            }
        }
    }
    print ${logf} "---\n\n";
    close $logf;
}

sub vpn_log {
    my ($log) = @_;

    open my $logfile, '>>', $LOGFILE
        or die "Can't open $LOGFILE: $!";

    use POSIX;
    my $timestamp = strftime("%Y-%m-%d %H:%M.%S", localtime);

    print ${logfile} "$timestamp\n$log\n";
    print ${logfile} "---\n\n";
    close $logfile;
}

sub printTree {
    my ($vc, $path, $depth) = @_;

    my @children = $vc->listNodes($path);
    foreach my $child (@children) {
        print '    ' x $depth;
        print $child . "\n";
        printTree($vc, "$path $child", $depth + 1);
    }
}

sub printTreeOrig {
    my ($vc, $path, $depth) = @_;

    my @children = $vc->listOrigNodes($path);
    foreach my $child (@children) {
        print '    ' x $depth;
        print $child . "\n";
        printTreeOrig($vc, "$path $child", $depth + 1);
    }
}

sub hasLocalWildcard {
    my $vc    = shift;
    my $orig  = shift;
    my @peers = $vc->listNodes('ipsec site-to-site peer');
    if ($orig) {
        @peers = $vc->listOrigNodes('ipsec site-to-site peer');
    }
    return 0 if (@peers == 0);
    foreach my $peer (@peers) {
        my $lip = $vcVPN->returnValue("ipsec site-to-site peer $peer local-address");
        if ($orig) {
            $lip = $vcVPN->returnOrigValue("ipsec site-to-site peer $peer local-address");
        }
        return 1 if ($lip && $lip eq '0.0.0.0');
    }
    return 0;
}

sub get_x509 {
    my $peer = pop(@_);

    # Setup x509, based on the L2TP x509 code
    #
    ## check that proper nodes are defined.
    my $path = "vpn ipsec site-to-site peer $peer authentication x509 ";
    my $cacrt = $vcVPN->returnValue("ipsec site-to-site peer $peer authentication x509 ca-cert-file");
    vpn_die([split(' ', ($path."ca-cert-file"))],"$vpn_cfg_err No CA certificate for peer \"$peer\" specified.\n") if !defined($cacrt);
    my $crl = $vcVPN->returnValue("ipsec site-to-site peer $peer authentication x509 crl-file");
    my $crt = $vcVPN->returnValue("ipsec site-to-site peer $peer authentication x509 cert-file");
    vpn_die([split(' ', ($path."cert-file"))],"$vpn_cfg_err No Certificate for peer \"$peer\" specified.\n") if !defined($crt);
    my $key = $vcVPN->returnValue("ipsec site-to-site peer $peer authentication x509 key file");
    vpn_die([split(' ', ($path."key-file"))],"$vpn_cfg_err No Key for peer \"$peer\" specified.\n") if !defined($key);

    # Verify the files exist
    vpn_die([split(' ', ($path."ca-cert-file"))], "Invalid ca-cert-file \"$cacrt\"")
        if (!-f $cacrt);
    vpn_die([split(' ', ($path."cert-file"))], "Invalid cert-file \"$crt\"")
        if (!-f $crt);
    vpn_die([split(' ', ($path."key-file"))], "Invalid key-file \"$key\"")
        if (!-f $key);

    # Copy files to the ipsec directory
    system("cp -f $cacrt $CA_CERT_PATH/");
    vpn_die([split(' ', ($path."ca-cert-file"))], "Cannot copy ca-cert-file \"$cacrt\"")
        if ($? >> 8);
    system("cp -f $crt $SERVER_CERT_PATH/");
    vpn_die([split(' ', ($path."cert-file"))], "Cannot copy cert-file \"$crt\"")
        if ($? >> 8);
    system("cp -f $key $SERVER_KEY_PATH/");
    vpn_die([split(' ', ($path."key-file"))], "Cannot copy key-file \"$key\"")
        if ($? >> 8);

    # Handle CRL file if it is defined
    if (defined($crl)) {
        vpn_die([split(' ', ($path."crl-file"))], "Invalid crl-file \"$crl\"")
            if (!-f $crl);
        system("cp -f $crl $CRL_PATH/");
        vpn_die([split(' ', ($path."crl-file"))], "Cannot copy crl-file \"$crl\"")
            if ($? >> 8);
    }
    $crt =~ s/^.*(\/[^\/]+)$/${SERVER_CERT_PATH}$1/;
    my $auth_str = "\tauthby=rsasig\n";
    $auth_str .= "\tleftrsasigkey=%cert\n";
    $auth_str .= "\trightrsasigkey=%cert\n";
    $auth_str .= "\trightca=%same\n";
    $auth_str .= "\tleftcert=$crt\n";
    return $auth_str;
}

sub get_x509_secret {
    my $peer = pop(@_);
    my $key_file = $vcVPN->returnValue("ipsec site-to-site peer $peer authentication x509 key file");
    my $key_pass = $vcVPN->returnValue("ipsec site-to-site peer $peer authentication x509 key password");
    my $pstr = (defined($key_pass) ? " \"$key_pass\"" : '');
    $key_file =~ s/^.*(\/[^\/]+)$/${SERVER_KEY_PATH}$1/;
    my $str = ": RSA ${key_file}$pstr \n";
    return $str;
}

sub get_dhcp_addr {
    my ($dhcp_iface, $peer) = @_;
    vpn_die(["vpn","ipsec","site-to-site","peer",$peer,"dhcp-interface"],"$vpn_cfg_err The specified interface is not configured for dhcp.")
        if (!(Vyatta::Misc::is_dhcp_enabled($dhcp_iface,0)));
    my @dhcp_addr = Vyatta::Misc::getIP($dhcp_iface,4);
    my $addr = pop(@dhcp_addr);
    if (!defined($addr)){
        $addr = '';
        return $addr;
    }
    @dhcp_addr = split(/\//, $addr);
    $addr = $dhcp_addr[0];
    return $addr;
}

sub dhcp_hook {
    my $dhcp_iface = pop(@_);
    my $str = '';
    if ($dhcp_iface > 0){
        $str =<<EOS;
#!/bin/sh
/usr/libexec/vyos/system/vyatta-ipsec-dhcp.py --interface=\"\$interface\" --new_ip=\"\$new_ip_address\" --reason=\"\$reason\" --old_ip=\"\$old_ip_address\"
EOS
    }
    my $hook = "/etc/dhcp/dhclient-exit-hooks.d/ipsecd";
    open my $dhcp_hook, '>', $hook
        or die "cannot open $hook";
    print ${dhcp_hook} $str;
    close $dhcp_hook;
}

sub get_dh_cipher_result { 
    my ($cipher) = @_;
    my $ciph_out;
    if ($cipher eq '1' || $cipher eq 'dh-group1') {
        $ciph_out = 'modp768';
    } elsif ($cipher eq '2' || $cipher eq 'dh-group2') {
        $ciph_out = 'modp1024';
    } elsif ($cipher eq '5' || $cipher eq 'dh-group5') {
        $ciph_out = 'modp1536';
    } elsif ($cipher eq '14' || $cipher eq 'dh-group14') {
        $ciph_out = 'modp2048';
    } elsif ($cipher eq '15' || $cipher eq 'dh-group15') {
        $ciph_out = 'modp3072';
    } elsif ($cipher eq '16' || $cipher eq 'dh-group16') {
        $ciph_out = 'modp4096';
    } elsif ($cipher eq '17' || $cipher eq 'dh-group17') {
        $ciph_out = 'modp6144';
    } elsif ($cipher eq '18' || $cipher eq 'dh-group18') {
        $ciph_out = 'modp8192';
    } elsif ($cipher eq '19' || $cipher eq 'dh-group19') {
        $ciph_out = 'ecp256';
    } elsif ($cipher eq '20' || $cipher eq 'dh-group20') {
        $ciph_out = 'ecp384';
    } elsif ($cipher eq '21' || $cipher eq 'dh-group21') {
        $ciph_out = 'ecp521';
    } elsif ($cipher eq '22' || $cipher eq 'dh-group22') {
        $ciph_out = 'modp1024s160';
    } elsif ($cipher eq '23' || $cipher eq 'dh-group23') {
        $ciph_out = 'modp2048s224';
    } elsif ($cipher eq '24' || $cipher eq 'dh-group24') {
        $ciph_out = 'modp2048s256';
    } elsif ($cipher eq '25' || $cipher eq 'dh-group25') {
        $ciph_out = 'ecp192';
    } elsif ($cipher eq '26' || $cipher eq 'dh-group26') {
        $ciph_out = 'ecp224';
    } elsif ($cipher eq '27' || $cipher eq 'dh-group27') {
        $ciph_out = 'ecp224bp';
    } elsif ($cipher eq '28' || $cipher eq 'dh-group28') {
        $ciph_out = 'ecp256bp';
    } elsif ($cipher eq '29' || $cipher eq 'dh-group29') {
        $ciph_out = 'ecp384bp';
    } elsif ($cipher eq '30' || $cipher eq 'dh-group30') {
        $ciph_out = 'ecp512bp';
    } elsif ($cipher eq '31' || $cipher eq 'dh-group31') {
        $ciph_out = 'curve25519';
    } elsif ($cipher eq '32' || $cipher eq 'dh-group32') {
        $ciph_out = 'curve448';
    } else {
        $ciph_out = 'unknown';
    }
    return $ciph_out;
}

# end of file
