/*
 * network.c -- handles stuff dealing with connecting and name resolving
 *
 * Written by Jeremy Nelson in 1995
 * See the COPYRIGHT file or do /help ircii copyright
 */
#define SET_SOURCE_SOCKET

#include "irc.h"
#include "ircaux.h"
#include "vars.h"

#ifdef HAVE_SYS_UN_H
#include <sys/un.h>
#endif

extern char hostname[NAME_LEN+1];

/*
 * connect_by_number:  Wheeeee. Yet another monster function i get to fix
 * for the sake of it being inadequate for extension.
 *
 * we now take four arguments:
 *
 *	- hostname - name of the host (pathname) to connect to (if applicable)
 *	- portnum - port number to connect to or listen on (0 if you dont care)
 *	- service -	0 - set up a listening socket
 *			1 - set up a connecting socket
 *	- protocol - 	0 - use the TCP protocol
 *			1 - use the UDP protocol
 *
 *
 * Returns:
 *	Non-negative number -- new file descriptor ready for use
 *	-1 -- could not open a new file descriptor or 
 *		an illegal value for the protocol was specified
 *	-2 -- call to bind() failed
 *	-3 -- call to listen() failed.
 *	-4 -- call to connect() failed
 *	-5 -- call to getsockname() failed
 *	-6 -- the name of the host could not be resolved
 *	-7 -- illegal or unsupported request
 *
 *
 * Credit: I couldnt have put this together without the help of BSD4.4-lite
 * User Supplimentary Document #20 (Inter-process Communications tutorial)
 */
int connect_by_number(char *hostn, unsigned short *portnum, int service, int protocol, int nonblocking)
{
	int fd = -1;
	int is_unix = (hostn && *hostn == '/');
	int sock_type, proto_type;

	sock_type = (is_unix) ? AF_UNIX : AF_INET;
	proto_type = (protocol == PROTOCOL_TCP) ? SOCK_STREAM : SOCK_DGRAM;

	if ((fd = socket(sock_type, proto_type, 0)) < 0)
		return -1;

	set_socket_options (fd);

	/* Unix domain server */
#ifdef HAVE_SYS_UN_H
	if (is_unix)
	{
		struct sockaddr_un name;

		memset(&name, 0, sizeof(struct sockaddr_un));
		name.sun_family = AF_UNIX;
		strcpy(name.sun_path, hostn);
#ifdef HAVE_SUN_LEN
# ifdef SUN_LEN
		name.sun_len = SUN_LEN(&name);
# else
		name.sun_len = strlen(hostn) + 1;
# endif
#endif

		if (is_unix && (service == SERVICE_SERVER))
		{
			if (bind(fd, (struct sockaddr *)&name, strlen(name.sun_path) + 2))
				return close(fd), -2;

			if (protocol == PROTOCOL_TCP)
				if (listen(fd, 4) < 0)
					return close(fd), -3;
		}

		/* Unix domain client */
		else if (service == SERVICE_CLIENT)
		{
			alarm(get_int_var(CONNECT_TIMEOUT_VAR));
			if (connect (fd, (struct sockaddr *)&name, strlen(name.sun_path) + 2) < 0)
			{
				alarm(0);
				return close(fd), -4;
			}
			alarm(0);
		}
	}
	else
#endif

	/* Inet domain server */
	if (!is_unix && (service == SERVICE_SERVER))
	{
		int length;
		struct sockaddr_in name;

		memset(&name, 0, sizeof(struct sockaddr_in));
		name.sin_family = AF_INET;
		name.sin_addr.s_addr = htonl(INADDR_ANY);
		name.sin_port = htons(*portnum);

		if (bind(fd, (struct sockaddr *)&name, sizeof(name)))
			return close(fd), -2;

		length = sizeof (name);
		if (getsockname(fd, (struct sockaddr *)&name, &length))
			return close(fd), -5;

		*portnum = ntohs(name.sin_port);

		if (protocol == PROTOCOL_TCP)
			if (listen(fd, 4) < 0)
				return close(fd), -3;
#ifdef NON_BLOCKING_CONNECTS
		if (nonblocking && set_non_blocking(fd) < 0)
			return close(fd), -4;
#endif
	}

	/* Inet domain client */
	else if (!is_unix && (service == SERVICE_CLIENT))
	{
		struct sockaddr_in server;
		struct hostent *hp;
		struct sockaddr_in localaddr;
#ifdef WINNT
		char buf[BIG_BUFFER_SIZE+1];
#endif		
		/*
		 * Doing this bind is bad news unless you are sure that
		 * the hostname is valid.  This is not true for me at home,
		 * since i dynamic-ip it.
		 */
		if (LocalHostName)
		{
			memset(&localaddr, 0, sizeof(struct sockaddr_in));
			localaddr.sin_family = AF_INET;
			localaddr.sin_addr = LocalHostAddr;
			localaddr.sin_port = 0;
			if (bind(fd, (struct sockaddr *)&localaddr, sizeof(localaddr)))
				return close(fd), -2;
		}

		memset(&server, 0, sizeof(struct sockaddr_in));
#ifndef WINNT
		if (!(hp = resolv(hostn)))
  			return close(fd), -6;
		memcpy(&(server.sin_addr), hp->h_addr, hp->h_length);
		server.sin_family = AF_INET;
		server.sin_port = htons(*portnum);
#else
		/* for some odd reason resolv() fails on NT... */
		server = (*(struct sockaddr_in *) hostn);
		if (!hostn)
		{
			gethostname(buf, sizeof(buf));
			hostn = buf;
		}
		if ((server.sin_addr.s_addr = inet_addr(hostn)) == -1)
		{
			if ((hp = gethostbyname(hostn)) != NULL)
			{
				bzero((char *) &server, sizeof(server));
				bcopy(hp->h_addr, (char *) &server.sin_addr,
					hp->h_length);
				server.sin_family = hp->h_addrtype;
			}
			else
				return (-2);
		}
		else
			server.sin_family = AF_INET;
		server.sin_port = (unsigned short) htons(*portnum);
#endif

#ifdef NON_BLOCKING_CONNECTS
		if (nonblocking && set_non_blocking(fd) < 0)
			return close(fd), -4;
#endif
		alarm(get_int_var(CONNECT_TIMEOUT_VAR));
		if (connect (fd, (struct sockaddr *)&server, sizeof(server)) < 0)
		{
			alarm(0);
			if (errno != EINPROGRESS && !nonblocking)
				return close(fd), -4;
		}
		alarm(0);
	}

	/* error */
	else
		return close(fd), -7;

	return fd;
}


