// Filename:	bbsipc.C
// Contents:	the bbs IPC object
// Author:		Greg Shaw
// Created:		6/13/93

/*
This file 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.

In addition to the permissions in the GNU General Public License, the
Free Software Foundation gives you unlimited permission to link the
compiled version of this file with other programs, and to distribute
those programs without any restriction coming from the use of this
file.  (The General Public License restrictions do apply in other
respects; for example, they cover modification of the file, and
distribution when not linked into another program.)

This file 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; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

#ifndef _BBSIPC_C_
#define _BBSIPC_C_

#undef DEBUG

#include "bbshdr.h"		// include *EVERYTHING*

// Function:	bbsipc constructor
// Purpose:	initialize the bbs ipc object.  
// Inputs:	none
// Outputs:	none - constructor may not return values
// Author:	Greg Shaw
// Created:	6/13/93

bbsipc::bbsipc()
{
	server = 0;		// server mode
	hostname[0] = 0;	// host name
	sock_open = 0;		// open socket
	sock_fd = 0;		// socket file descriptor 
	serv_sock_fd = 0;	// server socket file descriptor 
	sockt = 0;		// socket number
	connected = 0;	// not connected
}

// Function:	bbsipc destructor
// Purpose:	clean up the bbsipc object (close socket)
// Inputs:	none
// Outputs:	none
// Author:	Greg Shaw
// Created:	6/16/93

bbsipc::~bbsipc()
{
	close_sock(0);
};

// Function:	close_sock
// Purpose:	close the sockets (if open)
// Inputs:	none
// Outputs:	none
// Author:	Greg Shaw
// Created:	6/16/93

int bbsipc::close_sock(int client_only)
{
	if (sock_open)
		close(sock_fd);
	if (!client_only)
	{
		if (server)
			close(serv_sock_fd);
		server = 0;
		sock_open = 0;
	}
	connected = 0;
	return(0);
};

// Function:	open_sock
// Purpose:	open a socket for reading, writing or as a server.
// Inputs:	name - name of host to contact (NULL for server mode)
//		socknum - the socket number to contact
// Outputs:	-1 for error
// Author:	Greg Shaw
// Created:	6/16/93

int bbsipc::open_sock(char *name, int socknum)
{
	char macname[MAXHOSTNAMELEN+1];		// system information
	struct 	sockaddr_in	sa;	// socket information
	struct 	hostent	*hp;		// host entry information

	sockt = socknum;	// save socket number
	if (name == NULL)	// server mode?
	{
#ifdef DEBUG
	fprintf(stderr,"creating server socket.\r\n");	
#endif
		// yup, create socket
		server = 1;
		memset(&sa, 0, sizeof(struct sockaddr_in));	// nuke sa contents
		gethostname(macname,MAXHOSTNAMELEN);	// get this host's name
		hp = gethostbyname(macname);		// get host info
		if (hp == NULL)		// no info for me?
		{
			fprintf(stderr,"Unable to get host name.\n");
			return(-1);	// get out
		}
		sa.sin_family = hp->h_addrtype;		// set host address
		sa.sin_port= htons(socknum);		// set port number
		// create new socket
		if ((serv_sock_fd = socket(AF_INET, SOCK_STREAM,0)) < 0) 
		{
			fprintf(stderr,"Unable to open socket.\n");
			return(-1);	// can't create socket
		}
		if (bind(serv_sock_fd,(struct sockaddr *)&sa,(int)sizeof(sa)) < 0)
		{
			close(serv_sock_fd);
			fprintf(stderr,"Unable to bind socket.\n");
			return(-1);
		}
		listen(serv_sock_fd,5);		// 5 pending connections max
		sock_open = 1;
	}
	else
	{	// connect to socket
#ifdef DEBUG
	printf("attempting to connect client socket.\r\n");	
#endif
		strcpy(hostname, name);
		server = 0;
		if ((hp = gethostbyname(hostname)) == NULL) // get host addr
		{
#ifdef DEBUG
	printf("Couldn't get hostname to connect to.\r\n");	
#endif
			errno = ECONNREFUSED;	// connection refused
			return(-1);
		}
		memset(&sa, 0, sizeof(struct sockaddr_in));	// nuke sa contents
		memcpy((char *)&sa.sin_addr, (char *)hp->h_addr, hp->h_length);
		sa.sin_family = hp->h_addrtype;
		sa.sin_port = htons((unsigned short)socknum);
		if ((sock_fd = socket(hp->h_addrtype, SOCK_STREAM,0)) < 0)
			return(-1);
		if (connect(sock_fd,(struct sockaddr *)&sa,sizeof(sa)) < 0)
		{
#ifdef DEBUG
	printf("client connect failed.\r\n");	
#endif
			close(sock_fd);
			return(-1);
		}
#ifdef DEBUG
	printf("client connect successful.\r\n");	
#endif
		connected = 1;
		sock_open = 1;
	}
	return(0);		// exit normally
};

// Function:	connect
// Purpose:	connect to another process via server socket
// Inputs:	none
// Outputs:	returns true if socket connection successful
// Author:	Greg Shaw
// Created:	6/18/93

int bbsipc::do_connect(void)
{
	struct sockaddr_in isa;	// socket address info
	int	i_size;		// socket address size

	i_size = sizeof(isa);
	getsockname(serv_sock_fd,(struct sockaddr *)&isa,&i_size);

	if ((sock_fd = accept(serv_sock_fd,(struct sockaddr *)&isa,&i_size)) < 0)// accept connection
	{
#ifdef DEBUG
	printf("server: accept failed.\r\n");	
#endif
		return(-1);
	}
#ifdef DEBUG
	printf("server: accept succeeded.\r\n");	
#endif
	connected = 1;
	return(0);
};

// Function:	send
// Purpose:	send a message via the socket
// Inputs:	msg - the message to send (null terminated string)
// Outputs:	non-zero for error
// Author:	Greg Shaw
// Created:	6/16/93

int bbsipc::send(char *msg)
{
	int	cw;
	char eom = ETX;

	// send a message
#ifdef DEBUG
	printf("send: Sending %s.\r\n",msg);	
#endif
	if (!sock_open && !connected)
	{
#ifdef DEBUG
	printf("send: socket not open.\r\n");	
#endif
		return(-1);
	}
	if (cw = write(sock_fd,msg,strlen(msg)), cw != strlen(msg))
	{
#ifdef DEBUG
	printf("Unable to write message!\r\n");
#endif
		return(-1);
	}
	if (cw = write(sock_fd,&eom,1), cw != 1)
	{
#ifdef DEBUG
		printf("send: unable to send end of message.\r\n");
#endif
		return(-1);
	}
#ifdef DEBUG
	printf("send: sent %s.\r\n",msg);	
#endif

	return(0);
};

// Function:	receive
// Purpose:		receive a message from the other end of the socket
// Inputs:		none
// Outputs:		msg - the message to be received.
// Author:		Greg Shaw
// Created:		6/22/93

int bbsipc::receive(char *msg)
{
	char c;				// character read
	char eom;			// end of message found?
	int count;			// byte counter
	int byr;			// number of bytes read

#ifdef DEBUG
	printf("receive: start\r\n");	
#endif
	count = 0;
	byr = 0;
	eom = 0;
	while (!eom)
	{	       // look for trailing null
		if ((byr = read(sock_fd,&c,(unsigned)1)), byr > 0) 
		{
#ifdef DEBUG
	printf("%x char read\r\n",c);	
#endif
			if (c == ETX)
				eom++;
			else
				msg[count++] = c;	// add to msg
		}
		else if (byr == 0)	// connection is closed for 0 chars read
			return(-1);
	}
	msg[count] = 0;	/* add terminating null */
	return(count);
};

