/*****************************************************************************
 * File:	sockets.c
 *
 * Author:	Rhett "Jonzy" Jones	jonzy@cc.utah.edu
 *
 * Date:	March 15, 1993
 *
 * Modified:	I ain't got that far yet.
 *
 * Description:	Contains routines dealing with sockets and communicating
 *		over the internet.
 *
 * Routines:		void	IP2Hostname(int sockfd,char *host,char *ip);
 *			int	SetUpReadNwriter(int theSocket);
 *			void	CloseReadNwriter(void);
 *			int	ContactHost(char* theHost, int thePort);
 *			int	SendString(char *s);
 *			char	*GetString(FILE *ptr);
 *			void	TooMuchTime4Read(void);
 *			void	ChildProcess(void);
 *			int	ListenerEstablished(int port);
 *			int	ProcessRequest(int sockfd);
 *
 * Bugs:	No known bugs.
 *
 * Copyright:	Copyright 1993, University of Utah Computer Center.
 *		This source may be freely distributed as long as this copyright
 * 		notice remains intact, and is in no way used for any monetary
 * 		gain, by any institution, business, person, or persons.
 *
 ****************************************************************************/

#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/wait.h>
#include <sys/param.h>

#define READTIMEOUT	(5 * 60)
#define MAXLINE		512
#define RECVSIZE	4096

jmp_buf		env;

/* These are defined in "jughead.c". */
extern int		debug;
extern FILE		*wtPtr,
			*rdPtr;
#define BUFFERSIZE	2048
extern char		buffer[BUFFERSIZE];

/*****************************************************************************
 * IP2Hostname places the name of the host, as acquired from the IP
 * number, into 'host'.  This way we can log the name of the host instead
 * of the IP number.
 ****************************************************************************/
void IP2Hostname(sockfd,host,ip)
	int			sockfd;		/* Socket file descriptor. */
	char			*host;		/* The name of the host we are talking to. */
	char			*ip;		/* The hosts IP number. */
{	struct sockaddr_in	sin;		/* Info about the socket. */
	int			len;		/* The size of 'sin'. */
	struct hostent		*hostEntry;	/* Information about the host. */

	len = sizeof(sin);
	getpeername(sockfd,&sin,&len);
	strcpy(ip,inet_ntoa(sin.sin_addr));
	strcpy(host,inet_ntoa(sin.sin_addr));

	hostEntry = gethostbyaddr((char *)&sin.sin_addr,sizeof(sin.sin_addr.s_addr),AF_INET);
     
	if (hostEntry)
		(void)strcpy(host,hostEntry->h_name);

}	/* IP2Hostname */

/*****************************************************************************
 * SetUpReadNwriter returns true if we can set up read and write pointers
 * to the socket 'theSocket'.
 ****************************************************************************/
int SetUpReadNwriter(theSocket)
	int		theSocket;	/* The socket. */
{
	/* See if we can open a file for writing. */
	if (!(wtPtr = fdopen(theSocket,"w")))
		{
		fprintf(stderr,"error: SetUpReadNwriter could not open socket file for writing.\n");
		return(0);
		}

	/* See if we can open a file for reading. */
	if (!(rdPtr = fdopen(theSocket,"r")))
		{
		fprintf(stderr,"error: SetUpReadNwriter could not open socket file for reading.\n");
		return(0);
		}

	return(1);

}	/* SetUpReadNwriter */

/*****************************************************************************
 * CloseReadNwriter simply closes 'rdPtr' and 'wtPtr'.
 ****************************************************************************/
void CloseReadNwriter()
{
	fclose(wtPtr);
	fclose(rdPtr);

}	/* CloseReadNwriter */

/*****************************************************************************
 * ContactHost returns true if we were able to get a connection to 'theHost'
 * out port 'thePort', and open the files 'wrPtr' and 'rdPtr' for reading and
 * and writing information to and from 'theHost' out 'thePort'.  Otherwise
 * this routine returns false.
 ****************************************************************************/
int ContactHost(theHost,thePort)
	char			*theHost;	/* The host to connect with. */
	int			thePort;	/* The port to use. */
{	int			theSocket;	/* The socket. */
	struct sockaddr_in	scktRec;	/* The socket address info. */
	struct hostent		*theHostEntry;	/* The host entry. */

	if (debug)
		fprintf(stderr,"ContactHost attempting a connection to: %s %d\n",theHost,thePort);

	/* See if we can get a socket. */
	if ((theSocket = socket(PF_INET,SOCK_STREAM,0)) < 0)
		{
		fprintf(stderr,"error: ContactHost cannot get the socket.\n");
		return(0);
		}

	/* Initialize our socket address information. */
	scktRec.sin_family = AF_INET;
	scktRec.sin_port = htons(thePort);
	if (theHostEntry = gethostbyname(theHost))
		bcopy(theHostEntry->h_addr,(char *)&scktRec.sin_addr.s_addr, 4);
	else
		{
		fprintf(stderr,"error: ContactHost found unknown host [%s].\n",theHost);
		return(0);
		}

	/* See if we can get a connection. */
	if (connect(theSocket,(struct sockaddr *)&scktRec,sizeof(scktRec)) < 0)
		{
		fprintf(stderr,"error: ContactHost cannot connect to [%s] via port [%d].\n",theHost,thePort);
		return(0);
		}

	if (!SetUpReadNwriter(theSocket))
		return(0);

	if (debug)
		fprintf(stderr,"ContactHost has established connection\n");

	return(1);

}	/* ContactHost */