extern struct hostent *resolv (const char *stuff)
{
	struct hostent *hep;

	if ((hep = lookup_host(stuff)) == NULL)
		hep = lookup_ip(stuff);

	return hep;
}

extern struct hostent *lookup_host (const char *host)
{
	struct hostent *hep;

#ifdef WINNT
	sleep(1);
	alarm(2);
#else
	alarm(1);
#endif
	hep = gethostbyname(host);
	alarm(0);
	return hep;
}

extern char *host_to_ip (const char *host)
{
	struct hostent *hep = lookup_host(host);
	static char ip[256];

	return (hep ? sprintf(ip,"%u.%u.%u.%u",	hep->h_addr[0] & 0xff,
						hep->h_addr[1] & 0xff,
						hep->h_addr[2] & 0xff,
						hep->h_addr[3] & 0xff),
						ip : empty_string);
}

extern struct hostent *lookup_ip (const char *ip)
{
	int b1 = 0, b2 = 0, b3 = 0, b4 = 0;
	char foo[4];
	struct hostent *hep;

	sscanf(ip,"%d.%d.%d.%d", &b1, &b2, &b3, &b4);
	foo[0] = b1;
	foo[1] = b2;
	foo[2] = b3;
	foo[3] = b4;

#ifdef WINNT
	sleep(1);
	alarm(2);
#else
	alarm(1);
#endif
	hep = gethostbyaddr(foo, 4, AF_INET);
	alarm(0);

	return hep;
}

extern char *ip_to_host (const char *ip)
{
	struct hostent *hep = lookup_ip(ip);
	static char host[128];

	return (hep ? strcpy(host, hep->h_name): empty_string);
}

extern char *one_to_another (const char *what)
{

	if (!isdigit(what[strlen(what)-1]))
		return host_to_ip (what);
	else
		return ip_to_host (what);
}

