/*
This source file is part of Scol
For the latest info, see http://www.scolring.org

Copyright (c) 2011 Stephane Bisaro, aka Iri <iri@irizone.net>

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU Lesser General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.

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 Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 59 Temple
Place - Suite 330, Boston, MA 02111-1307, USA, or go to
http://www.gnu.org/copyleft/lesser.txt

For others informations, please contact us from http://www.scolring.org/
*/

#include "main.h"

static void netftp_reply_free (NetFtpObject o)
{
    int i;

    for (i = 0; i < o->sizereply; i++)
    {
        free (o->reply[i]);
        o->reply[i] = NULL;
    }
    o->sizereply = 0;
    return;
}

static void net_ftp_ipport (char *buffer, NetFtpObject o)
{
    char *a = NULL;
    char b[5];
    char ip[16];
    int p = 0;
    int segments = 0, i = 0;

    ip[0] = '\0';
    a = 1+strchr (buffer, '(');
    if (a == NULL)
        return;
    p = searchCharMem (a, ')');
    if (p == -1)
        return;

    p = searchCharMem (a, ',');
    if (p != -1)  /* PASV / IPv4 : xxx,xxx,xxx,xxx,port1,port2 */
    {
        for (i = 0; (i < 16) && (segments < 4); i++) /* IPv4 address */
        {
            if ((*a == ',') && (segments < 3))
            {
                ip[i] = '.';
                segments ++;
            }
            else if (!isdigit(*a))
            {
                ip[i] = '\0';
                break;
            }
            else
                ip[i] = *a;
            a++;
        }

        a = strchr (a, ',');
        p = searchChar (a+1, ',');
        strncpy (b, a+1, p);
        o->portdatas = 256 * atoi (b);
        a = strchr (a+p+1, ',');
        p = searchChar (a, ')');
        strncpy (b, a+1, p);
        o->portdatas += atoi (b);

        o->domaindatas = malloc (sizeof (char) * 17);
        strncpy (o->domaindatas, ip, 16);
        o->domaindatas[16] = '\0';

        return;
    }
    /* EPSV / IPv6 */
    p = searchCharMem (a, '|');

}



static int netftp_conn_srv (NetFtpObject o)
{
    netftp_reply_free (o);

    o->code = net_get_XYZ (o->sockCmd, o, &(o->reply[o->sizereply]));
    o->sizereply += 1;
    if (o->code != 220)
    {
        MMechostr (MSKDEBUG, "netftp_conn_srv error : bad response : %d - %s on socket %d\n", o->code, o->reply[0], o->sockCmd);
        return 1;
    }
    /* send : USER */
    snprintf (o->lastCmd, NETFTP_MAXBUFFER-1, "USER %s", o->username);
    if (0 != net_connection_write (o->sockCmd, o->lastCmd))
    {
        MMechostr (MSKDEBUG, "netftp_conn_srv error : send user name failed\n");
        return NETFTP_FAILSEND;
    }
    o->code = net_get_XYZ (o->sockCmd, o, &(o->reply[o->sizereply]));
    o->sizereply += 1;

    if (o->code != 331)
    {
        MMechostr (MSKDEBUG, "netftp_conn_srv error : bad user response : %d - %s\n", o->code, o->reply[o->sizereply-1]);
        return NETFTP_FAILUSER;
    }

    /* send PASS */
    snprintf (o->lastCmd, NETFTP_MAXBUFFER-1, "PASS %s", o->password);
    if (0 != net_connection_write (o->sockCmd, o->lastCmd))
    {
        MMechostr (MSKDEBUG, "netftp_conn_srv error : send user name failed\n");
        return NETFTP_FAILSEND;
    }
    o->code = net_get_XYZ (o->sockCmd, o, &(o->reply[o->sizereply]));
    o->sizereply += 1;
    if (o->code != 230)
    {
        MMechostr (MSKDEBUG, "netftp_conn_srv error : bad pass response : %d - %s\n", o->code, o->reply[o->sizereply-1]);
        return NETFTP_FAILPASSWORD;
    }

    return NETFTP_RESPONSEOK;
}



