/*****************************************************************************
* 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[] =
{
    "NASTATUS_SUCCESS",
    "NASTATUS_IPSEC_OUT_OF_RESOURCES",
    "NASTATUS_IPSEC_INVALID_PARAMETER",
    "NASTATUS_IPSEC_NOT_INITIALIZED",
    "NASTATUS_IPSEC_INVALID_PEER",
    "NASTATUS_IPSEC_INVALID_KEY",
    "NASTATUS_IPSEC_INVALID_ENCRYPTION_ALGORITHM",
    "NASTATUS_IPSEC_INVALID_KEYLENGTH",
    "NASTATUS_IPSEC_INVALID_AUTHENTICATION_ALGORITHM",
    "NASTATUS_IPSEC_INVALID_HANDLE",
    "NASTATUS_IPSEC_INVALID_SECONDS",
    "NASTATUS_IPSEC_INVALID_KILOBYTES",
    "NASTATUS_IPSEC_INVALID_INDEX",
    "NASTATUS_IPSEC_WRONG_LIBRARY",
    "NASTATUS_IPSEC_ALREADY_INITIALIZED",
    "NASTATUS_IPSEC_INVALID_DEFAULT_POLICY",
    "NASTATUS_IPSEC_INVALID_SELECTOR",
    "NASTATUS_IPSEC_INVALID_ADDRESS",
    "NASTATUS_IPSEC_INVALID_IP_ADDRESS",
    "NASTATUS_IPSEC_SA_NOT_ACTIVE",
    "NASTATUS_IPSEC_INVALID_TABLE_ENTRY",
    "NASTATUS_IPSEC_INVALID_RULES",
    "NASTATUS_IPSEC_SA_IN_USE",
    "NASTATUS_IPSEC_NOT_CONFIGURED"
};

const unsigned MAX_ERROR_CODE = asizeof(errorLookupTable);
static NaIpsecPolicySelectorHandle_t selectorHandle;
static NaIpsecPolicyContentHandle_t contentHandle;



/*
 * 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 != NASTATUS_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;
}









/*
 * This routine sets some configuration values for IPSEC and IKE.
 */
void setupIpsec(void)
{
    /*
     * Antireplay should be disabled for manually keyed connections.
     */
    HANDLE_ERROR(naIpsecSetAntireplay(FALSE), "naIpsecSetAntireplay");
    
}


/*
 * 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.
 *
 * When IPSEC processes a packet whose policy indicates it must be sent over a 
 * VPN, IPSEC will check the SADB to see if the appropriate SAs have been set
 * up.  The packet will be sent over the VPN if the SAs have been set up, or
 * dropped if they have not.
 */
void setupPolicyTable(void)
{
    NaIpsecPolicyTableHandle_t policyHandle;
    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 ENCRYPTION_ALGORITHM
     * and AUTHENTICATION_ALGORITHM.
     */
    HANDLE_ERROR(naIpsecPolicyContentSetLocalAddress(contentHandle, 1, &localAddress),
                 "naIpsecPolicyContentSetLocalAddress");
    HANDLE_ERROR(naIpsecPolicyContentSetRemoteAddress(contentHandle, 1, &remoteAddress),
                 "naIpsecPolicyContentSetRemoteAddress");
    HANDLE_ERROR(naIpsecPolicyContentSetAuthentication(contentHandle, 1, 
                                                        AUTHENTICATION_ALGORITHM),
                                                        "naIpsecPolicyContentSetAuthentication");
    HANDLE_ERROR(naIpsecPolicyContentSetEncryption(contentHandle, 1, 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, LIFETIME_SECONDS, 
                                                  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 policy pair table after naIpsecPolicyCheckingStart() completes,
     * but we need the other two tables to create the SAs.
     */
    HANDLE_ERROR(naIpsecPolicyTableFree(policyHandle), "naIpsecPolicyTableFree");
    
    /*
     * 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.
     */
}


/*
 * This function manually adds the Security Association (SA) entries needed by the VPN
 * tunnel into the SA database (SADB).  Normally, this is done automatically by IKE,
 * but some older equiptment does not support IKE.  It is necessary to manually 
 * add the SAs in order to interoperate with equiptment like this.  
 *
 * We need to create 2 SAs, one for each direction.  We would need additional SAs if
 * we were creating more than one VPN tunnel.  
 */
void createSAs(void)
{
    NaIpsecSaHandle_t inSa, outSa;

/*
 * Set up the inbound SA.
 */
    HANDLE_ERROR(naIpsecSaCreate(&inSa), "naIpsecSaCreate");
    HANDLE_ERROR(naIpsecSaSetDirection(inSa, NA_IPSEC_DIRECTION_INBOUND), "naIpsecSaSetDirection");
    HANDLE_ERROR(naIpsecSaSetSpi(inSa, INBOUND_SPI), "naIpsecSaSetSpi");
    HANDLE_ERROR(naIpsecSaSetPolicySelector(inSa, selectorHandle, 1), "naIpsecSaSetPolicySelector");
    HANDLE_ERROR(naIpsecSaSetPolicyContent(inSa, contentHandle, 1), "naIpsecSaSetPolicyContent");
    HANDLE_ERROR(naIpsecSaSetEncryptionKey(inSa, (unsigned char *)ENCRYPTION_KEY), "naIpsecSaSetEncryptionKey");
    HANDLE_ERROR(naIpsecSaSetAuthenticationKey(inSa, (unsigned char *)AUTHENTICATION_KEY), "naIpsecSaSetAuthenticationKey");
    HANDLE_ERROR(naIpsecSaSetFate(inSa, SAS_ARE_PERMANENT), "naIpsecSaSetFate");
    HANDLE_ERROR(naIpsecSaActivate(inSa), "naIpsecSaActivate");

/*
 * Set up the outbound SA.
 */    
    HANDLE_ERROR(naIpsecSaCreate(&outSa), "naIpsecSaCreate");
    HANDLE_ERROR(naIpsecSaSetDirection(outSa, NA_IPSEC_DIRECTION_OUTBOUND), "naIpsecSaSetDirection");
    HANDLE_ERROR(naIpsecSaSetSpi(outSa, OUTBOUND_SPI), "naIpsecSaSetSpi");
    HANDLE_ERROR(naIpsecSaSetPolicySelector(outSa, selectorHandle, 1), "naIpsecSaSetPolicySelector");
    HANDLE_ERROR(naIpsecSaSetPolicyContent(outSa, contentHandle, 1), "naIpsecSaSetPolicyContent");
    HANDLE_ERROR(naIpsecSaSetEncryptionKey(outSa, (unsigned char *)ENCRYPTION_KEY), "naIpsecSaSetEncryptionKey");
    HANDLE_ERROR(naIpsecSaSetAuthenticationKey(outSa, (unsigned char *)AUTHENTICATION_KEY), "naIpsecSaSetAuthenticationKey");
    HANDLE_ERROR(naIpsecSaSetFate(outSa, SAS_ARE_PERMANENT), "naIpsecSaSetFate");
    HANDLE_ERROR(naIpsecSaActivate(outSa), "naIpsecSaActivate");
}


/*
 * This example program sets up IPSEC to create a VPN tunnel using manually keyed SAs.
 * SAs are normally negotiated by IKE, but can be manually keyed when IKE cannot be 
 * used for some reason.  
 *
 * Manually keyed SAs are not as secure as using IKE.  
 */
void createVpn(void)
{
    HANDLE_ERROR(naIpsecInitialize(),"naIpsecInitialize");
    setupIpsec();
    setupPolicyTable();
    createSAs();
    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);

}