/*****************************************************************************
 * SendString returns true no matter what, but first writes the string 's' to
 * 'wtPtr' which is the machine we are talking to.
 ****************************************************************************/
int SendString(s)
	char	*s;	/* The string we are sending to the machine. */
{

	fprintf(wtPtr,"%s",s);
	fflush(wtPtr);
	return(1);

}	/* SendString */

/*****************************************************************************
 * GetString returns a line of text from either the socket or file we are
 * reading from, which is pointed to by the 'ptr'.  The string will contain at
 * most 'BUFFERSIZE' characters.  For more information on the contents of the
 * string returned by this routine consult the man page on 'fgets'.
 ****************************************************************************/
char *GetString(ptr)
	FILE	*ptr;	/* The file or socket we are reading from. */
{
	return(fgets(buffer,BUFFERSIZE,ptr));

}	/* GetString */

/*****************************************************************************
 * TooMuchTime4Read gets called only when the time period has elapsed when
 * waiting to read from our input..
 ****************************************************************************/
void TooMuchTime4Read()
{
	if (debug)
		fprintf(stderr,"In TooMuchTime4Read\n");

	longjmp(env,1);

}	/* TooMuchTime4Read */

/*****************************************************************************
 * ChildProcess wait3() for a child's return code and status.  This routine
 * is only called by the signal handler after receiving SIGCHLD.
 ****************************************************************************/
void ChildProcess()
{	int	status;		/* Status of the child process. */

	while (wait3(&status,WNOHANG|WUNTRACED,NULL) > 0)
		;

}	/* ChildProcess */

/*****************************************************************************
 * ListenerEstablished returns -1 if we could NOT: create a socket, set up the socket
 * such that the address will be reused and will stick around for the client,
 * we can bind the socket to the port 'port', and we can set up to listen for
 * up to 5 connections out the port, a socket listening out port 'port'.
 * Otherwise we were able to do all of the above so we return the socket.
 ****************************************************************************/
int ListenerEstablished(port)
	int			port;		/* The port to use. */
{	struct sockaddr_in	sin;		/* The socket information. */
	struct linger		so_linger;	/* The SO_LINGER info. */
	int			so_reuseaddr,	/* The SO_REUSEADDR info. */
				sckt,		/* The socket to return. */
				error;		/* Did we get an error? */

	if (debug)
		fprintf(stderr,"In ListenerEstablished\n");

	/* Initialize the variables. */
	so_reuseaddr = 1;
	error = so_linger.l_onoff = so_linger.l_linger = 0;
	bzero((char *)&sin,sizeof(sin));
	sin.sin_family = AF_INET;
	sin.sin_addr.s_addr = htonl(INADDR_ANY);
	sin.sin_port = htons(port);

	/* Create a socket binding the same local address and make sure the other will still be around. */
	if ((sckt = socket(AF_INET,SOCK_STREAM,0)) >= 0)
		if (setsockopt(sckt,SOL_SOCKET,SO_REUSEADDR,(char *)&so_reuseaddr,sizeof(so_reuseaddr)) >= 0)
			if (setsockopt(sckt,SOL_SOCKET,SO_LINGER,(char *)&so_linger,sizeof(so_linger)) >= 0)
				if (bind(sckt,(struct sockaddr *)&sin,sizeof(sin)) >= 0)
					if (listen(sckt,5) >= 0)
						return(sckt);
					else
						error = fprintf(stderr,"ListenerEstablished: can't listen 5\n");
				else
					error = fprintf(stderr,"ListenerEstablished: can't bind\n");
			else
				error = fprintf(stderr,"ListenerEstablished: can't setsockopt SO_LINGER\n");
		else
			error = fprintf(stderr,"ListenerEstablished: can't setsockopt REUSEADDR!\n");
	else
		error =  fprintf(stderr,"ListenerEstablished: can't socket\n");

	return(-1);

}	/* ListenerEstablished */

/*****************************************************************************
 * ProcessRequest gets and processes a request from a port.
 ****************************************************************************/
int ProcessRequest(sockfd)
	int		sockfd;			/* The socket file descriptor. */
{	char		*inputline;		/* The request to process. */
	int		length;			/* Length of the command line */

	if (!SetUpReadNwriter(sockfd))
		{
		fprintf(stderr,"error: ProcessRequest could not setup read and writters\n");
		return(0);
		}

	/* Set things up so we don't wait for ever waiting to get a request. */
	(void)signal(SIGALRM,TooMuchTime4Read);
	(void)alarm(READTIMEOUT);

	if (setjmp(env))
		{
		SendString("GetString: Timed out!\r\n");
		exit(-1);
		}

	inputline = GetString(rdPtr);
	length = strlen(inputline);

	/* We got our request to deactivate the alarm. */
	(void)alarm(0);
	(void)signal(SIGALRM,SIG_IGN);

	if (length <= 0)
		{
		SendString("ProcessRequest: readline error\r\n");
		close(sockfd);
		return(0);
		}

	RemoveCRLF(inputline);

	if (debug)
		fprintf(stderr,"ProcessRequest processing [%s]\n",inputline + 1);

	PostPositions(sockfd,inputline);
	SendString(".\r\n");
	CloseReadNwriter();

}	/* ProcessRequest */

