/*****************************************************************************
* Copyright (c) 2005 Digi International Inc., All Rights Reserved
*
* This software contains proprietary and confidential information of Digi
* International Inc.  By accepting transfer of this copy, Recipient agrees
* to retain this software in confidence, to prevent disclosure to others,
* and to make no use of this software other than that for which it was
* delivered.  This is an unpublished copyrighted work of Digi International
* Inc.  Except as permitted by federal law, 17 USC 117, copying is strictly
* prohibited.
*
* Restricted Rights Legend
*
* Use, duplication, or disclosure by the Government is subject to
* restrictions set forth in sub-paragraph (c)(1)(ii) of The Rights in
* Technical Data and Computer Software clause at DFARS 252.227-7031 or
* subparagraphs (c)(1) and (2) of the Commercial Computer Software -
* Restricted Rights at 48 CFR 52.227-19, as applicable.
*
* Digi International Inc. 11001 Bren Road East, Minnetonka, MN 55343
*
*****************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "Npttypes.h"
#include "sockapi.h"
#include "ipsec/naipsec.h"
#include "naip_global.h"
#include "tx_api.h"
#include "vpn.h"


#define NA_IPSEC_FACILITY_CODE                      (0x3)

const char *errorLookupTable[] =
{
    "NA_SUCCESS",
    "NA_IPSEC_OUT_OF_RESOURCES",
    "NA_IPSEC_INVALID_PARAMETER",
    "NA_IPSEC_NOT_INITIALIZED",
    "NA_IPSEC_INVALID_PEER",
    "NA_IPSEC_INVALID_KEY",
    "NA_IPSEC_INVALID_ENCRYPTION_ALGORITHM",
    "NA_IPSEC_INVALID_KEYLENGTH",
    "NA_IPSEC_INVALID_AUTHENTICATION_ALGORITHM",
    "NA_IPSEC_INVALID_HANDLE",
    "NA_IPSEC_INVALID_SECONDS",
    "NA_IPSEC_INVALID_KILOBYTES",
    "NA_IPSEC_INVALID_INDEX",
    "NA_IPSEC_WRONG_LIBRARY",
    "NA_IPSEC_ALREADY_INITIALIZED",
    "NA_IPSEC_INVALID_DEFAULT_POLICY",
    "NA_IPSEC_INVALID_SELECTOR",
    "NA_IPSEC_INVALID_ADDRESS",
    "NA_IPSEC_INVALID_IP_ADDRESS",
    "NA_IPSEC_SA_NOT_ACTIVE",
    "NA_IPSEC_INVALID_TABLE_ENTRY",
    "NA_IPSEC_INVALID_RULES",
    "NA_IPSEC_SA_IN_USE",
    "NA_IPSEC_NOT_CONFIGURED"
};

const unsigned MAX_ERROR_CODE = asizeof(errorLookupTable);



/*
 * I use this macro to examine the return code from ipsec function calls.  If
 * a function returns in error the macro will report the error and exit.
 *
 * There is no reason why you have to use a macro like this.  I am doing it
 * only because I don't want to write code everywhere I make a function call
 * to check the return value.
 */
#define HANDLE_ERROR(fn, fnName)                                    \
        {                                                           \
            NaStatus ccode = fn;                                    \
            if (ccode != NA_SUCCESS)                                \
            {                                                       \
                reportError(fnName, ccode, __FILE__, __LINE__);     \
            }                                                       \
        }                                                           \

/*
 * Utility function to report an error and exit.  If the error is one of the
 * codes in our lookup table above, then we will translate it into a string
 * to make it more readable.
 */
void reportError(char *functionName, NaStatus ccode, char *filename, int lineNumber)
{
    unsigned facilityCode = (ccode >> 16) & 0xfff;
    unsigned errorIndex = ccode & 0xffff;

    if ((facilityCode == NA_IPSEC_FACILITY_CODE) && (errorIndex < MAX_ERROR_CODE))
    {
        printf ("%s returned error %s at line %d in file %s.\n", functionName,
                errorLookupTable[errorIndex], lineNumber, filename);
    }
    else
    {
        printf ("%s returned error 0x%8.8X at line %d in file %s.\n", functionName,
                (unsigned int)ccode, lineNumber, filename);
    }

    tx_thread_suspend(tx_thread_identify());
}


