/*
 * Copyright (c) 2016 VMware, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at:
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "Conntrack.h"
#include "PacketParser.h"

/* Eg: 227 Entering Passive Mode (a1,a2,a3,a4,p1,p2)*/
#define FTP_PASV_RSP_PREFIX "227"

typedef enum FTP_TYPE {
    FTP_TYPE_PASV = 1,
    FTP_TYPE_ACTIVE
} FTP_TYPE;

static __inline UINT32
OvsStrncmp(const char *s1, const char *s2, size_t n)
{
    if (!s1 || !s2) {
        return 0;
    }

    const char *s2end = s2 + n;
    while (s2 < s2end && *s2 != '\0' && toupper(*s1) == toupper(*s2)) {
        s1++, s2++;
    }

    if (s2end == s2) {
        return 0;
    }

    return (UINT32)(toupper(*s1) - toupper(*s2));
}

static __inline VOID
OvsStrlcpy(char *dest, const char *src, size_t size)
{
    /* XXX Replace ret with strlen(src) instead. */
    size_t ret = size;
    if (size) {
       size_t len = (ret >= size) ? size - 1 : ret;
       memcpy(dest, src, len);
       dest[len] = '\0';
   }
}

/*
 *---------------------------------------------------------------------------
 * OvsCtExtractNumbers
 * Returns an array of numbers after parsing the string.
 *    Eg: PASV: 192,168,0,1,5,6 -> {192,168,0,1,5,6}
 *        EPRT: 192.168.0.1 -> {192,168,0,1}
 *
 *---------------------------------------------------------------------------
 */
static __inline NDIS_STATUS
OvsCtExtractNumbers(char *buf,
                    UINT32 bufLen,
                    UINT32 arr[],
                    UINT32 arrLen,
                    char delimiter)
{
    if (!buf) {
        return  NDIS_STATUS_INVALID_PACKET;
    }

    UINT32 i = 0;

    while (*buf != '\0') {
        if (i >= bufLen || i >= arrLen) {
            /* Non-standard FTP command */
            return NDIS_STATUS_INVALID_PARAMETER;
        }

        /* Parse the number */
        if (*buf >= '0' && *buf <= '9') {
            arr[i] = arr[i] * 10 + *buf - '0';
        } else if (*buf == delimiter) {
            i++;
        } else {
            /* End of FTP response is either ) or \r\n */
            if (*buf == ')' || *buf == '\r' || *buf == '\n') {
                return NDIS_STATUS_SUCCESS;
            }
            /* Could be non-numerals or space */
        }
        buf++;
    }

    /* Parsing ended without the correct format */
    return NDIS_STATUS_INVALID_PARAMETER;
}

/*
 *----------------------------------------------------------------------------
 * OvsCtHandleFtp
 *     Extract the FTP control data from the packet and created a related
 *     entry if it's a valid connection. This method doesn't support extended
 *     FTP yet. Supports PORT and PASV commands.
 *     Eg:
 *     'PORT 192,168,137,103,192,22\r\n' -> '192.168.137.103' and 49174
 *     '227 Entering Passive Mode (192,168,137,104,194,14)\r\n' gets extracted
 *      to '192.168.137.104' and 49678
 *----------------------------------------------------------------------------
 */
NDIS_STATUS
OvsCtHandleFtp(PNET_BUFFER_LIST curNbl,
               OvsFlowKey *key,
               OVS_PACKET_HDR_INFO *layers,
               UINT64 currentTime,
               POVS_CT_ENTRY entry,
               BOOLEAN request)
{
    NDIS_STATUS status;
    FTP_TYPE ftpType = 0;
    const char *buf;
    char temp[256] = { 0 };
    char ftpMsg[256] = { 0 };

    UINT32 len;
    TCPHdr tcpStorage;
    const TCPHdr *tcp;
    tcp = OvsGetTcpHeader(curNbl, layers, &tcpStorage, &len);
    if (!tcp) {
        return NDIS_STATUS_INVALID_PACKET;
    }

    if (len > sizeof(temp)) {
        /* We only care up to 256 */
        len = sizeof(temp);
    }

    buf = OvsGetPacketBytes(curNbl, len,
                            layers->l4Offset + TCP_HDR_LEN(tcp),
                            temp);
    if (buf == NULL) {
        return NDIS_STATUS_INVALID_PACKET;
    }

    OvsStrlcpy((char *)ftpMsg, (char *)buf, min(len, sizeof(ftpMsg)));
    char *req = NULL;

    if (request) {
        if ((len >= 5) && (OvsStrncmp("PORT", ftpMsg, 4) == 0)) {
            ftpType = FTP_TYPE_ACTIVE;
            req = ftpMsg + 4;
        }
    } else {
        if ((len >= 4) && (OvsStrncmp(FTP_PASV_RSP_PREFIX, ftpMsg, 3) == 0)) {
            ftpType = FTP_TYPE_PASV;
            /* There are various formats for PASV command. We try to support
             * some of them. This has been addressed by RFC 2428 - EPSV.
             * Eg:
             *    227 Entering Passive Mode (h1,h2,h3,h4,p1,p2).
             *    227 Entering Passive Mode (h1,h2,h3,h4,p1,p2
             *    227 Entering Passive Mode. h1,h2,h3,h4,p1,p2
             *    227 =h1,h2,h3,h4,p1,p2
             */
            char *paren;
            paren = strchr(ftpMsg, '(');
            if (paren) {
                req = paren + 1;
            } else {
                /* PASV command without ( */
                req = ftpMsg + 3;
            }
        }
    }

    if (req == NULL) {
        /* Not a PORT/PASV control packet */
        return NDIS_STATUS_SUCCESS;
    }

    UINT32 arr[6] = {0};
    status = OvsCtExtractNumbers(req, len, arr, 6, ',');

    if (status != NDIS_STATUS_SUCCESS) {
        return status;
    }

    UINT32 ip = ntohl((arr[0] << 24) | (arr[1] << 16) |
                      (arr[2] << 8) | arr[3]);
    UINT16 port = ntohs(((arr[4] << 8) | arr[5]));

    switch (ftpType) {
    case FTP_TYPE_PASV:
        /* Ensure that the command states Server's IP address */
        ASSERT(ip == key->ipKey.nwSrc);

        OvsCtRelatedEntryCreate(key->ipKey.nwProto,
                                key->l2.dlType,
                                /* Server's IP */
                                ip,
                                /* Use intended client's IP */
                                key->ipKey.nwDst,
                                /* Dynamic port opened on server */
                                port,
                                /* We don't know the client port */
                                0,
                                currentTime,
                                entry);
        break;
    case FTP_TYPE_ACTIVE:
        OvsCtRelatedEntryCreate(key->ipKey.nwProto,
                                key->l2.dlType,
                                /* Server's default IP address */
                                key->ipKey.nwDst,
                                /* Client's IP address */
                                ip,
                                /* FTP Data Port is 20 */
                                ntohs(IPPORT_FTP_DATA),
                                /* Port opened up on Client */
                                port,
                                currentTime,
                                entry);
        break;
    default:
        OVS_LOG_ERROR("invalid ftp type:%d", ftpType);
        status = NDIS_STATUS_INVALID_PARAMETER;
        break;
    }

    return status;
}