static int netftp_conn_create (NetFtpObject ftp, int flag)
{
    SOCKET sockC = 0;

    if (flag == 0)
    {
        net_get_host (ftp->domain, &(ftp->ipsrv));
        sockC = net_connection_socket (ftp->port, ftp->ipsrv);
    }
    else
    {
        net_get_host (ftp->domaindatas, &(ftp->ipsrvdatas));
        MMechostr (0, "netftp_conn_create : %d   %s\n", ftp->portdatas, ftp->domaindatas);
        if (ftp->flag == NETFTP_PASSIVEMODE)
            sockC = net_connection_socket (ftp->portdatas, ftp->domaindatas);
        else
            0;
    }
    if ((int) sockC < 0)
    {
        MMechostr (MSKDEBUG, "netftp_conn_create error : socket not connected : %d\n", sockC);
        return 1;
    }
    if (flag == 0)
        ftp->sockCmd = sockC;
    else
    {
        if (ftp->flag == NETFTP_PASSIVEMODE)
            ftp->sockDatas = sockC;
        else
            0;
    }
    if (flag == 0)
    {
        if (netftp_conn_srv (ftp) != NETFTP_RESPONSEOK)
            return 1;
        ftp->active = 1;
    }

    return 0;
}


static void netftp_call_cb_receive (mmachine m, NetFtpObject o)
{
    netftp_reply_free (o);

    o->reply[o->sizereply] = malloc (NETFTP_MAXBUFFER + 1);
    o->code = net_get_XYZ (o->sockCmd, o, &(o->reply[o->sizereply]));
    o->sizereply += 1;

    /* scol */
    if (OBJbeginreflex (m, ObjFtp, (int) o, NET_FTP_CB_RECEIVE))
    {
        MMechostr (MSKDEBUG, "netftp_call_cb_receive error : no reflex defined to the command ' %s '\n", o->lastCmd);
        MMpush (m, NIL);
        return;
    }

    MMpush (m, ITOM (NETFTP_RESPONSE_CMD));
    MMpush (m, ITOM (o->code));
    Mpushstrbloc (m, o->reply[0]);
    Mpushstrbloc (m, o->lastCmd);
    OBJcallreflex (m, 4);
}





/* PUBLIC */


int NETFTP_New (mmachine m)
{
    int mchannel, mdomain, mport, muser, mpassword, mflag;
    int len, objtab;
    NetFtpObject o;

    MMechostr (MSKDEBUG, "NETFTP_New : entering\n");

    mflag = MMpull (m);
    mpassword = MMpull (m);
    muser = MMpull (m);
    mport = MMpull (m);
    mdomain = MMpull (m);
    mchannel = MMget (m, 0);

    if (mchannel == NIL)
    {
        MMechostr (0, "NETFTP_New error : channel is nil\n");
        MMpull (m);
        MMpush (m, NIL);
        return 0;
    }
    if (mdomain == NIL)
    {
        MMechostr (0, "NETFTP_New error : domain is nil\n");
        MMpull (m);
        MMpush (m, NIL);
        return 0;
    }
    o = malloc (sizeof (struct NetFtpObject));
    if (o == NULL)
    {
        MMechostr (0, "NETFTP_New error : memory failed\n");
        MMpull (m);
        MMpush (m, NIL);
        return 0;
    }
    /* internal */
    if (muser != NIL)
        o->username = MMstartstr (m, MTOP (muser));
    else
        o->username = "anonymous";
    if (mpassword != NIL)
        o->password = MMstartstr (m, MTOP (mpassword));
    else
        o->password = "scolEngine";
    if (mport != NIL)
        o->port = MTOI (mport);
    else
        o->port = 21;
    o->domain = MMstartstr (m, MTOP (mdomain));
    o->flag = 0;
    o->active = 0;
    o->sizereply = 0;
    o->lastCmd = malloc (sizeof (char) * NETFTP_MAXBUFFER);
    if (netftp_conn_create (o, 0))
    {
        MMechostr (MSKDEBUG, "NETFTP_New : connexion refuse !\n");
	    MMpull (m);
	    MMpush (m, NIL);
	    return 0;
    }
    /* scol */
    len = (sizeof (struct NetFtpObject) + 3) >> 2;
    objtab = MMmalloc (m, len, TYPETAB);
    if (objtab == NIL)
	{
	    MMechostr (MSKDEBUG, "NETFTP_New : Scol memory allocation failed !\n");
	    MMpull (m);
	    MMpush (m, NIL);
	    return 0;
	}
	MMstore (m, objtab, NET_FTP_HANDLE, (int) o);
    MMpush (m, PTOM (objtab));

    return OBJcreate (m, ObjFtp, (int) o, -1, -1);
}