/*
 * This is a simple utility function that translates an IPv4 or IPv6 address string
 * into sockaddr_storage structure.  IPv4 address strings must be in the form "a.b.c.d",
 * and IPv6 address strings must be in the form "abcd:efgh:....".
 *
 * The function returns -1 if the string cannot be translated.
 */
static int translateAddressString(char *addressString, struct sockaddr_storage *address)
{
    int result = 0;
    int ccode;

    if (strchr(addressString, ':') != NULL)     /* IPv6 address have colons*/
    {
        ccode = inet_pton(AF_INET6, addressString, &address->addr.ipv6.sin6_addr);
        address->ss_family = AF_INET6;
    }
    else
    {
        ccode = inet_pton(AF_INET, addressString, &address->addr.ipv4.sin_addr);
        address->ss_family = AF_INET;
    }
    if (ccode <= 0)
    {
        result = -1;
    }

    return result;
}


void tfSetSendSaLifetimes(unsigned int newSendSaLifetimes);


/*
 * This function starts IKE.  We have to pass an ID and the local VPN address when we
 * start IKE.  The ID and local VPN address are used to authenticate us to remote VPN
 * peers during IKE phase 1 negotiations.  You set these parameters by setting OUR_ID
 * and LOCAL_VPN_ADDRESS in vpn.h.
 */
void startIke(void)
{
    NaIkeIdentifier_t identifier;
    struct sockaddr_storage localVpnAddress;
    NaIkeIdentifier_t peerId;

    identifier.idType = VPN_ID_TYPE;
    peerId.idType = VPN_ID_TYPE;

/*
 * If the ID is an IP address, then translate it into a sockaddr_storage structure.
 * This should be the address of the local side of the VPN connection, which is
 * normally the same as LOCAL_VPN_ADDRESS.  This means that OUR_ID and LOCAL_VPN_ADDRESS
 * should be the same.  Although you can set them to be different, the remote VPN peer
 * will almost certainly reject the connection when it finds the IP address used for
 * the ID does not match our actual IP address.
 */
    printf("our ID set to %s.\n", OUR_ID);
    if (identifier.idType == IPSEC_ID_IPADDRESS)
    {
        if (translateAddressString(OUR_ID, &identifier.id.address) == -1)
        {
            printf ("OUR_ID = %s is not a valid IP address.\n", OUR_ID);
            exit(1);
        }
        if (strcmp(OUR_ID, LOCAL_VPN_ADDRESS) != 0)
        {
            printf ("Warning:  OUR_ID != LOCAL_VPN_ADDRESS.  That probably is not going to work.\n");
        }
        if (translateAddressString(PEER_ID, &peerId.id.address) == -1)
        {
            printf ("REMOTE_VPN_PEER_ADDRESS = %s is not a valid address.\n", PEER_ID);
            exit(1);
        }
    }
    else /* if OUR_ID is a fqdn or user fqdn */
    {
        strcpy(identifier.id.fqdn, OUR_ID);
        strcpy(peerId.id.fqdn, PEER_ID);
    }

    if (translateAddressString(LOCAL_VPN_ADDRESS, &localVpnAddress) == -1)
    {
        printf ("LOCAL_VPN_ADDRESS = %s is not a valid IP address.\n", LOCAL_VPN_ADDRESS);
        exit(1);
    }

    HANDLE_ERROR(naIpsecIkeStart(&identifier, &localVpnAddress),"naIpsecIkeStart");
    /*
     * The function call above starts IKE.  That means we will both accept negotiation attempts
     * from remote VPN peers, and will automatically attempt to negotiate a connection if
     * packets are detected that need to be sent across a VPN.
     */

    /*
     * We will use preshared keys to authentication with remote VPN gateways.
     * You need to add a preshared key for each remote VPN gateway you want
     * to communicate with.  You must add the keys after calling naIpsecIkeStart.
     */
    HANDLE_ERROR(naIpsecPresharedKeyAdd(&peerId, (unsigned char *)SHARED_KEY), "naIpsecPresharedKeyAdd");

    /*
     * This statement should not be necessary since the IPSEC library sets this
     * option to TRUE by default.  It's here to remind you that you can try
     * not sending the lifetime information  if your VPN device rejects the IKE
     * negotiation.  There are some VPN devices which will reject the negotiation
     * if the SA lifetime information is included.  Of course, the RFCs say they
     * should accept it.
     */
    HANDLE_ERROR(naIpsecIkeSendSaLifetimes(TRUE), "naIpsecIkeSendSaLifetimes");
}