#ifdef NON_BLOCKING_CONNECTS
int set_non_blocking(int fd)
{
	int	res, nonb = 0;

#if defined(NBLOCK_POSIX)
	nonb |= O_NONBLOCK;
#else
# if defined(NBLOCK_BSD)
	nonb |= O_NDELAY;
# else
#  if defined(NBLOCK_SYSV)
	res = 1;

	if (ioctl (fd, FIONBIO, &res) < 0)
		return -1;
#  else
#   error no idea how to set an fd to non-blocking 
#  endif
# endif
#endif
#if (defined(NBLOCK_POSIX) || defined(NBLOCK_BSD)) && !defined(NBLOCK_SYSV)
	if ((res = fcntl(fd, F_GETFL, 0)) == -1)
		return -1;
	else if (fcntl(fd, F_SETFL, res | nonb) == -1)
		return -1;
#endif
	return 0;
}

int set_blocking(int fd)
{
	int	res, nonb = 0;

#if defined(NBLOCK_POSIX)
	nonb |= O_NONBLOCK;
#else
# if defined(NBLOCK_BSD)
	nonb |= O_NDELAY;
# else
#  if defined(NBLOCK_SYSV)
	res = 0;

	if (ioctl (fd, FIONBIO, &res) < 0)
		return -1;
#  else
#   error no idea how to return an fd blocking 
#  endif
# endif
#endif
#if (defined(NBLOCK_POSIX) || defined(NBLOCK_BSD)) && !defined(NBLOCK_SYSV)
	if ((res = fcntl(fd, F_GETFL, 0)) == -1)
		return -1;
	else if (fcntl(fd, F_SETFL, res &~ nonb) == -1)
		return -1;
#endif
	return 0;
}
#endif


#ifndef HAVE_INET_ATON
/*
 * Copyright (c) 1983, 1990, 1993
 *    The Regents of the University of California.  All rights reserved.
 * Portions Copyright (c) 1993 by Digital Equipment Corporation.
 *
 * <Insert standard BSD copyright license here.>
 */

/* 
 * Check whether "cp" is a valid ascii representation
 * of an Internet address and convert to a binary address.
 * Returns 1 if the address is valid, 0 if not.
 * This replaces inet_addr, the return value from which
 * cannot distinguish between failure and a local broadcast address.
 */
int inet_aton(const char *cp, struct in_addr *addr)
{
	unsigned long	val;
	int		base, n;
	char		c;
	unsigned	parts[4];
	unsigned	*pp = parts;

	c = *cp;
	for (;;) {
		/*
		 * Collect number up to ``.''.
		 * Values are specified as for C:
		 * 0x=hex, 0=octal, isdigit=decimal.
		 */
		if (!isdigit(c))
			return (0);
		val = 0; base = 10;
		if (c == '0') {
			c = *++cp;
			if (c == 'x' || c == 'X')
				base = 16, c = *++cp;
			else
				base = 8;
		}
		for (;;) {
			if (isascii(c) && isdigit(c)) {
				val = (val * base) + (c - '0');
				c = *++cp;
			} else if (base == 16 && isascii(c) && isxdigit(c)) {
				val = (val << 4) |
					(c + 10 - (islower(c) ? 'a' : 'A'));
				c = *++cp;
			} else
				break;
		}
		if (c == '.') {
			/*
			 * Internet format:
			 *	a.b.c.d
			 *	a.b.c	(with c treated as 16 bits)
			 *	a.b	(with b treated as 24 bits)
			 */
			if (pp >= parts + 3)
				return (0);
			*pp++ = val;
			c = *++cp;
		} else
			break;
	}
	/*
	 * Check for trailing characters.
	 */
	if (c != '\0' && (!isascii(c) || !isspace(c)))
		return (0);
	/*
	 * Concoct the address according to
	 * the number of parts specified.
	 */
	n = pp - parts + 1;
	switch (n) {

	case 0:
		return (0);		/* initial nondigit */

	case 1:				/* a -- 32 bits */
		break;

	case 2:				/* a.b -- 8.24 bits */
		if (val > 0xffffff)
			return (0);
		val |= parts[0] << 24;
		break;

	case 3:				/* a.b.c -- 8.8.16 bits */
		if (val > 0xffff)
			return (0);
		val |= (parts[0] << 24) | (parts[1] << 16);
		break;

	case 4:				/* a.b.c.d -- 8.8.8.8 bits */
		if (val > 0xff)
			return (0);
		val |= (parts[0] << 24) | (parts[1] << 16) | (parts[2] << 8);
		break;
	}
	if (addr)
		addr->s_addr = htonl(val);
	return (1);
}
#endif
