/* scon.c
 * Copyright (c) 1996 by Donald C. Asonye
 * Email: donald@uh.edu
 */
#include "scon.h"

SOCKET socCtrl= INVALID_SOCKET;
int bdebug= 0;
char reply[512];

static SOCKET PassiveConnect(){
	char buf[256];
	SOCKET sdata= INVALID_SOCKET;

	if(227==send_command_fp("PASV\r\n", 0, 0)){
		// Entering passive mode
		char *cp= strchr(reply, '(');
		if(cp){
			int inos[6];
			unsigned short port;
			int n= sscanf(cp, "(%d,%d,%d,%d,%d,%d)",
				&inos[0],&inos[1],&inos[2],&inos[3],&inos[4],&inos[5]);
			sprintf(buf, "%d.%d.%d.%d", inos[0],inos[1],inos[2],inos[3]);
			port= 256*inos[4]+inos[5];
			if(bdebug)
				printf("...passive from %s:%d\n",buf,port);
			sdata= ConnectToServer(buf, port);
		}
	}
	if(sdata==INVALID_SOCKET)
		printf("Passive Connect failed\n");
	return sdata;
}
static SOCKET GetListenSocket(){
	char buf[256];
	SOCKET slisten;
	struct sockaddr_in  serv_addr, TempAddr;
	int len1= sizeof(serv_addr);
	int len2= sizeof(TempAddr);
	unsigned char *pport,*paddr;

	/* Open a TCP socket */
	if ((slisten= socket(AF_INET, SOCK_STREAM, 0)) < 0){
		erreport("socket");
		return INVALID_SOCKET;
	}
	memset((char *) &serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family= AF_INET;
	serv_addr.sin_addr.s_addr= htonl(INADDR_ANY);
	serv_addr.sin_port= htons(0); /* let system choose */

	/* bind the address to the socket */
	if(bind(slisten,(struct sockaddr *)&serv_addr, sizeof(serv_addr)) <0
	|| getsockname(slisten,(struct sockaddr *)&serv_addr,&len1)<0
	|| getsockname(socCtrl,(struct sockaddr *)&TempAddr,&len2)<0){
		erreport("bind or getsockname");
		closesocket(slisten);
		return INVALID_SOCKET;
	}
	paddr= (unsigned char *)&TempAddr.sin_addr;	// My IP
	pport = (unsigned char *)&serv_addr.sin_port;	// Bound port
	sprintf(buf,"PORT %d,%d,%d,%d,%d,%d\r\n",
		  paddr[0],paddr[1],paddr[2],paddr[3],pport[0],pport[1]);

	/* allow ftp server to connect--allow only one server */
	if( listen(slisten, 1) < 0){
		erreport("listen");
		closesocket(slisten);
		return INVALID_SOCKET;
	}
	if(400<send_command_fp(buf, 0, 0))
		SocClose(&slisten);
	return slisten;
}
SOCKET AcceptConnection(SOCKET slisten){
	struct sockaddr_in cli_addr;
	int clilen= sizeof(cli_addr);
	SOCKET sdata;

	if(bpasv)
		return slisten;
	sdata= accept(slisten, (struct sockaddr *) &cli_addr, &clilen);
	closesocket(slisten);
	if (sdata==INVALID_SOCKET)
		erreport("accept");
	return sdata;
}
SOCKET DataConnect(){
	return bpasv? PassiveConnect() : GetListenSocket();
}
SOCKET ConnectToServer(char *name, unsigned short portnum){
	SOCKET s;
	struct sockaddr_in server;
	struct hostent *hp;
	int tf= 0;	// set stuff off
	int z= sizeof(tf);

	if( !name || ! (*name) )
		return INVALID_SOCKET;

	memset((char *) &server, 0, sizeof(server));

	if( isdigit(name[0])){
		server.sin_family= AF_INET;
		server.sin_addr.s_addr= inet_addr(name);
		server.sin_port= htons(portnum);
	}else{
		if ( (hp= gethostbyname(name)) == NULL){
			erreport("gethostbyname");
			return INVALID_SOCKET;
		}
		memmove((char *) &server.sin_addr,hp->h_addr,hp->h_length);
		server.sin_family= hp->h_addrtype;
		server.sin_port= (short)htons(portnum);
	}
	/* create socket */
	if( (s= socket(AF_INET, SOCK_STREAM, 0)) < 1){
		erreport("socket");
		return INVALID_SOCKET;
	}
	if (connect(s,(struct sockaddr *)&server, sizeof(server))< 0){
		erreport("connect");
		return INVALID_SOCKET;
	}
//setsockopt(s,SOL_SOCKET,SO_LINGER, &tf, (int)(z));
	setsockopt(s,SOL_SOCKET,SO_REUSEADDR, (char *)&tf, (int)(z));
	setsockopt(s,SOL_SOCKET,SO_KEEPALIVE, (char *)&tf, (int)(z));
	lasterror();
	return s;
}
int SendMsg(SOCKET sockfd, char *buff, int len){
	if(len<0)
		len= strlen(buff);
	if(bdebug && sockfd==socCtrl)
		printf(">>>%s", buff);
	return send(sockfd,buff,(size_t)len,0) > 0;
}
void SocClose(SOCKET *ps){
	if(*ps!=INVALID_SOCKET)
		closesocket(*ps);
	*ps= INVALID_SOCKET;
}
static int legible(SOCKET sockfd, int millis){
	int n;
	fd_set readfds;
	struct timeval timeout;

	if(sockfd==INVALID_SOCKET)
		return 0;

	FD_ZERO(&readfds);
	FD_SET(sockfd, &readfds);
	timeout.tv_sec= millis/1000;	/* seconds */
	timeout.tv_usec= (millis%1000)*1000;	/* microseconds  */
	do
		n= select(1+(int)sockfd, &readfds, 0, 0, &timeout);
	while(n==-1 && lasterror()==EINTR);
	if(n<0)
		erreport("select");
	return FD_ISSET(sockfd, &readfds);
}
int GetLine(SOCKET s, char *buff, int len, char *keep, int *pkn){
	int n, nb=0;
	char ch= 0;
	int kn= *pkn;

	--len;
	do{
		for(n= 0; ch!='\n' && n<kn && nb<len; n++){
			ch= keep[n];
			if(ch!='\r' && ch)
				buff[nb++]= ch;
		}
		if((kn -= n) > 0)
			memmove(keep, keep+n, kn);
		else if(legible(s, 200) >0){
			kn= recv(s, keep, 1024, 0);
			if(kn<0){
				erreport("recv");
				*pkn= 0;
				return -1;
			}
		}
	}while(kn>0 && ch!='\n');
	*pkn= kn;
	buff[nb]= 0;
	if(aborted)
		nb= *pkn= 0;
	return nb;
}
int GetReply(char *buff, int len, FILE *fo){
	static char keep[1024];
	static int kn= 0;
	int nb;
	int code;
	
	unsigned int tquit= GetTickCount()+60000;
	if(fo==0 && bdebug){
		fo= stdout;
		printf("<<<");
	}
	do{
		memset(buff,0,len);
		nb= GetLine(socCtrl, buff, len, keep, &kn);
		if(nb<0 || (nb==0 && aborted)){
			if(bdebug)printf("[599 returned]\n");
			code= 599;
		}else if(nb>3){
			code= atoi(buff);
			if(code<0)
				code= 0;
		}else
			code= 0;
		if(buff[0] && fo){
			fputs(buff, fo);
			fflush(fo);
		}
		if(buff[3]=='-')
			tquit= GetTickCount()+60000;
		else if(GetTickCount()>tquit){
			printf("reply timed out\n");
			return 599;
		}
	}while(buff[3] == '-' || code==0);
	return code;
}
int send_command_fp(char *msgfmt, char *arg, FILE *fp){
	unsigned int iErr= 0;
	int code;

	if(msgfmt){
		sprintf(reply, msgfmt, arg);
		SendMsg(socCtrl, reply, -1);
		iErr= erreport("send message");
	}else
		reply[0]= 0;
	code= GetReply(reply, sizeof(reply), fp);
	if(iErr)
		return 598;
	return code;
}
int send_command(char *msgfmt, char *arg){
	return send_command_fp(msgfmt, arg, verbose? stdout:0);
}
void flush_control_socket(FILE *fo){
	char ctmp[513];
	while(legible(socCtrl, 0)){
		int n= recv(socCtrl, ctmp, sizeof(ctmp)-1, 0);
		if(n>0){
			if(ctmp[0]==0)
				break;
			if(fo){
				ctmp[n]= 0;
				fputs(ctmp, fo);
			}
		}else{
			printf("\r\nConnection closed by server\r\n");
			SocClose(&socCtrl);
			socCtrl= INVALID_SOCKET;
			break;
		}
	}
}