/*
    Callback : fun [ObjFtp fun [ObjFtp u0 I I S S] u1 u0] ObjFtp
    - Ftp object
    - user parameter
    - flag : NETFTP_RESPONSE_CMD or NETFTP_RESPONSE_DATAS
    - code (xyz) returned by the server
    - full raw response
    - command (if any)
*/
int NETFTP_cbReceive (mmachine m)
{
    int mobj;

    MMechostr (MSKDEBUG, "NETFTP_cbReceive : entering\n");

    mobj = MMget (m, 2);
    if (OBJTESTOK (mobj))
    {
        MMechostr (MSKDEBUG, "NETFTP_cbReceive error : object is nil\n");
        MMpush (m, NIL);
        return 0;
    }

    OBJaddreflex (m, ObjFtp, NET_FTP_CB_RECEIVE);
    return 0;
}

int NETFTP_sendCmd (mmachine m)
{
    int mobj, mcmd;
    NetFtpObject o;

    MMechostr (MSKDEBUG, "NETFTP_sendCmd : entering\n");

    mcmd = MMpull (m);
    mobj = MMpull (m);

    if (OBJTESTOK (mobj))
    {
        MMechostr (MSKDEBUG, "NETFTP_sendCmd error : object is nil\n");
        MMpush (m, NIL);
        return 0;
    }

    o = (NetFtpObject) MMfetch (m, MTOP (mobj), NET_FTP_HANDLE);
    /* internal */
    if (o->active == 0)
    {
        MMechostr (MSKDEBUG, "NETFTP_sendCmd error : connection is not active\n");
        MMpush (m, NIL);
        return 0;
    }

    if (mcmd == NIL)
        o->lastCmd = "NOOP";
    else
        strncpy (o->lastCmd, MMstartstr (m, MTOP (mcmd)), NETFTP_MAXBUFFER-1);

    if (net_connection_write (o->sockCmd, o->lastCmd) < 0)
    {
        MMechostr (MSKDEBUG, "NETFTP_sendCmd error : unable to send the command ' %s '\n", o->lastCmd);
        MMpush (m, NIL);
        return 0;
    }
    netftp_call_cb_receive (m, o);

    return 0;
}

int NETFTP_close (mmachine m)
{
    int mobj;
    NetFtpObject o;

    MMechostr (MSKDEBUG, "NETFTP_close : entering\n");

    mobj = MMpull (m);
    if (OBJTESTOK (mobj))
    {
        MMechostr (MSKDEBUG, "NETFTP_close error : object is nil\n");
        MMpush (m, NIL);
        return 0;
    }

    o = (NetFtpObject) MMfetch (m, MTOP (mobj), NET_FTP_HANDLE);

    strncpy (o->lastCmd, "REIN", NETFTP_MAXBUFFER-1);
    if (net_connection_write (o->sockCmd, o->lastCmd) < 0)
    {
        MMechostr (MSKDEBUG, "NETFTP_close error : unable to send the command ' %s '\n", o->lastCmd);
        MMpush (m, NIL);
        return 0;
    }
    netftp_call_cb_receive (m, o);

    MMpush (m, 0);
    return 0;
}

int NETFTP_open (mmachine m)
{
    int mobj;
    NetFtpObject o;

    MMechostr (MSKDEBUG, "NETFTP_open : entering\n");

    mobj = MMpull (m);
    if (OBJTESTOK (mobj))
    {
        MMechostr (MSKDEBUG, "NETFTP_open error : object is nil\n");
        MMpush (m, NIL);
        return 0;
    }

    o = (NetFtpObject) MMfetch (m, MTOP (mobj), NET_FTP_HANDLE);

    if (netftp_conn_create (o, 0))
    {
        MMechostr (MSKDEBUG, "NETFTP_open : connexion refuse !\n");
	    MMpush (m, NIL);
	    return 0;
    }
    MMpush (m, mobj);
    return 0;
}


int NETFTP_destroy (mmachine m)
{
    int mobj;
    NetFtpObject o;

    MMechostr (MSKDEBUG, "NETFTP_destroy : entering\n");

    mobj = MMget (m, 0);
    if (OBJTESTOK (mobj))
    {
        MMechostr (MSKDEBUG, "NETFTP_destroy error : object is nil\n");
        MMpush (m, NIL);
        return 0;
    }
    o = (NetFtpObject) MMfetch (m, MTOP (mobj), NET_FTP_HANDLE);

    strncpy (o->lastCmd, "QUIT", NETFTP_MAXBUFFER-1);
    if (net_connection_write (o->sockCmd, o->lastCmd) < 0)
    {
        MMechostr (MSKDEBUG, "NETFTP_destroy error : unable to send the command ' %s '\n", o->lastCmd);
        MMset (m, 0, NIL);
        return 0;
    }
    netftp_call_cb_receive (m, o);

    OBJdelTH (m, ObjFtp, (int) o);
    MMset (m, 0, 0);
    return 0;
}


