/*
 * ircserv.c: A quaint little program to make irc life PING free 
 *
 * Written By Michael Sandrof
 *
 * Copyright(c) 1990 
 *
 * See the COPYRIGHT file, or do a HELP IRCII COPYRIGHT 
 */

/*
 * !*!*!*!*!*!*!*! Important! Read this at least once! !*!*!*!*!*!*!*!*!*!
 *
 *
 * This version of ircserv does *not* work like any previous version of
 * ircserv in any other version of the client!  Older versions assumed that
 * file descriptor 0 was a read-only pipe to the client, and that descriptor 1
 * was a write-only pipe to the client.  This version requires that file
 * descriptor 0 is a 2-way socket to the client.  This version CANNOT be used
 * interactively or with any older clients or programs that assume the pipe 
 * interface.  However, it should be trivial to convert any older programs
 * to use the new interface by using socketpair(2).
 *
 * Why did i do this?  It greatly cleans up both the client and ircserv's
 * end of it, since ircserv connections are handled like every other direct
 * server connection (that is, by a 2-way socket), and on the ircserv end,
 * we only have to juggle one descriptor instead of two.
 *
 *
 * !*!*!*!*!*!*!*! Important! Read this at least once! !*!*!*!*!*!*!*!*!*!
 */

#if 0
static	char	rcsid[] = "@(#)$Id: ircserv.c,v 1.17 1994/07/25 13:36:10 mrg Exp $";
#endif

#include "defs.h"

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#ifdef HAVE_SYS_FCNTL_H
#include <sys/fcntl.h>
#endif
#include <sys/file.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <signal.h>
#ifdef HAVE_SYS_UN_H
#include <sys/un.h>
#endif

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

/* machines we don't want to use <unistd.h> on 'cause its broken */
#if defined(pyr) || defined(_SEQUENT_)
# undef HAVE_UNISTD_H
#endif

#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif

/* FD that we're talking to the client on. */
#define CLIENT_FD 0

char empty_string[] = "";


#ifdef __STDC__
void report (int signal)
#else
void report (signal)
int signal;
#endif
{
	char buffer[1024];
	sprintf(buffer, "!!!IRCSERV!! got signal %d\n", signal);
	write(CLIENT_FD, buffer, strlen(buffer));
	exit(signal);
	return;
}


/*
 * ircserv: This little program connects to the server (given as arg 1) on
 * the given port (given as arg 2).  It then accepts input from stdin and
 * sends it to that server. Likewise, it reads stuff sent from the server and
 * sends it to stdout.  Simple?  Yes, it is.  But wait!  There's more!  It
 * also intercepts server PINGs and automatically responds to them.   This
 * frees up the process that starts ircserv (such as IRCII) to pause without
 * fear of being pooted off the net. 
 */
/* Some problems with the original ircserv:
 *	* Pinged out after filling up the blocking buffer
 *	  Now it blocks forever simply mallocing happily on the way.
 */
/* This implementation is non-blocking.  What this means
 * is if you suspend your ircII client, ircserv will sit happily
 * reading from the server and answering pings and so forth.  While
 * you are suspended, ircserv will not make any attempt to write
 * to the ircII client.  When you resume the client, ircserv will
 * dump everything on the client ASAP.  The memory used to buffer 
 * the stuff coming from the server is allocated dynamically, which
 * means you can be suspended forever without pinging out at the
 * expense of some ram. 
 *
 * (Written/Modified by nelson@cs.uwp.edu for the EPIC project)
 */