/*
 * This function sets up a IKE phase 1 transform.  Normally you do NOT need to do
 * this.  The default phase 1 transform works just fine.  We do it here solely to
 * demonstrate how the API works.
 *
 * IKE negotiates connections in 2 phases.  In the first phase, the two VPN peers
 * authenticate each other and create a pair of SAs that will be used during phase 2.
 * In phase 2, additional SAs are created that are used for data transfer.
 *
 * The two peers negotiate the security parameters for the first 2 SAs during phase 1.
 * The initiator sends a list of up to 2 sets of security parameters, refered to as
 * transforms, to the remote VPN.  The remote VPN picks one, and those settings are
 * used to create the SA.
 */
void setupPhase1Settings(void)
{
    NaIkePhase1TransformHandle_t handle;

    HANDLE_ERROR(naIpsecIkePhase1GetTransformHandle(&handle), "naIpsecIkePhase1GetTransformHandle");
    HANDLE_ERROR(naIpsecIkePhase1SetEncryption(handle, PHASE1_ENCRYPTION_ALGORITHM, PHASE1_ENCRYPTION_KEY_LENGTH),
                 "naIpsecIkePhase1SetEncryption");
    HANDLE_ERROR(naIpsecIkePhase1SetAuthentication(handle, PHASE1_AUTHENTICATION_ALGORITHM),
                 "naIpsecIkePhase1SetAuthentication");
    HANDLE_ERROR(naIpsecIkePhase1SetDHGroup(handle, DIFFIE_HELMAN_GROUP), "naIpsecIkePhase1SetDHGroup");
    HANDLE_ERROR(naIpsecIkePhase1SetLifetime(handle, PHASE1_LIFETIME_SECONDS, PHASE1_LIFETIME_KILOBYTES),
                 "naIpsecIkePhase1SetLifetime");

    /*
     * There are only 2 phase 1 transforms, 0 and 1.  Since we are only going to
     * specify 1 transform, it is number 0.
     */
    HANDLE_ERROR(naIpsecIkePhase1TransformSet(handle, 0), "naIpsecIkePhase1TransformSet");

    /*
     * The handle can be deleted after naIpsecIkePhase1TransformSet() returns.
     */
    HANDLE_ERROR(naIpsecIkePhase1DeleteTransformHandle(handle), "naIpsecIkePhase1DeleteTransformHandle");
}


/*
 * This routine sets some configuration values for IPSEC and IKE.
 */
void setupIpsec(void)
{
    /*
     * See comments in vpn.h for details on what these settings control.  The
     * important thing is to make sure the 2 VPN peers agree on DH group,
     * use of antireplay, and PFS.  The connection will be rejected if the
     * two VPNs are configured differently.
     */
    HANDLE_ERROR(naIpsecIkeSetPhase1Mode(PHASE1_MODE), "naIpsecIkeSetPhase1Mode");
    HANDLE_ERROR(naIpsecIkeSetPhase1AggressiveModeDHGroup(DIFFIE_HELMAN_GROUP),
                 "naIpsecIkeSetPhase1AggressiveModeDHGroup");
    HANDLE_ERROR(naIpsecSetAntireplay(USE_ANTI_REPLAY), "naIpsecSetAntireplay");
    HANDLE_ERROR(naIpsecIkePhase2SetPfs(WANT_PFS), "naIpsecIkePhase2SetPfs");

}


