www.pudn.com > Firewall_PNE_3_3.zip > fwExtFtp.c, change:2009-03-16,size:23202b


/* fwExtFtp.c - Firewall FTP Extension module */

/*
 * Copyright (c) 2004-2006 Wind River Systems, Inc.
 *
 * The right to copy, distribute, modify or otherwise make use
 * of this software may be licensed only pursuant to the terms
 * of an applicable Wind River license agreement.
 */

/*
modification history
--------------------
01l,15mar06,kch  Added fwExtFtpHandlerInit() routine and additional debug 
                 statements to fwExtFtpHandler() and dataChannelRuleCreate()
                 (SPR#119165).
01k,26apr05,zhu  updated for gtfTimerManagerCB
01j,01feb05,myz  added FTP6 handling
01i,13jul04,myz  move common header files to fw.h
01h,05mar04,myz  Register a data channel creation timeout function
01g,04mar04,svk  Set FTP server port to 0 for PORT command
01f,13feb04,myz  add choice of where to insert the created data channel rules
01e,11feb04,myz  Delete the data channel rules in timer task context
01d,09feb04,myz  modified for fwRuleGroupCreate function change
01c,30jan04,myz  public API name changes.
01b,29jan04,myz  change to use new state structure
01a,21nov03,myz  written
*/

/*
DESCRIPTION

This file provides the FTP extension handler. This handler parses FTP payload
to retrieve the information embedded in either the PORT command or the response
to the PASV command to obtain the IP address and port number of the FTP data 
channel. Based on the retrieved information, two rule entries are created at 
appropriate locations to allow the FTP data channel communicate. These two rule
entries are automatically deleted at the end of data channel connection. The
handler fwExtFtpHandler() should be installed by calling fwExtHandlerInstall().

INTERNAL:
When creating the rule entries for the data channel, the FTP handler installs
the connection start and end event funtions and registers a timeout function 
with NAT timer for the connection start period. If the data connection
does not start within 30 seconds(default), the timeout function will be called 
to delete the two rule entries. If it starts, then the start event function will
remove the registered timeout function and the data connection should proceed
normally. If the data channel connection ends normally, the end event function 
will be invoked to delete the two rule entries created for the data channel.

*/

#include "fw.h"

/* debug macros */

#undef FW_FTPEXT_DEBUG

#ifdef FW_FTPEXT_DEBUG

int fwFtpDebug = 0;
#define DBG_PRINT(X) \
    do { \
    if (fwFtpDebug) \
        printf X; \
    }while(0)
#else

#define DBG_PRINT(X)

#endif

#define FTP_PASV_PARAMETER_STRING \
        "%hu Entering Passive Mode (%hu,%hu,%hu,%hu,%hu,%hu)\r\n"
#define FTP_PASV_PARAMETER_COUNT 7
#define FTP_PASV_STRING_END_MARKER '\n' /* not ')' */
#define FTP_PASV_RETURN_CODE 227
#define FTP_PORT_STRING "PORT"
#define FTP_PORT_PARAMETER_STRING "PORT %hu,%hu,%hu,%hu,%hu,%hu\r\n"
#define FTP_PORT_PARAMETER_COUNT 6
#define FTP_PORT_STRING_END_MARKER '\n'
#define FTP6_PORT_CMD       "EPRT"
#define FTP6_PASV_RESPONSE  "229 Entering Extended Passive Mode"
#define FTP_AF_NUM_IPV4     1
#define FTP_AF_NUM_IPV6     2
#define IPADDR_STR_SIZE        (INET6_ADDRSTRLEN + 1)
#define INSPECTED_SIZE_MAX  3000  /* max inspected data size for FTP command*/ 

#ifdef FW_FTPEXT_DEBUG
LOCAL char *locStr[] = {
    "PRE-INPUT", "INPUT", "FORWARD", "OUTPUT",
#ifdef INET6
    "PRE-INPUT6", "INPUT6", "FORWARD6", "OUTPUT6",
#endif /* INET6 */
    };
