#include <all_far.h>


#include "Int.h"

BOOL Connection::Init(LPCSTR Host, LPCSTR Port, LPCSTR User, LPCSTR Password)
{
	FP_Screen _scr;
	PROC(("Connection::Init","[%s] [%s] [%s]",Host,User,Password))
	//_fmode        = _O_BINARY; // This causes an error somewhere.
	SocketError = INVALID_SOCKET;
	WINPORT(SetLastError)(ERROR_SUCCESS);
	/* Set up defaults for FTP. */
	type = TYPE_A;
	strcpy(bytename, "8"), bytesize = 8;
	cpend = 0;  /* no pending replies */
	proxy = 0;      /* proxy not active */
	ResetCmdBuff();
	char *argv[5];
	argv[0] = (char*)"open";
	argv[1] = (char*)Host;
	argv[2] = (char*)Port;
	argv[3] = (char*)User;
	argv[4] = (char*)Password;

	if(!setpeer(5, argv))
	{
		Log(("!setpeer"));
		return FALSE;
	}

	Log(("OK"));
	return TRUE;
}

//--------------------------------------------------------------------------------
BOOL Connection::hookup(char *host, int port)
{
	FP_Screen _scr;
	struct hostent *he;
	SOCKET   sock = INVALID_SOCKET;
	socklen_t      len = sizeof(myctladdr);
	Log(("Connecting: [%s]:%d",host,port));
	hostname[0] = 0;
	memset(&hisctladdr, 0, sizeof(hisctladdr));
	memset(&myctladdr,  0, sizeof(myctladdr));
	hisctladdr.sin_port = port;
	ConnectMessage(MResolving,host);
	hisctladdr.sin_addr.s_addr = inet_addr(host);

	do
	{
//Server addr
		if(hisctladdr.sin_addr.s_addr != INADDR_NONE)
		{
			hisctladdr.sin_family = AF_INET;
		}
		else if((he=gethostbyname(host)) != NULL)
		{
			memmove(&hisctladdr.sin_addr,he->h_addr,he->h_length);
			hisctladdr.sin_family = he->h_addrtype;
		}
		else {
			break;			
		}

//Sock
		if(!scValid(sock=scCreate(hisctladdr.sin_family))) break;

//Connect
		ConnectMessage(MWaitingForConnect,NULL);

		if(!nb_connect(&sock,(sockaddr*)&hisctladdr,sizeof(hisctladdr)))
			break;

//Local addr
	
		if(getsockname(sock,(sockaddr*)&myctladdr,&len) != 0)
			break;

//Read startup message from server
		cin = cout = sock;
		StrCpy(hostname, host, ARRAYSIZE(hostname));
		portnum = port;
		
		ConnectMessage(MWaitingForResponse,NULL);
		int repl = getreply(0);

		if(repl > RPL_COMPLETE || repl == RPL_ERROR) break;

//OK
		Log(("Connected: %d",sock));
		cmd_peer = sock;
		return TRUE;
	}
	while(0);

//ERROR
	Log(("!connect"));
	scClose(sock);
	cin      = 0;
	cout     = 0;
	code     = -1;
	cmd_peer = INVALID_SOCKET;
	portnum  = 0;
	return FALSE;
}

//--------------------------------------------------------------------------------
int Connection::login(void)
{
	//char *acct=NULL;
	//int   aflag = 0;
	int   n;
	ConnectMessage(MSendingName);
	n = command("%s %s", Opt.cmdUser, *UserName ? UserName : "anonymous");

	if(n == RPL_CONTINUE)
	{
		ConnectMessage(MPasswordName);
		n = command("%s %s",Opt.cmdPass, *UserPassword ? UserPassword : Opt.DefaultPassword);
	}

	if(n == RPL_CONTINUE)
	{
		//aflag++;
		//acct = "";
		n = command("%s %s",Opt.cmdAcct,""/*acct*/);
	}

	if(n != RPL_COMPLETE)
	{
		WINPORT(SetLastError)(EPERM);//ERROR_INTERNET_LOGIN_FAILURE);
		return 0;
	}

	LoginComplete = TRUE;
	/*
	  if (!aflag && acct != NULL)
	    command( "%s %s",Opt.cmdAcct,acct );
	*/
	return 1;
}

void Connection::CheckResume(void)
{
	int oldcode = code;

	if(Host.AsciiMode)
	{
		if(setascii() &&
		        command("%s 0",Opt.cmdRest) != RPL_ERROR)
		{
			if(code == 350)
				ResumeSupport = TRUE;
		}
	}
	else
	{
		if(setbinary() &&
		        command("%s 0",Opt.cmdRest) != RPL_ERROR)
		{
			if(code == 350)
				ResumeSupport = TRUE;
		}
	}

	code = oldcode;
}

