/* Copyright (C) 1999 Beau Kuiper

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2, 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 General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  */

/* This library contains various TCP/IP connection utilities to make connecting
   and listening to sockets easier to impliment. */

#include "ftpd.h"

sigjmp_buf sigbuf;

/* server is in machine format (not network format) */
char *getipstr(unsigned int server)
{
	static char ipstr[20];
	snprintf(ipstr, 20, "%d.%d.%d.%d", server >> 24, 
					   (server >> 16) & 0xFF,
					   (server >> 8) & 0xFF,
					   server & 0xFF);
	return(ipstr);
}
	
void takentoolong(int sig)
{
	siglongjmp(sigbuf, 1);
}

/* server is in machine format */
char *getnetworkstr(unsigned int server)
{
	struct hostent *hostentry;
	unsigned int myserve = htonl(server);
	int a;
	
	a = sigsetjmp(sigbuf, 1);
	signal(SIGALRM, takentoolong);
	
	if ((a == 0) && (config->dnstimeout != -1))
	{
		alarm(config->dnstimeout);
		hostentry = gethostbyaddr((char *)&myserve, sizeof(unsigned int), AF_INET);
	}
	else
		hostentry = NULL;
	signal(SIGALRM, SIG_IGN);
	alarm(0);

	if (hostentry)
		return(strdupwrapper(hostentry->h_name));
	else 
		return(strdupwrapper((char *)getipstr(server)));
}

/* This procedure gets the internet ip address for a hostname or IP string.
   It returns either the network address or -1 signalling DNS failure for 
   a host name
*/

int getnetworkint(char *server, unsigned int *out)
{
	int pos, trigger;
	struct hostent *hostentry;
	char **ipaddrstr;
	char *buffertmp;
	unsigned int a1, a2, a3, a4;
	unsigned int l;
  
	pos = 0; trigger = 0;
	while ((server[pos] != 0) && (trigger != 1))
	{
		if (((server[pos] < '0') || (server[pos] > '9')) && (server[pos] != 46))
			trigger = 1;
		pos = pos + 1;
	}
	if (trigger == 1)
	{
		hostentry = gethostbyname(server);
		if (hostentry == NULL)
			return(-1);		/* DNS failure */
		ipaddrstr = hostentry->h_addr_list;
		memcpy(&l, *ipaddrstr, 4);
		l = ntohl(l);
	}
	else
	{
	
		buffertmp = mallocwrapper(sizeof(char) * (strlen(server) + 1));
		pos = 0;
		while (server[pos] != 0)
		{
			if (server[pos] == 46)
				buffertmp[pos] = 65;
			else
				buffertmp[pos] = server[pos];
			pos = pos + 1;
		}
		buffertmp[pos] = 0;
		sscanf(buffertmp, "%uA%uA%uA%u", &a1, &a2, &a3,&a4);
		l = ((a1 * 256 * 256 * 256) + (a2 * 256 * 256) + (a3 * 256) + a4);
		freewrapper(buffertmp);
	} 
	*out = l;
	return(0);
}
 
int conn_server(unsigned int ip, int port)
{
	int socknum, pos;

	struct sockaddr_in dest;

	dest.sin_addr.s_addr = htonl(ip);
	dest.sin_port = htons(port);
	dest.sin_family = AF_INET;
	socknum = socket( AF_INET, SOCK_STREAM, 6);
	if (socknum == -1)
		return(-1);

 	pos = connect(socknum, (struct sockaddr *)&dest, sizeof(dest));
	if (pos == -1) 
		return(-1);					/* Connection failure */

	return (socknum);
}