// Function:	msg_avail
// Purpose:	poll the socket to determine whether a message is available
//			for reading.
// Input:	none
// Output:	non-zero for a message available
// Author:	Greg Shaw
// Created:	6/22/93

int bbsipc::msg_avail(char wait)
{
	// NOTE: below may be a problem.  In a server, it should not be
	// possible for a particular connection to tie up the server.  If the
	// below is left as is (wait forever for message), it could happen that
	// the calling process might die before sending the message, hence a 
	// forever wait for a message that will never come.  This would cause a
	// lockup of the server (and that would be a Bad Thing.

	fd_set pfd;		// file descriptor set 
	FD_ZERO(&pfd);
	FD_SET(sock_fd,&pfd);
        struct timeval waittime;

        waittime.tv_sec = 0; 
        waittime.tv_usec = 100; // 100msec
	if (!wait)	// wait?
	{
		select(FD_SETSIZE,&pfd,NULL,NULL,&waittime); 
		return(FD_ISSET(sock_fd,&pfd)); 
				// don't wait, return immed.
	}
	else
	{
		select(FD_SETSIZE,&pfd,NULL,NULL,NULL);  // wait forever
		return(FD_ISSET(sock_fd,&pfd)); 
	}
};


// Function:	two_connect
// Purpose:	connect to a server master socket, get socket from server, 
//			disconnect and connect to new socket number.
//			(a two level socket connect)
// Inputs:	none
// Outputs:	ipc object will connect if possible
// Notes:	this is a client version only.  The server version will be 
//			much much more complex.
// Author:	Greg Shaw
// Created:	7/23/93

int bbsipc::two_connect(char *name, int socknum)
{
	char 	tmpstr[50];

	// attempt to connect to server to get socket number
	if (open_sock(name,socknum)	== 0)
	{ // gotcha!
		if (msg_avail(1) < 0)	// wait for message
			return(-1);
		// now get new socket number
		receive(tmpstr);	// get socket #
		if (sscanf(tmpstr,"%d",&sockt) != 1)
			return(-1);
		else
			close_sock(0);	// close current socket
		if (open_sock(name,sockt) == 0)
		{	// got new socket
			return(0);
		}
	}
	return(-1);
};
#endif // _BBSIPC_C_