int NETFTP_getLastResponse (mmachine m)
{
    int mobj;
    int i = 0;
    NetFtpObject o;

    MMechostr (MSKDEBUG, "NETFTP_getLastResponse : entering\n");

    mobj = MMpull (m);

    if (OBJTESTOK (mobj))
    {
        MMechostr (MSKDEBUG, "NETFTP_getLastResponse error : object is nil\n");
        MMpush (m, NIL);
        return 0;
    }

    o = (NetFtpObject) MMfetch (m, MTOP (mobj), NET_FTP_HANDLE);

    for (i = 0; i < o->sizereply; i++)
        Mpushstrbloc (m, o->reply[i]);
    MMpush (m, NIL);
    for (i = 0; i < o->sizereply; i++)
    {
        MMpush (m, ITOM (2));
        MBdeftab (m);
    }
    netftp_reply_free (o);
    return 0;
}

int NETFTP_getPassiveParam (mmachine m)
{
    int mobj, mbuffer;
    NetFtpObject o;

    MMechostr (MSKDEBUG, "NETFTP_getPassiveParam : entering\n");

    mbuffer = MMpull (m);
    mobj = MMpull (m);

    if (OBJTESTOK (mobj))
    {
        MMechostr (MSKDEBUG, "NETFTP_getPassiveParam error : object is nil\n");
        MMpush (m, NIL);
        return 0;
    }
    if (OBJTESTOK (mbuffer))
    {
        MMechostr (MSKDEBUG, "NETFTP_getPassiveParam error : buffer is nil\n");
        MMpush (m, NIL);
        return 0;
    }

    o = (NetFtpObject) MMfetch (m, MTOP (mobj), NET_FTP_HANDLE);
    net_ftp_ipport (MMstartstr (m, MTOP (mbuffer)), o);

    Mpushstrbloc (m, o->domaindatas);
    MMpush (m, o->portdatas);
    MMpush (m, ITOM (2));
    MBdeftab (m);
    return 0;
}

int NETFTP_connDatas (mmachine m)
{
    int mobj, mflag;
    NetFtpObject o;

    MMechostr (MSKDEBUG, "NETFTP_connDatas : entering\n");

    mflag = MMpull (m);
    mobj = MMpull (m);

    if (OBJTESTOK (mobj))
    {
        MMechostr (MSKDEBUG, "NETFTP_connDatas error : object is nil\n");
        MMpush (m, NIL);
        return 0;
    }

    o = (NetFtpObject) MMfetch (m, MTOP (mobj), NET_FTP_HANDLE);
    o->flag = NETFTP_PASSIVEMODE;
    if (MTOI (mflag) == NETFTP_ACTIVEMODE)
        o->flag = NETFTP_ACTIVEMODE;

    if (netftp_conn_create (o, 1))
    {
        MMechostr (MSKDEBUG, "NETFTP_connDatas : connexion refusee !\n");
	    MMpull (m);
	    MMpush (m, NIL);
	    return 0;
    }
    MMpush (m, mobj);
    return 0;
}

int NETFTP_getFile (mmachine m)
{
    int mobj, mfilename;
    NetFtpObject o;

    MMechostr (MSKDEBUG, "NETFTP_getFile : entering\n");

    mfilename = MMpull (m);
    mobj = MMpull (m);

    if (OBJTESTOK (mobj))
    {
        MMechostr (MSKDEBUG, "NETFTP_getFile error : object is nil\n");
        MMpush (m, NIL);
        return 0;
    }

    o = (NetFtpObject) MMfetch (m, MTOP (mobj), NET_FTP_HANDLE);

}











char* net_ftp_name[NET_FTP_PKG_NB]=
{
    "ObjFtp",

    "NETFTP_RESPONSE_CMD", "NETFTP_RESPONSE_DATAS",
    "NETFTP_RESPONSEOK", "NETFTP_FAILSEND",
    "NETFTP_FAILCONNEXION", "NETFTP_FAILUSER",
    "NETFTP_FAILPASSWORD",

    "NETFTP_PASSIVEMODE", "NETFTP_ACTIVEMODE",

    "_ftpNew",
    "_ftpCBreceive",
    "_ftpCmdSend",
    "_ftpCmdLastResponses",
    "_ftpClose",
    "_ftpOpen",
    "_ftpDestroy",
    "_ftpPassiveGetParam",
    "_ftpOpenDatas"
};