int main (int argc, char *argv[])
{
	int		des;			/* server fd */
	unsigned short 	port;

	/* stuff from the for(;;) loop */
	fd_set	rd,			/* the ircII client + server */
		wd;			/* the ircII client */
	int	wrote,			/* how much we wrote */
		c;			/* length of blocking buffer */
	char	buffer[BUFSIZ + 1],	/* buffer for incoming stuff */
		*block_buffer = NULL,	/* stuff thats blocked */
		*quit_message = "QUIT :ircserv caught EOF!\r\n",
		*pong = "PONG . .";

	/* must have 3 args to make any sense */
	if (argc < 3)
	{
		fprintf(stderr, "Usage: %s server port\n", argv[0]);
		exit(1);
	}

	port = atoi(argv[2]);
	des = connect_by_number(argv[1], &port, SERVICE_CLIENT, PROTOCOL_TCP);


	/* if we couldnt connect, punt */
        if (des < 0)
	{
		const char *message = "!!!IRCSERV!! Couldnt connect\n";
		write(CLIENT_FD, message, strlen(message));
                exit(des);
	}


	(void) MY_SIGNAL(SIGINT, report, 0);
	(void) MY_SIGNAL(SIGTERM, report, 0);
        (void) MY_SIGNAL(SIGSEGV, report, 0);
        (void) MY_SIGNAL(SIGPIPE, report, 0);
#ifdef SIGWINCH
        (void) MY_SIGNAL(SIGWINCH, SIG_IGN, 0);
#endif
#ifdef SIGBUS
        (void) MY_SIGNAL(SIGBUS, report, 0);
#endif

	/* Mark our socket with ircII client as nonblocking */
        if (fcntl(CLIENT_FD, F_SETFL, FNDELAY) < 0)
                exit(1);

	/* Each iteration through this loop is one read cycle */
	/* Loop runs forever until explicitly stopped */
	for (;;)
	{
		/* set up our fd's every run through */
		FD_ZERO(&rd);
		FD_SET(CLIENT_FD, &rd);
		FD_SET(des, &rd);
		FD_ZERO(&wd);

		/* If we are waiting for the client to unblock,
		 * then we set it for writing, otherwise we leave
		 * it as uninteresting.
		 */
		if (block_buffer)
			FD_SET(CLIENT_FD, &wd);

		switch (new_select(&rd, &wd, NULL))
		{
		case -1:
		case  0:	break;

		default:
		{
			/* we can write to the client */
			if (FD_ISSET(CLIENT_FD, &wd))
			{
				c = strlen(block_buffer);

				if ((wrote = write(CLIENT_FD, block_buffer, c)) == -1)
				{
					/* something happened to ircII */
					if (errno != EAGAIN)
					{
						write(des, quit_message, strlen(quit_message));
						exit(0);
					}
				}

				else if (wrote < c)
					strcpy(block_buffer, &(block_buffer[wrote]));

				else
					new_free(&block_buffer);
			}

			/* stuff incoming from the client */
			if (FD_ISSET(CLIENT_FD, &rd))
			{
				if ((c = dgets(buffer, BUFSIZ, CLIENT_FD, NULL)))
					write(des, buffer, c);
				else
				{
					write(des, quit_message, strlen(quit_message));
					exit(0);
				}
			}

			/* incoming stuff from the server */
			if (FD_ISSET(des, &rd))
			{
				/* get the line */
				if ((c = dgets(buffer, BUFSIZ, des, NULL)) >= 0)
				{
					/* wheeeee. */
					buffer[c] = 0;

					/* if its a ping, handle it */
					if (strncmp(buffer, "PING ", 5) == 0)
						write(des, pong, strlen(pong));

					/* 
					 * We use the '!' character as a 
					 * special flag to ircII that its
					 * an urgent message from us.  Its
					 * therefore our duty to weed out
					 * any attempt from a rouge server
					 * to send us a message starting with
					 * '!' which the client would misint.
					 */
					else if (*buffer == '!')
						;	/* munch munch */

					/* its a regular line */
					else
					{
						/* tack it to the end of the stuff
						   we've already blocked on */
						char *tmp;
						int foo = 0;

						if (block_buffer) 
							foo = strlen(block_buffer);
						foo += strlen(buffer) + 1;
						tmp = (char *)new_malloc(foo);
						*tmp = 0;

						if (block_buffer)
							strcpy(tmp, block_buffer);
						strcat(tmp, buffer);
						new_free(&block_buffer);
						block_buffer = tmp;

						/* We will try to write it
						 * when we loop through again
						 * since block_buffer has stuff
						 */
					}
				}

				/* EOF from server */
				else
				{
					char *message = "!!!IRCSERV!! Got EOF from server";
					write(CLIENT_FD, message, strlen(message));
					exit(0);
				}
			} 
		} 
		}
	}
}