#endif /* FW_FTPEXT_DEBUG */

LOCAL void ftpDataChannelDelete (void *);
LOCAL void ftpEventEndFunc (REMOVE_LIST_NODE_PAIR *);
LOCAL void ftpEventStartFunc (void *);
LOCAL int dataChannelRuleCreate (FW_RULE_ENTRY *, char *,
				 UINT16, char *, UINT16, int,void *);
LOCAL BOOL cmdStrParse (char *, char *, int *, int *, char *);

/* externals */

IMPORT GTF_TIMER_MANAGER *  gtfTimerManagerCB;   /* timer manager */


/******************************************************************************
*
* fwExtFtpHandlerInit - FTP extension handler init routine
*
* Init routine for the FTP extension handler.
* 
* RETURNS: N/A
*
* NOMANUAL
*/
void fwExtFtpHandlerInit (void)
    {
    return;
    }

/******************************************************************************
*
* fwExtFtpHandler - handle FTP protocol 
*
* This function parses the FTP payload and retrieves the FTP data channel's IP
* address and port number from either the PORT or response to the PASV commands.
*
* RETURNS: TRUE if successfully get the info or FALSE
*/

UINT32 fwExtFtpHandler
    (
    FW_LOC_TYPE loc,
    void * groupId,
    struct mbuf * pMblk,
    void * usrCtxt
    )
    {
    struct ip * pIp = NULL;
#ifdef INET6
    struct ip6_hdr * pIpv6 = NULL;
#endif
    char * pFtpPayload;
    int ftpPktOffset;
    struct in_addr ipAddr;
    int port = 0;
    char * pFtpCmdStrEnd;
    int len;
    FW_LOC_TYPE stateNewRuleLoc = FW_IN_LOC;
    UINT16 tmp[7];
    int retCode = FALSE;
    char * pTemp = NULL;
    FW_RULE_ENTRY * pEntry;
    FW_GROUP_HEAD_ENTRY * pHead;
    char dstAddrStr[IPADDR_STR_SIZE];
    char srcAddrStr[IPADDR_STR_SIZE];

    DBG_PRINT(("fwExtFtpHandler: starting for %s\n", locStr[loc]));

    pHead = (FW_GROUP_HEAD_ENTRY *)groupId;
    pEntry = &pHead->ruleEntry.rule;

    /* make sure the ext. headers and FTP payload is in a contiguous memory
     * when performing the parsing
     */
    if (pMblk->m_len < pMblk->m_pkthdr.len)
        {
        DBG_PRINT(("FW FTP: data in multiple mbuf: mbuf1: %d, plen: %d\n",
                   pMblk->m_len,pMblk->m_pkthdr.len));

        /* packet in multiple mbufs, copy to a temp buf */

        len = min(pMblk->m_pkthdr.len,INSPECTED_SIZE_MAX);

        if ((pTemp = malloc(len)) == NULL)
            {
            printf("FW FTP: Error, malloc fails\n");
            return FALSE;
            }

        if (!netMblkOffsetToBufCopy(pMblk,0,pTemp,len,NULL))
            {
            printf("fwExtFtpHandler: Error, mbuf copy fails\n");
            free(pTemp);
            return FALSE;  /* no payload, just return */
            }
        }
        
    if ((*mtod(pMblk,UINT8 *) & IPV6_VERSION_MASK) == IPV4_VERSION)
        {
        pIp = (pTemp == NULL)? mtod(pMblk,struct ip *): (struct ip *)pTemp;

        ftpPktOffset = (pIp->ip_hl << 2);
        pFtpPayload = (char *)pIp + ftpPktOffset;
        }
#ifdef INET6
    else if ((*mtod(pMblk,UINT8 *) & IPV6_VERSION_MASK) == IPV6_VERSION) 
        {
        int proto;

        pIpv6 = (pTemp == NULL)? mtod(pMblk,struct ip6_hdr *): 
                (struct ip6_hdr *)pTemp;

        ipv6UpHdrOffsetGet(pIpv6,&proto,&ftpPktOffset,
                          pMblk->m_pkthdr.len  - sizeof(struct ip6_hdr));
        pFtpPayload = (char *)pIpv6 + ftpPktOffset;
        }
#endif
    else  /* don't know about this type of packet */
        {
        if (pTemp)
            free(pTemp);
        return FALSE;   
        }
    ftpPktOffset += (((struct tcphdr *)pFtpPayload)->th_off << 2);
    pFtpPayload += (((struct tcphdr *)pFtpPayload)->th_off << 2);

    if (pMblk->m_pkthdr.len <= ftpPktOffset)
        {
        DBG_PRINT(("pMlk=0x%x, pktLen=%d,ftpPktOffset = %d, IP id: 0x%x\n",
	       (int)pMblk,pMblk->m_pkthdr.len,ftpPktOffset,pIp->ip_id));

        /* no payload in the packet, just return */

        if (pTemp)
            free(pTemp);
        return FALSE;
        }

    DBG_PRINT(("1st 20bytes of  payLoad: 0x%x,0x%x,0x%x,0x%x\n",
                *(int *)pFtpPayload,
                *((int *)pFtpPayload + 1),*((int *)pFtpPayload + 2),
                *((int *)pFtpPayload + 3)));

    dstAddrStr[0] = 0;

    /* look for the port or pasv command. Assume the entire command is
     * contained in one TCP packet.
     */

    /* PASV Section */

    if (sscanf (pFtpPayload, FTP_PASV_PARAMETER_STRING, &tmp[0],&tmp[1],&tmp[2],
                &tmp[3],&tmp[4],&tmp[5],&tmp[6]) == FTP_PASV_PARAMETER_COUNT)
        {

        pFtpCmdStrEnd = strchr (pFtpPayload,
                                       FTP_PASV_STRING_END_MARKER);

        if (pFtpCmdStrEnd != NULL)
            {
            ipAddr.s_addr = ((tmp[1] & 0xff) << 24) | ((tmp[2] & 0xff) << 16) |
                            ((tmp[3] & 0xff) << 8)  | (tmp[4] & 0xff);
            ipAddr.s_addr = htonl(ipAddr.s_addr);
            inet_ntoa_b(ipAddr,dstAddrStr);

            port = ((tmp[5] & 0xff) * 256) + (tmp[6] & 0xff);
         
            retCode = TRUE;

            DBG_PRINT(("FTP ALG: PASV command, IpAddr: %s, Port: 0x%x\n",
                   dstAddrStr, port));
            }
        }
    else if (sscanf (pFtpPayload, FTP_PORT_PARAMETER_STRING, &tmp[1],&tmp[2],
                &tmp[3],&tmp[4],&tmp[5],&tmp[6]) == FTP_PORT_PARAMETER_COUNT)
        {
        pFtpCmdStrEnd = strchr (pFtpPayload,
                                       FTP_PORT_STRING_END_MARKER);

        if (pFtpCmdStrEnd != NULL)
            {
            ipAddr.s_addr = ((tmp[1] & 0xff) << 24) |
                            ((tmp[2] & 0xff) << 16) |
                            ((tmp[3] & 0xff) << 8)  | (tmp[4] & 0xff);
            ipAddr.s_addr = htonl(ipAddr.s_addr);
            inet_ntoa_b(ipAddr,dstAddrStr);
            port = ((tmp[5] & 0xff) * 256) + (tmp[6] & 0xff);

            /* The port command */

            retCode = TRUE;

            DBG_PRINT(("FTP ALG: PORT command, IpAddr: %s, Port: 0x%x\n",
                   dstAddrStr, port));
            }
        }
    else if ((bcmp(pFtpPayload,FTP6_PORT_CMD,strlen(FTP6_PORT_CMD)) == 0) ||
             (bcmp(pFtpPayload,FTP6_PASV_RESPONSE,strlen(FTP6_PASV_RESPONSE))
                   == 0) )
        {
        char * pCmdStr;
        int afNum;
        char dltChar[2] = {0,0};

        /* cmdStrParse function modifies the passed string, if the string is
         * in original mblk buffer, it needs to be moved to a temp buffer for
         * the function to operate on
         */

        DBG_PRINT(("FTP ALG: ftp payload %s\n", pFtpPayload));

        if (bcmp(pFtpPayload,FTP6_PORT_CMD,strlen(FTP6_PORT_CMD)) == 0)
            len = strlen(FTP6_PORT_CMD) + 1;
        else
            len = strlen(FTP6_PASV_RESPONSE) + 2;

        if (pTemp == NULL)
            {
            pCmdStr = malloc(pMblk->m_pkthdr.len - (ftpPktOffset + len));
            if (pCmdStr != NULL)
                {
                bcopy(pFtpPayload + len,pCmdStr,
                      pMblk->m_pkthdr.len - (ftpPktOffset + len));

                dltChar[0] = *pCmdStr; /* get the delimiter */

                if (cmdStrParse(pCmdStr+1,dltChar,&afNum,&port,dstAddrStr) == OK)
                    retCode = TRUE;
                free(pCmdStr);               
                }
            }
        else  /* string already in a temp buffer */   
            {       
            pCmdStr = pFtpPayload + len;

            dltChar[0] = *pCmdStr; /* get the delimiter */

            if (cmdStrParse (pCmdStr + 1,dltChar,&afNum,&port,dstAddrStr) == OK)
                retCode = TRUE;
            }
        }
        
    if (retCode == TRUE)
        {
        if (loc == FW_OUT_LOC)
            {
            /* PORT cmd: a inside FTP client tells a outside FTP server
             * PASV cmd: a inside FTP server tells a outside FTP client
             * to use this IP address and port to open the data channel
             */

            stateNewRuleLoc = FW_PREIN_LOC;
            }
#ifdef INET6
        else if (loc == FW_OUT_LOC_V6)
            stateNewRuleLoc = FW_PREIN_LOC_V6;
#endif

        else if (loc == FW_IN_LOC || loc == FW_PREIN_LOC)
            {
            /* PORT cmd: a outside FTP client tells a inside FTP server
             * PASV cmd: a outside FTP server tells a inside FTP client
             * to use this IP address and port to open the data channel
             */
            stateNewRuleLoc = FW_OUT_LOC;
            }
#ifdef INET6
        else if (loc == FW_IN_LOC_V6 || loc == FW_PREIN_LOC_V6)
            stateNewRuleLoc = FW_OUT_LOC_V6;
#endif
        else
            stateNewRuleLoc = loc; /*forward location */

        if ((*mtod(pMblk,UINT8 *) & IPV6_VERSION_MASK) == IPV4_VERSION)
            {
            inet_ntoa_b(pIp->ip_dst,srcAddrStr);
            if (dstAddrStr[0] == 0)
                {
                /* address not in command string, simply use the packet's src*/

                inet_ntoa_b(pIp->ip_src,dstAddrStr);
                }
            }
#ifdef INET6
        else  /* IPV6 */
            {
            inet_ntop (AF_INET6, (void *)pIpv6->ip6_dst.s6_addr8,
                       srcAddrStr,INET6_ADDRSTRLEN);
            if (dstAddrStr[0] == 0)
                {
                inet_ntop (AF_INET6, (void *)pIpv6->ip6_src.s6_addr8,
                       dstAddrStr,INET6_ADDRSTRLEN);
                }
            }
#endif
        
        dataChannelRuleCreate(pEntry, srcAddrStr, 0, dstAddrStr, port, 
			      stateNewRuleLoc, usrCtxt);
        }
    if (pTemp != NULL)
        free(pTemp);

    return retCode;
    }