int conn_server_nonblocking(unsigned int ip, int port, int localport, int fd)
{
	int socknum, pos;
	struct sockaddr_in dest;
	int on = 1;
	int tmp;
	
	socknum = socket(AF_INET, SOCK_STREAM, 6);
	if (socknum == -1)
		return(-1);

	/* See if we can get the local port we want to bind to */
	/* If we can't, just let the computer choose a port for us */

	dest.sin_family = AF_INET;
	
	tmp = sizeof(dest);
	getsockname(fd, (struct sockaddr *)&dest, &tmp);
	
	dest.sin_port = htons(localport);
		
	setsockopt(socknum, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

	/* attempt to bind the socket, if it doesn't work, it isn't a problem */
	bind(socknum, (struct sockaddr *)&dest, sizeof(dest));
	
	dest.sin_port = htons(port);
	dest.sin_addr.s_addr = htonl(ip);
	fcntl(socknum, F_SETFL, O_NONBLOCK);
	
	pos = connect(socknum, (struct sockaddr *)&dest, sizeof(dest));
	if ((pos == -1) && (errno != EINPROGRESS)) 
		return(-1);			/* Connection failure */
	
	return (socknum);
}

/* This procedure will setup a socket to accept connections from remote
   servers. The parameter needed is the port to listen on and the maximum
   number of connections that can be pending.
   
   It will return one of the following values
     >0 - The socket ready to accept connections
     -1 - Binding the socket failed, probably because the port is already used
     -2 - Socket creation failed for some, probably serious reason
*/
   
int listenport(int port, unsigned int ip, int maxconnect)
{
	int newport;
	struct sockaddr_in dest;
	int on = 1;
  
	newport = socket(AF_INET, SOCK_STREAM, 6);  
	if (newport == -1)
		return(-1);
  
	dest.sin_family = AF_INET;
	dest.sin_port = htons(port);
	dest.sin_addr.s_addr = htonl(ip);	/* clear the address so it works */
  
	setsockopt(newport, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));

	if (bind(newport, (struct sockaddr *)&dest, sizeof(dest)) == -1)
		return(-1);

   	listen(newport, maxconnect);

	return(newport);
}
 
/* This procedure accepts a connection on a socket defined with the above
   procedure. It requires the socket number.
   
   It returns a 4 byte integer representing the other hosts ip number in 
   iaddress and returns the following:
     >0 - the file descriptor of the open connection
     -1 - failure (something went wrong)
*/
 
int get_conn(int socketin, unsigned int *addr)
{
	struct sockaddr_in dest;
 	int inport, n;
  
 	n = sizeof(dest);

	inport = accept(socketin, (struct sockaddr *)&dest, &n);
  
	if (inport == -1)
	{
		return(-1);
   	} 

  	*addr = ntohl(dest.sin_addr.s_addr);
  	return(inport);
}

/* this sets up a connection parralel to a current connection. It is used
   for the data connection in the FTP server */
  
int listenparrelelport(int fd, int *port, unsigned int *ip, int maxconnect)
{
	int newport;
	struct sockaddr_in dest;
	int tmp;
  
	newport = socket(AF_INET, SOCK_STREAM, 6);  
	if (newport == -1)
		return(-1);
  
  	tmp = sizeof(dest);
  	getsockname(fd, (struct sockaddr *)&dest, &tmp);

  	tmp = 1;
	setsockopt(newport, SOL_SOCKET, SO_REUSEADDR, &tmp, sizeof(tmp));

	dest.sin_port = htons(*port);
	if (bind(newport, (struct sockaddr *)&dest, sizeof(dest)) == -1)
		return(-1);

	tmp = sizeof(dest);
	getsockname(newport, (struct sockaddr *)&dest, &tmp);
	
	*port = ntohs(dest.sin_port);
	*ip = ntohl(dest.sin_addr.s_addr);
   	
   	listen(newport, maxconnect);

	return(newport);
}

void getsockinfo(int fd, unsigned int *ip, int *port)
{
	struct sockaddr_in dest;
	int tmp;

  	tmp = sizeof(dest);
  	getsockname(fd, (struct sockaddr *)&dest, &tmp);
	*ip = ntohl(dest.sin_addr.s_addr);
	*port = ntohs(dest.sin_port);
}

unsigned int getremoteip(int fd)
{
	struct sockaddr_in name;
	int len = sizeof(name);
	
	if (!getpeername(fd, (struct sockaddr *)&name, &len))
  		return(ntohl(name.sin_addr.s_addr));
 	else
 		return(1);
}

void socket_flush_wait(int fd, int timeout)
{
	char c;
	
	signal(SIGALRM, SIG_IGN);
        alarm(timeout);
	
	shutdown(fd, 1);
	read(fd, &c, 1);
	alarm(0);
}