/*
 * 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 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;

	fd = socket(sock_type, proto_type, 0);

	if (fd < 0)
		return -1;

	set_socket_options (fd);

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

		bzero (&name, sizeof(struct sockaddr_un));
		name.sun_family = AF_UNIX;
		strcpy(name.sun_path, hostn);

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

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

	/* Unix domain client */
	else if (is_unix && (service == SERVICE_CLIENT))
	{
		struct sockaddr_un name;

		bzero (&name, sizeof(struct sockaddr_un));
		name.sun_family = AF_UNIX;
		strcpy(name.sun_path, hostn);

		alarm(CONNECT_TIMEOUT);
		if (connect (fd, (struct sockaddr *)&name, sizeof(name)) < 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;

		bzero (&name, sizeof(struct sockaddr_in));
		name.sin_family = AF_INET;
		name.sin_addr.s_addr = INADDR_ANY;
		name.sin_port = *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;
	}

	/* Inet domain client */
	else if (!is_unix && (service == SERVICE_CLIENT))
	{
		struct sockaddr_in name;
		struct hostent *hp;

		bzero (&name, sizeof(struct sockaddr_in));
		hp = resolv(hostn);
		if (hp == NULL)
			return close(fd), -6;
		
		bcopy(hp->h_addr, &(name.sin_addr), hp->h_length);
		name.sin_family = AF_INET;
		name.sin_port = htons(*portnum);

		alarm(CONNECT_TIMEOUT);
		if (connect (fd, (struct sockaddr *)&name, sizeof(name)) < 0)
		{
			alarm(0);
			return close(fd), -4;
		}
		alarm(0);
	}

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

	return fd;
}


#ifdef __STDC__
extern struct hostent *resolv (const char *stuff)
#else
extern struct hostent *resolv (stuff)
const char *stuff;
#endif
{
	struct hostent *hep;

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

	return hep;
}

#ifdef __STDC__
extern struct hostent *lookup_host (const char *host)
#else
extern struct hostent *lookup_host (host)
const char *host;
#endif
{
	struct hostent *hep;

	alarm(1);
	hep = gethostbyname(host);
	alarm(0);
	return hep;
}

#ifdef __STDC__
extern char *host_to_ip (const char *host)
#else
extern char *host_to_ip (host)
const char *host;
#endif
{
	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);
}

#ifdef __STDC__
extern struct hostent *lookup_ip (const char *ip)
#else
extern struct hostent *lookup_ip (ip)
const char *ip;
#endif
{
	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;

	alarm(1);
	hep = gethostbyaddr(foo, 4, AF_INET);
	alarm(0);

	return hep;
}

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

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

#ifdef __STDC__
extern char *one_to_another (const char *what)
#else
extern char *one_to_another (what)
const char *what;
#endif
{
	int b1 = 0, b2 = 0, b3 = 0, b4 = 0;
	sscanf(what, "%d.%d.%d.%d", &b1, &b2, &b3, &b4);

	if ((b1 == 0) && (b2 == 0) && (b3 == 0) && (b4 == 0))
		return host_to_ip (what);
	else
		return ip_to_host (what);
}


#ifdef __STDC__
char	*new_malloc (size_t size)
#else
char	*new_malloc(size)
size_t	size;
#endif
{
	char	*ptr;

	if ((ptr = (char *) malloc(size)) == (char *) 0)
	{
		static	char	error[] = "!!!IRCSERV!! Malloc failed\n";
		write(0, error, strlen(error));
		exit(1);
	}
	return (ptr);
}

#ifdef __STDC__
char *	new_free(char **ptr)
#else
char *	new_free(ptr)
char	**ptr;
#endif
{
	if (*ptr)
	{
		free(*ptr);
		*ptr = (char *) 0;
	}
	return *ptr;
}