/******************************************************************************
*
* dataChannelRuleCreate - Create the firewall rules for the data channel
*
* This routine creates the two firewall rules with stateful inspection feature
* enabled for the FTP data channel. 
*
* RETURNS: OK if successfully created or FALSE
*/

LOCAL int dataChannelRuleCreate
    (
    FW_RULE_ENTRY * pEntry,
    char * pRuleNewSrcIp,
    UINT16 ruleNewSrcPort,
    char * pRuleNewDstIp,
    UINT16 ruleNewDstPort,
    int stateNewRuleLocType,
    void * usrCtxt   /* upper 16 bit for outbound, lower 16 bit for inbound */
    )
    {
    FW_EVENT_FUNC_ENTRY pEvtFuncTbl[3];
    REMOVE_LIST_NODE_PAIR * pPair;
    int len;
    FW_RULE_IF * pRuleNewSrcIf = &pEntry->dstIf;
    FW_RULE_IF * pRuleNewDstIf = &pEntry->srcIf;
    FW_SERVICE_DESC service;
    void * groupId;
    UINT32 insertInx;
    GTF_TIMER * timerId;

    DBG_PRINT(("dataChannelRuleCreate: starting for new %s\n", locStr[stateNewRuleLocType]));

    DBG_PRINT(("dataChannelRuleCreate: pRuleNewSrcIp %s ruleNewSrcPort %d\n",
               pRuleNewSrcIp == NULL? "N/A": pRuleNewSrcIp, ruleNewSrcPort));
    
    DBG_PRINT(("dataChannelRuleCreate: pRuleNewDstIp %s ruleNewDstPort %d\n",
              pRuleNewDstIp == NULL ? "N/A" : pRuleNewDstIp, ruleNewDstPort));

    /* create a rule group to allow data channel */

    groupId =fwRuleGroupCreate(stateNewRuleLocType,
         "FtpClientDataChannel", 0);

    if ((stateNewRuleLocType == FW_OUT_LOC) 
#ifdef INET6
        || (stateNewRuleLocType == FW_OUT_LOC_V6)
#endif
        )
        insertInx = ((UINT32)usrCtxt) >> 16;
    else
        insertInx = ((UINT32)usrCtxt) & 0xffff;

    if (insertInx)
        {
        void * preGrpId;

        preGrpId = fwRuleGroupIdGet(stateNewRuleLocType,insertInx);
        if (preGrpId) 
	    fwRuleGroupMove(groupId, preGrpId, FW_MOVE_AFTER);
        else
	    fwRuleGroupMove(groupId, NULL, FW_MOVE_TO_TAIL);
        }         
    else
        fwRuleGroupMove(groupId,NULL,FW_MOVE_TO_HEAD);

    if ((stateNewRuleLocType == FW_FORW_LOC)
#ifdef INET6
        || (stateNewRuleLocType == FW_FORW_LOC_V6)
#endif
                                             )
        {
        fwRuleFieldSet(groupId, FW_FIELD_NETIF,
                                (UINT32)pRuleNewSrcIf->name,
                                pRuleNewSrcIf->unit,
                                (UINT32)pRuleNewDstIf->name,
                                pRuleNewDstIf->unit);
                                
        }

    fwRuleFieldSet(groupId, FW_FIELD_IPADDRSTR, (UINT32)pRuleNewSrcIp,
                   pRuleNewSrcIp, pRuleNewDstIp,
                   pRuleNewDstIp);
    fwRuleFieldSet(groupId, FW_FIELD_TCP,ruleNewSrcPort,
                   ruleNewSrcPort,ruleNewDstPort,ruleNewDstPort,
                   0,0,0);
    fwRuleFieldSet(groupId,FW_FIELD_ACTION,FW_ACCEPT);

    /* need to track the state, so created rule entries can be deleted
     * at the end of the data transfer.
     */
  
    
    fwRuleFieldSet(groupId,FW_FIELD_STATE,FW_CONN_INITIATOR,
                   FW_CONN_STATE_ALL);

    len = sizeof(REMOVE_LIST_NODE_PAIR)  * 2;

    if ((pPair = malloc(len)) == NULL)
        return ERROR;

    bzero((char *)pEvtFuncTbl,sizeof(pEvtFuncTbl[0]) * 3);

    pEvtFuncTbl[0].state = TCP_END_STATE;
    pEvtFuncTbl[0].pEventFunc = (FW_EVT_FUNC_PTR)ftpEventEndFunc;
    pEvtFuncTbl[0].eventFuncArg   = (void *)pPair;

    /* 
     * Register a timeout function. So if we don't receive a start event, i.e.
     * a SYN packet for this data channel, in specified time, we consider
     * the remote site has closed and delete the created data channel.
     * In normal case, the FTP start event function will delete the registered
     * function when receiving the start event.
     */

    /* start a 30 second timer */

    timerId = gtfTimerCreate(gtfTimerManagerCB);    

    if (timerId == NULL || gtfTimerInit (gtfTimerManagerCB, timerId, 
              FW_FTP_DC_TIMEOUT * sysClkRateGet(),
              (GTF_TIMER_HANDLER)ftpDataChannelDelete, pPair) != 0)
        printf("fail to create a timer for FTP ext module\n");
    else
        gtfTimerStart(gtfTimerManagerCB,timerId);

    pEvtFuncTbl[1].state = TCP_SYN_SENT;
    pEvtFuncTbl[1].pEventFunc = (FW_EVT_FUNC_PTR)ftpEventStartFunc;
    pEvtFuncTbl[1].eventFuncArg   = (void *)timerId;

    pPair->loc = stateNewRuleLocType;
    pPair->groupId = groupId;

    service.proto = 0;
    fwExtHandlerInstall (pPair->groupId, &service,NULL,
                       NULL,pEvtFuncTbl);

    pPair++;

    /* create a rule group to allow it out */

    if (stateNewRuleLocType == FW_IN_LOC || stateNewRuleLocType == FW_PREIN_LOC)
        stateNewRuleLocType = FW_OUT_LOC;
    else if (stateNewRuleLocType == FW_OUT_LOC)
        stateNewRuleLocType = FW_PREIN_LOC;
#ifdef INET6
    else if (stateNewRuleLocType == FW_IN_LOC_V6 ||
             stateNewRuleLocType == FW_PREIN_LOC_V6)
        stateNewRuleLocType = FW_OUT_LOC_V6;
    else if (stateNewRuleLocType == FW_OUT_LOC_V6)
        stateNewRuleLocType = FW_PREIN_LOC_V6;
#endif

    groupId = fwRuleGroupCreate(stateNewRuleLocType,
         "FtpClientDataChannel", 0);

    if ((stateNewRuleLocType == FW_OUT_LOC)
#ifdef INET6
        || (stateNewRuleLocType == FW_OUT_LOC_V6)
#endif
        )
        insertInx = ((UINT32)usrCtxt) >> 16;
     else
        insertInx = ((UINT32)usrCtxt) & 0xffff;

    if (insertInx)
        {
        void * preGrpId;

        preGrpId = fwRuleGroupIdGet(stateNewRuleLocType,insertInx);
        if (preGrpId) 
	    fwRuleGroupMove(groupId, preGrpId, FW_MOVE_AFTER);
        else
	    fwRuleGroupMove(groupId, NULL, FW_MOVE_TO_TAIL);
        }         
    else
        fwRuleGroupMove(groupId,NULL,FW_MOVE_TO_HEAD);


    if ((stateNewRuleLocType == FW_FORW_LOC)
#ifdef INET6
        || (stateNewRuleLocType == FW_FORW_LOC_V6)
#endif
                                            )
        {
        fwRuleFieldSet(groupId, FW_FIELD_NETIF,
                                (UINT32)pRuleNewDstIf->name,
                                pRuleNewDstIf->unit,
                                (UINT32)pRuleNewSrcIf->name,
                                pRuleNewSrcIf->unit);
        }

    fwRuleFieldSet(groupId, FW_FIELD_IPADDRSTR, (UINT32)pRuleNewDstIp,
                   pRuleNewDstIp, pRuleNewSrcIp, pRuleNewSrcIp);
    fwRuleFieldSet(groupId, FW_FIELD_TCP,(UINT32)ruleNewDstPort,
                   ruleNewDstPort,ruleNewSrcPort,ruleNewSrcPort,
                   0,0,0);
    fwRuleFieldSet(groupId,FW_FIELD_STATE,FW_CONN_RESPONDER,
                   FW_CONN_STATE_ESTABLISHED);
    fwRuleFieldSet(groupId,FW_FIELD_ACTION,FW_ACCEPT);

    pPair->loc = stateNewRuleLocType;
    pPair->groupId = groupId;

    return OK;
    }