int (*net_ftp_fun[NET_FTP_PKG_NB])(mmachine m)=
{
    NULL,

    SCTYPVAR (NETFTP_RESPONSE_CMD), SCTYPVAR (NETFTP_RESPONSE_DATAS),
    SCTYPVAR (NETFTP_RESPONSEOK), SCTYPVAR (NETFTP_FAILSEND),
    SCTYPVAR (NETFTP_FAILCONNEXION), SCTYPVAR (NETFTP_FAILUSER),
    SCTYPVAR (NETFTP_FAILPASSWORD),

    SCTYPVAR (NETFTP_PASSIVEMODE), SCTYPVAR (NETFTP_ACTIVEMODE),

    NETFTP_New,
    NETFTP_cbReceive,
    NETFTP_sendCmd,
    NETFTP_getLastResponse,
    NETFTP_close,
    NETFTP_open,
    NETFTP_destroy,
    NETFTP_getPassiveParam,
    NETFTP_connDatas
};

int net_ftp_narg[NET_FTP_PKG_NB]=
{
    TYPTYPE,

    TYPVAR, TYPVAR,
    TYPVAR, TYPVAR,
    TYPVAR, TYPVAR,
    TYPVAR,

    TYPVAR, TYPVAR,

    6,           /* _ftpNew */
    3,           /* _ftpCBreceive */
    2,           /* _ftpCmdSend */
    1,           /* _ftpCmdLastResponses */
    1,           /* _ftpClose */
    1,           /* _ftpOpen */
    1,           /* _ftpDestroy */
    2,           /* _ftpPassiveGetParam */
    2           /* _ftpOpenDatas */
};

char* net_ftp_type[NET_FTP_PKG_NB]=
{
    NULL,

    "I", "I",
    "I", "I",
    "I", "I",
    "I",

    "I", "I",

    "fun [Chn S I S S I] ObjFtp",                               /* _ftpNew */
    "fun [ObjFtp fun [ObjFtp u0 I I S S] u1 u0] ObjFtp",         /* _ftpCBreceive */
    "fun [ObjFtp S] ObjFtp",                                     /* _ftpCmdSend */
    "fun [ObjFtp] [S r1]",                                       /* _ftpCmdLastResponses */
    "fun [ObjFtp] I",                                            /* _ftpClose */
    "fun [ObjFtp] ObjFtp",                                       /* _ftpOpen */
    "fun [ObjFtp] I",                                            /* _ftpDestroy */
    "fun [ObjFtp S] [S I]",                                      /* _ftpPassiveGetParam */
    "fun [ObjFtp I] ObjFtp"                                     /* _ftpOpenDatas */
};


int ObjFtpTypeDestroy (mmachine m, int handsys, int mobj)
{
    NetFtpObject o;

    MMechostr(MSKDEBUG, "ObjFtpTypeDestroy: entering\n");

    o = (NetFtpObject) MMfetch (m, MTOP (mobj), NET_FTP_HANDLE);
    if ((int) o == 0)
    {
        MMechostr(MSKDEBUG, "ObjFtpTypeDestroy : object already destroyed\n");
        MMset (m, 0, NIL);
        return 0;
    }

    strncpy (o->lastCmd, "QUIT", NETFTP_MAXBUFFER-1);
    net_connection_write (o->sockCmd, o->lastCmd);
    net_connection_end (o->sockCmd);
    net_connection_end (o->sockDatas);
    netftp_reply_free (o);
    SAFEdelete (o);
    MMstore (m, MTOP (mobj), NET_FTP_HANDLE, 0);
    MMechostr(MSKDEBUG, "ObjFtpTypeDestroy: object has been destroyed\n");

    return 0;
}

int SCOLftpLoadAPI (mmachine m)
{
    int k;

    MMechostr(MSKDEBUG, "SCOLftpLoadAPI: entering\n");

    ObjFtp = OBJregister (NET_FTP_CB, 1, ObjFtpTypeDestroy, "ObjFtp");
    nbSocks = 0;

    k = PKhardpak (m, "Net_Ftp", NET_FTP_PKG_NB, net_ftp_name, net_ftp_fun, net_ftp_narg, net_ftp_type);
    return k;
}