/*
 * Whenever packets are sent or received, IPSEC checks against them against the
 * policy table to determine how they should be handled.  The choices are to
 * discard them, to let the TCP/IP stack process them as normal, or to have
 * IPSEC process them for communications over a VPN.
 *
 * There are 3 tables which are all interconnected.  Policies are divided into
 * policy selectors and policy contents.  The selectors contain the information
 * that tells IPSEC how to identify which packets a policy applies to.  The
 * contents contain the information on what IPSEC is suppose to do with a packet.
 * A third table matches policy selectors to policy contents.  We create all
 * three tables and register them with IPSEC.
 *
 * Note that when IPSEC processes a packet whose policy indicates it must be
 * sent over a VPN, IPSEC creates a VPN connection to the remote network if
 * one does not already exist.  So it is the policy tables that govern when
 * we initiate IKE negotiations.
 */
void setupPolicyTable(void)
{
    NaIpsecPolicyTableHandle_t policyHandle;
    NaIpsecPolicySelectorHandle_t selectorHandle;
    NaIpsecPolicyContentHandle_t contentHandle;
    NaIpsecAddressRange_t range;
    struct sockaddr_storage localAddress, remoteAddress;

    /*
     * Translate string address LOCAL_VPN_ADDRESS and REMOTE_VPN_PEER_ADDRESS
     * into sockaddr_storage structures.
     */
    if (translateAddressString(LOCAL_VPN_ADDRESS, &localAddress) == -1)
    {
        printf ("LOCAL_VPN_ADDRESS = %s is invalid.\n", LOCAL_VPN_ADDRESS);
        exit(1);
    }
    if (translateAddressString(REMOTE_VPN_PEER_ADDRESS, &remoteAddress) == -1)
    {
        printf ("LOCAL_VPN_ADDRESS = %s is invalid.\n", REMOTE_VPN_PEER_ADDRESS);
        exit(1);
    }

    /*
     * We are going to create a simple example with only 2 policy
     * entries.  One for IPSEC traffic, and one for everything else.
     * So we create the 3 tables with 2 entries each.
     */
    HANDLE_ERROR(naIpsecPolicyTableCreate(&policyHandle, 2), "naIpsecPolicyTableCreate");
    HANDLE_ERROR(naIpsecPolicySelectorTableCreate(&selectorHandle, 2),
                 "naIpsecPolicySelectorTableCreate");
    HANDLE_ERROR(naIpsecPolicyContentTableCreate(&contentHandle, 2),
                 "naIpsecPolicyContentTableCreate");

    /*
     * IPSEC scans the table in reverse order, so it is going to scan entry 1 first,
     * and then entry 0.  Entry 0 is special.  It has to handle all of the packets
     * that are not selected by previous policies.  So entry 0 has to:
     *
     *  1.  Select all packets.
     *  2.  Tell IPSEC what to do with those packets, either discard or process by TCP/IP.
     *
     * First step is to create a selector that selects all packets.
     */
    range.format = NA_IPSEC_RANGE_ALL;
    HANDLE_ERROR(naIpsecPolicySelectorSetLocalAddresses(selectorHandle, 0, &range),
                 "naIpsecPolicySelectorSetLocalAddresses");
    HANDLE_ERROR(naIpsecPolicySelectorSetRemoteAddresses(selectorHandle, 0, &range),
                 "naIpsecPolicySelectorSetRemoteAddresses");
    HANDLE_ERROR(naIpsecPolicySelectorSetLocalPort(selectorHandle, 0, NA_IPSEC_ANY_PORT),
                 "naIpsecPolicySelectorSetLocalPort");
    HANDLE_ERROR(naIpsecPolicySelectorSetRemotePort(selectorHandle, 0, NA_IPSEC_ANY_PORT),
                 "naIpsecPolicySelectorSetRemotePort");
    HANDLE_ERROR(naIpsecPolicySelectorSetProtocol(selectorHandle, 0, NA_IPSEC_ANY_PROTOCOL),
                 "naIpsecPolicySelectorSetProtocol");

    /*
     * Now create a policy content.  We are going to set the content rules flag to
     * NA_IPSEC_CONTENT_BYPASS_IPSEC_POLICY_CHECKING which causes matching packets to be
     * processed as normal by TCP/IP.  This is the value you would normally use for entry
     * 0.  However, there are some security intensive applications where you might not want
     * to process left over packets.  In that case you would set the rules flag to
     * be NA_IPSEC_CONTENT_DISCARD_MATCHING_PACKETS.  Be careful using that as it can
     * cause communications problems if you are relying on some protocol, like TCP for
     * example, that you forgot to create an entry for in the policy table.
     */
    HANDLE_ERROR(naIpsecPolicyContentSetRules(contentHandle, 0,
                                              NA_IPSEC_CONTENT_BYPASS_IPSEC_POLICY_CHECKING),
                                              "naIpsecPolicyContentSetRules");

    /*
     * Now set selector and content in the policy table.  The direction must be set
     * to NA_IPSEC_DIRECTION_BOTH for entry 0.
     */
    HANDLE_ERROR(naIpsecPolicySet(policyHandle, 0, 0, 0, NA_IPSEC_DIRECTION_BOTH),
                 "naIpsecPolicySet");


    /*
     * Now create the entry to catch packets that should be processed by IPSEC.
     * Set the local and remote address ranges to those specified in vpn.h.
     */
    range.format = NA_IPSEC_RANGE_SUBNET;
    if (translateAddressString(LOCAL_IP_SUBNET, &range.first) == -1)
    {
        printf ("LOCAL_IP_SUBNET = %s is invalid.\n", LOCAL_IP_SUBNET);
        exit(1);
    }
    if (translateAddressString(LOCAL_IP_SUBNET_MASK, &range.last) == -1)
    {
        printf ("LOCAL_IP_SUBNET_MASK = %s is invalid.\n", LOCAL_IP_SUBNET_MASK);
        exit(1);
    }
    HANDLE_ERROR(naIpsecPolicySelectorSetLocalAddresses(selectorHandle, 1, &range),
                 "naIpsecPolicySelectorSetLocalAddresses");
    if (translateAddressString(REMOTE_IP_SUBNET, &range.first) == -1)
    {
        printf ("REMOTE_IP_SUBNET = %s is invalid.\n", REMOTE_IP_SUBNET);
        exit(1);
    }
    if (translateAddressString(REMOTE_IP_SUBNET_MASK, &range.last) == -1)
    {
        printf ("LOCAL_IP_SUBNET_MASK = %s is invalid.\n", REMOTE_IP_SUBNET_MASK);
        exit(1);
    }
    HANDLE_ERROR(naIpsecPolicySelectorSetRemoteAddresses(selectorHandle, 1, &range),
                 "naIpsecPolicySelectorSetRemoteAddresses");
    HANDLE_ERROR(naIpsecPolicySelectorSetLocalPort(selectorHandle, 1, NA_IPSEC_ANY_PORT),
                 "naIpsecPolicySelectorSetLocalPort");
    HANDLE_ERROR(naIpsecPolicySelectorSetRemotePort(selectorHandle, 1, NA_IPSEC_ANY_PORT),
                 "naIpsecPolicySelectorSetRemotePort");
    HANDLE_ERROR(naIpsecPolicySelectorSetProtocol(selectorHandle, 1, NA_IPSEC_ANY_PROTOCOL),
                 "naIpsecPolicySelectorSetProtocol");

    /*
     * Set the policy content so matching packets are sent over a VPN tunnel whose
     * remote endpoint is at REMOTE_VPN_PEER_ADDRESS using the PHASE2_ENCRYPTION_ALGORITHM
     * and PHASE2_AUTHENTICATION_ALGORITHM.
     */
    HANDLE_ERROR(naIpsecPolicyContentSetLocalAddress(contentHandle, 1, &localAddress),
                 "naIpsecPolicyContentSetLocalAddress");
    HANDLE_ERROR(naIpsecPolicyContentSetRemoteAddress(contentHandle, 1, &remoteAddress),
                 "naIpsecPolicyContentSetRemoteAddress");
    HANDLE_ERROR(naIpsecPolicyContentSetAuthentication(contentHandle, 1,
                                                        PHASE2_AUTHENTICATION_ALGORITHM),
                                                        "naIpsecPolicyContentSetAuthentication");
    HANDLE_ERROR(naIpsecPolicyContentSetEncryption(contentHandle, 1, PHASE2_ENCRYPTION_ALGORITHM),
                 "naIpsecPolicyContentSetEncryption");
    /*
     * Select tunnel mode and the ESP algorithm.  This is the combination you should use
     * in most cases.  Transport mode and AH protocol are NOT as secure and have problems
     * with some routers.  Do not use transport mode and AH protocol unless your application
     * requires it.
     */
    HANDLE_ERROR(naIpsecPolicyContentSetRules(contentHandle, 1,
                                              NA_IPSEC_CONTENT_TUNNEL_MODE | NA_IPSEC_CONTENT_ESP),
                                              "naIpsecPolicyContentSetRules");
    HANDLE_ERROR(naIpsecPolicyContentSetLifetimes(contentHandle, 1, PHASE2_LIFETIME_SECONDS,
                                                  PHASE2_LIFETIME_KILOBYTES), "naIpsecPolicyContentSetLifetimes");

    /*
     * Put these selectors and contents into the policy table.
     */
    HANDLE_ERROR(naIpsecPolicySet(policyHandle, 1, 1, 1, NA_IPSEC_DIRECTION_BOTH), "naIpsecPolicySet");

    /*
     * Tables have been built, so lets register them and start IPSEC policy checking.
     */
    HANDLE_ERROR(naIpsecPolicyCheckingStart(policyHandle, selectorHandle, contentHandle),
                    "naIpsecPolicyCheckingStart");

    /*
     * We can release the data structures after naIpsecPolicyCheckingStart() completes.
     */
    HANDLE_ERROR(naIpsecPolicyTableFree(policyHandle), "naIpsecPolicyTableFree");
    HANDLE_ERROR(naIpsecPolicySelectorTableDestroy(selectorHandle), "naIpsecPolicySelectorTableDestroy");
    HANDLE_ERROR(naIpsecPolicyContentTableDelete(contentHandle), "naIpsecPolicyContentTableDelete");

    /*
     * We created a very simple policy table with a one to one relationship between policy
     * contents and policy selectors.  However, you can create more complex relationships.
     * For example, suppose you want all TCP and UDP traffic between two subnets to be
     * routed over the same VPN tunnel, but do not want other protocols to be sent over
     * the tunnel.  You could create two different policy selectors that select TCP and
     * UDP, and have them share a single policy content.
     */
}



void createVpn(void)
{
    HANDLE_ERROR(naIpsecInitialize(),"naIpsecInitialize");
    setupIpsec();
    setupPolicyTable();
    startIke();
    setupPhase1Settings();
    printf ("IPSEC has been configured to tunnel traffic between\n");
    printf ("subnets %s and %s.\n", LOCAL_IP_SUBNET, REMOTE_IP_SUBNET);
    printf ("The remote VPN peer address has been set to %s.\n", REMOTE_VPN_PEER_ADDRESS);

    /*
     * You must enabled forwarding both by calling this function and by updating the
     * code in the platform directory to enable forward on the Ethernet interface.
     */
    NAIpSetIpForwarding(TRUE);

}