//--------------------------------------------------------------------------------
SOCKET Connection::dataconn(void)
{
	PROC(("dataconn", NULL))
	struct sockaddr_in from;
	socklen_t                fromlen = sizeof(from);
	SOCKET             s;

	if(brk_flag)
		return INVALID_SOCKET;

	if(!Host.PassiveMode)
	{
		if(!nb_waitstate(&data_peer, ws_accept) ||
		        (s=scAccept(&data_peer, (struct sockaddr *) &from, &fromlen)) == INVALID_SOCKET)
		{
			scClose(data_peer,-1);
			return INVALID_SOCKET;
		}

		Log(("SOCK: accepted data %d -> %d",data_peer,s));
		close(data_peer);
		data_peer = s;
	}
	else
	{
		if(data_peer == INVALID_SOCKET)
		{
			InternalError();
			return INVALID_SOCKET;
		}

		if(brk_flag ||
		        !nb_connect(&data_peer,(sockaddr*)&data_addr,sizeof(data_addr)))
		{
			scClose(data_peer,-1);
			return INVALID_SOCKET;
		}
	}

	return data_peer;
}

//--------------------------------------------------------------------------------
/*
 * Need to start a listen on the data channel
 * before we send the command, otherwise the
 * server's connect may fail.
 */
BOOL Connection::initconn()
{
	int result, tmpno = 0;
	int on = 1;
	socklen_t len;
noport:
	data_addr = myctladdr;

	if(sendport)
		data_addr.sin_port = 0; /* let system pick one */

	scClose(data_peer,-1);

	if(brk_flag ||
	        !scValid(data_peer=socket(AF_INET,SOCK_STREAM,0)))
	{
		Log(("!socket"));

		if(tmpno)
			sendport = 1;

		ErrorCode = errno;
		return FALSE;
	}

	Log(("SOCK: created data %d",data_peer));
	/* $ VVM  Switch socket to nonblocking mode */
	SetSocketBlockingEnabled(data_peer, false);

	/* VVM $ */
	if(!sendport)
		if(setsockopt(data_peer, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) < 0)
		{
			Log(("!setsockopt (reuse address)"));
			ErrorCode = errno;
			goto bad;
		}

	if(!Host.PassiveMode || !sendport)
	{
		if(bind(data_peer, (struct sockaddr *)&data_addr, sizeof(data_addr)) < 0)
		{
			Log(("!bind"));
			ErrorCode = errno;
			goto bad;
		}
/*
		if(setsockopt(data_peer, SOL_SOCKET, SO_DEBUG, (char*)&on, sizeof(on)) < 0)
		{
			Log(("!setsockopt SO_DEBUG (ignored)"));
		}*/

		len = sizeof(data_addr);

		if(getsockname(data_peer, (struct sockaddr *)&data_addr, &len) < 0)
		{
			Log(("!getsockname"));
			ErrorCode = errno;
			goto bad;
		}

		if(listen(data_peer, 1) < 0)
		{
			Log(("!listen"));
			ErrorCode = errno;
			goto bad;
		}
	}

	if(sendport)
	{
		if(Host.PassiveMode)
			result = command(Opt.cmdPasv);
		else
			result =  command("%s %d,%d,%d,%d,%d,%d",
			                  Opt.cmdPort,
			                  data_addr.sin_addr.s_addr&0xff,
			                  (data_addr.sin_addr.s_addr >> 8) & 0xff,
			                  (data_addr.sin_addr.s_addr >> 16) & 0xff,
			                  (data_addr.sin_addr.s_addr >> 24) & 0xff,
			                  (unsigned char)data_addr.sin_port,
			                  (unsigned char)(data_addr.sin_port>>8));

		if(/*2*/ result == 0 && sendport == -1) //TODO: CHECK: result == ERROR
		{
			sendport = 0;
			tmpno = 1;
			goto noport;
		}

		if(result != RPL_COMPLETE)
		{
			Log(("!Complete"));
			goto bad;
		}

		if(Host.PassiveMode)
		{
			char LastReply2[100];  //Not need to use String - PORT string is small.
			char *p;
			GetReply((BYTE*)LastReply2, sizeof(LastReply2));

			if(code != 227 || (p=strchr(LastReply2,'('))==NULL)
			{
				Log(("!Pasv port reply"));
				return FALSE;
			}

			unsigned a1, a2, a3, a4, p1, p2;
#define TOP(a) (a&~0xFF)

			if(sscanf(p, "(%u,%u,%u,%u,%u,%u)", &a1, &a2, &a3, &a4, &p1, &p2) !=6 ||
			        TOP(a1) || TOP(a2) || TOP(a3) || TOP(a4) || TOP(p1) || TOP(p2))
			{
				Log(("Bad psv port reply"));
				return FALSE;
			}

			data_addr.sin_addr.s_addr = (a4<<24) | (a3<<16) | (a2<<8) | a1;
			data_addr.sin_port        = (p2<<8) | p1;

			if(!data_addr.sin_addr.s_addr || data_addr.sin_addr.s_addr == INADDR_ANY) return 1;

			if(data_addr.sin_addr.s_addr != hisctladdr.sin_addr.s_addr)
			{
				char Msg[30];
				snprintf(Msg, ARRAYSIZE(Msg),
				          "[%s -> %s]",
				          inet_ntoa(hisctladdr.sin_addr), inet_ntoa(data_addr.sin_addr));
				ConnectMessage(0,Msg,0);
			}
		}

		return TRUE;
	}

	if(tmpno)
		sendport = 1;

	return TRUE;
bad:
	scClose(data_peer,-1);

	if(tmpno)
		sendport = 1;

	return FALSE;
}