/******************************************************************************
*
* ftpEventStartFunc - start(SYN_SENT) state event function
*
* This routine is installed as the state event function. It is invoked when
* the FTP data channel connection is first start. This function
* will remove the timeout function registered in the case the data channel
* is not started.  
*
*/

LOCAL void ftpEventStartFunc
    (
    void * timerId
    )
    {
    DBG_PRINT(("ftp event start function called\n"));
    gtfTimerDelete(gtfTimerManagerCB,(GTF_TIMER *)timerId);
    }

/******************************************************************************
*
* ftpEventEndFunc - End state event function 
*
* This routine is installed as the state event function. It is invoked when
* the FTP data channel connection transits to the END state. This function 
* will remove both the firewall rules created for the data channel.
*
*/

LOCAL void ftpEventEndFunc
    (
    REMOVE_LIST_NODE_PAIR * pPair
    )
    {
    void * timerId;

    /* this function is executed in the context of ruleFilter engine, we can't
     * delete the rules inside the rule checking loop. So defer the action
     * to a external context. Simply use the GTF timer for convience
     */

    timerId = gtfTimerCreate(gtfTimerManagerCB);

    if (timerId == NULL || gtfTimerInit (gtfTimerManagerCB, timerId, 1,
              (GTF_TIMER_HANDLER)ftpDataChannelDelete, pPair) != 0)
        {
        printf("ftpEventEndFunc: fail to create a timer\n");
        return;
        }
    else
        gtfTimerStart(gtfTimerManagerCB,timerId);
    }

/*******************************************************************************
*
*  ftpDataChannelDelete - Delete the FTP data channel
*
*/
LOCAL void ftpDataChannelDelete
    (
    void * arg
    )
    {
    REMOVE_LIST_NODE_PAIR * pPair = (REMOVE_LIST_NODE_PAIR *)arg;

    /* remove the system rule entries for the FTP data channel when the
     * channel is closed
     */

    DBG_PRINT(("ftp event end function called\n"));

    fwRuleGroupDelete(pPair->loc,pPair->groupId);
    fwRuleGroupDelete(pPair[1].loc,pPair[1].groupId);
    free(pPair);
    }

/*******************************************************************************
*
* cmdStrParse - Parse the FTP extended commands EPRT and EPSV response
*
*/
LOCAL BOOL cmdStrParse
    (
    char * pCmdStr,
    char * pDelimit,
    int  * pAfNum,
    int  * pPort,
    char * pIpAddr
    )
    {
    char * pTok;
    char * pHolder;

    /* the EPRT command has this format: |<afNum>|<address>|<port>| 
     * the EPSV response usually has: |||<port>|
     */
    *pAfNum    = 0;
    pIpAddr[0] = 0; 
    *pPort     = 0;
    pTok = strtok_r(pCmdStr,pDelimit,&pHolder);
    if (pTok)
        {
        /* check if have EPSV response with format |||<port>| */

        if ((pCmdStr[0] == pDelimit[0]) && (pCmdStr[1] == pDelimit[0]))
            {
            *pPort = atoi(pTok);
            return OK;
            }
        else
            {
            *pAfNum = atoi(pTok);

            pTok = strtok_r(NULL,pDelimit,&pHolder);

            if (pTok)
                {
                strcpy(pIpAddr,pTok);
                pTok = strtok_r(NULL,pDelimit,&pHolder);
                if (pTok)
                    {
                    *pPort = atoi(pTok);
                    return OK;
                    }
                }
            }
        }
    return ERROR;
    }