// Filename:   rochatd.C
// Contents:   the chat daemon
// Author:     Gregory Shaw
// Created:    4/9/95

/*
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 _ROCHATD_C_
#define _ROCHATD_C_

#include "bbshdr.h"
#include "lang/bbsstring.h"

#undef DEBUG

// This is C (c++), so let's do prototypes

int check_admin_connect(void);
int shuffle_data(void);
int check_allocated(void);
int check_info_connect(void);
int client_master_connect(void);
void delete_client(RoomInfo    *, ClientInfo *);
void getout(int);
void init_data(void);
void init_masters(void);

// declared global so that the 'getout' function can close the socket
bbsipc master_client;           // master client port
bbsipc master_info;             // master information port
bbsipc master_info_admin;       // master admin information port
                                // room tracking information
RoomInfo   roomlist[MAX_CHAT_ROOMS];
errlog error_logger;            // error logging connection
char   portlist[MAX_CLIENTS];   // list of available ports
                                // do we have any clients issued, but waiting?
int        clients_wo_connected=0;
int        clients=0;           // do we have any clients?
long    messages_sent;          // messages sent
long    messages_received;      // messages received
long    connections;            // messages received
long    info_connects;          // information connects received
time_t boot;                    // start time


// Function:   check_admin_connect
// Purpose:    check for an administration connection and handle appropriately
// Input:  none
// Output: checks for connect.  if successful, admin data is sent to the client.
// Author: Gregory Shaw
// Created:    4/9/95

int check_admin_connect(void)
{
	int x;
	int used;
	char msg[255];
	char *timestr;              // boot time
	ClientInfo *tmp;            // iterator

                                // connection?
	if (master_info_admin.do_connect(0) == 0)
	{
		info_connects++;
		used = 0;
		// build information message
		// send to client
		timestr = ctime(&boot);
		messages_sent++;
		master_info_admin.send("The rocat BBS System: rochatd\n");
		master_info_admin.send("\nGlobal Information:\n");
		sprintf(msg,"    Up since: %s\n",timestr);
		master_info_admin.send(msg);
		master_info_admin.send("  Messages:\n");
		sprintf(msg,"    Received: %ld\n",messages_received);
		master_info_admin.send(msg);
		sprintf(msg,"    Sent: %ld\n",messages_sent);
		master_info_admin.send(msg);
		sprintf(msg,"\nConnections: %ld\n\n",connections);
		master_info_admin.send(msg);
		master_info_admin.send("Room Information:\n\n");
		for (x=0; x< MAX_CHAT_ROOMS; x++)
		{
			if (roomlist[x].room != -1)
			{
				used++;
				sprintf(msg,"\nRoom %d:\n",roomlist[x].room);
				master_info_admin.send(msg);
				master_info_admin.send(CHATDHEADER);
				tmp = roomlist[x].list;
				while (tmp != NULL)
				{
					timestr = ctime(&tmp->connected);
					sprintf(msg,"%-10s   %-15s  %ld            %s\n",tmp->login_name, tmp->alias, tmp->mess_sent,timestr);
					master_info_admin.send(msg);
					tmp = tmp->next;
				}
			}
		}
		if (!used)
			master_info_admin.send("\n\nNo rooms currently in use.\n\n");
                                // disconnect the bugger
		master_info_admin.close_sock(1);
	}
	return(0);
}


// Function:   check_allocated
// Purpose:    check the lists for clients with allocated ports that never connected
// Input:  none
// Output: checks for connect.  If successful, data structures are updated.  If not, it checks
//     against a timeout to see if port has expired.
// Author: Gregory Shaw
// Created:    4/9/95

int check_allocated(void)
{
	ClientInfo *tmp,*tmp2;      // new record
	int    x;

	// check for non-connected (but allocated) clients
	// delete client if it hasn't responded in 10 seconds
	if (clients_wo_connected > 0)
	{                           // run through the list trying to connect
		// attempt to connect
		#ifdef DEBUG
		printf("Checking allocated clients\n");
		fflush(stdout);
		#endif
		for (x=0; x< MAX_CHAT_ROOMS; x++)
		{
			if (roomlist[x].list != NULL)
			{
				tmp = roomlist[x].list;
				while (tmp != NULL)
				{
					if (!tmp->online)
						if (tmp->port->do_connect(0) == 0)
					{
						#ifdef DEBUG
						printf("Client connect successful.\n");
						fflush(stdout);
						#endif
						tmp->online = 1;
						clients_wo_connected--;
					}
					else
					{
						#ifdef DEBUG
						printf("Client connect failed.\n");
						fflush(stdout);
						#endif
						if (time(NULL) - tmp->connected > PORT_EXPIRATION_TIME)
						{       // give up
							#ifdef DEBUG
							printf("Gave up on client %s\n",tmp->login_name);
							fflush(stdout);
							#endif
							delete_client(&roomlist[x],tmp);
							clients_wo_connected--;
						}
					}
					tmp2 = tmp;
					tmp = tmp->next;
				}
			}
		}
	}
	return(0);
}


// Function:   check_info_connect
// Purpose:    check the master info port for connections and handle appropriately
// Input:  none
// Output: if the connect is successful, a status message it returned to the client
// Author: Gregory Shaw
// Created:    4/9/95

int check_info_connect(void)
{
	int x;
	int room,used;
	int client;
	char msg[255];
	char tmpstr[255];
	char *timestr;              // use time
	ClientInfo *tmp;

                                // connection?
	if (master_info.do_connect(0) == 0)
	{
		#ifdef DEBUG
		printf("got info connect\n");
		fflush(stdout);
		#endif
		used = 0;
		info_connects++;
		if (master_info.receive(msg) < 0)
		{
			master_info.close_sock(1);
			return(1);
		}
		if (sscanf(msg,"%d",&room) != 1)
		{
			error_logger.ap_log("rochatd: bad message from client");
                                // disconnect the bugger
			master_info.close_sock(1);
			return(1);
		}
		#ifdef DEBUG
		printf("got %s\n",msg);
		fflush(stdout);
		#endif
		if (room == -1)
		{                       // give information for all rooms
			#ifdef DEBUG
			printf("doing all rooms\n");
			fflush(stdout);
			#endif
			msg[0] = 0;
			for (x=0; x< MAX_CHAT_ROOMS; x++)
			{
				if (roomlist[x].room != -1)
				{
					client = 0;
					used++;
					tmp = roomlist[x].list;
					while (tmp != NULL)
					{
						client++;
						tmp = tmp->next;
					}
					sprintf(tmpstr,"%d:%d ",roomlist[x].room,client);
					strcat(msg,tmpstr);
				}
			}
			messages_sent++;
			if (!used)
			{
				#ifdef DEBUG
				printf("no data\n");
				fflush(stdout);
				#endif
				master_info.send("none");
			}
			else
				master_info.send(msg);
		}
		else
		{
			#ifdef DEBUG
			printf("doing one room\n");
			fflush(stdout);
			#endif
			// build information message
			for (x=0; x<MAX_CHAT_ROOMS; x++)
			{
				if (roomlist[x].room == room)
				{
					used++;
					master_info.send("User         Alias         Messages        On since\n");
					tmp = roomlist[x].list;
					while (tmp != NULL)
					{
						timestr = ctime(&tmp->connected);
						sprintf(msg,"%-10s   %-15s  %ld            %s\n",tmp->login_name, tmp->alias, tmp->mess_sent,timestr);
						master_info.send(msg);
						tmp = tmp->next;
					}
				}
			}
			if (!used)
			{
				master_info.send("none");
			}
		}
                                // disconnect the bugger
		master_info.close_sock(1);
	}
	return(0);
}


// Function:   client_master_connect
// Purpose:    check the master client port for connections and connect, as necessary.
// Input:  none
// Output: the socket is connected, and data structures are configured for the client
// Author: Gregory Shaw
// Created:    4/9/95

int client_master_connect(void)
{
	ClientInfo *tmp,*newrec;    // new record
	int    x,y;
	int    room;                // room
	char       msg[255];
                                // login name
	char   login[MAX_LOGIN_LENGTH];
                                // alias
	char   alias[MAX_ALIAS_LENGTH];
	char       tmpstr[255];     // string to output to file

                                // connection?
	if (master_client.do_connect(0) == 0)
	{
		#ifdef DEBUG
		printf("got connect\n");
		fflush(stdout);
		#endif
		connections++;
		clients++;              // more clients
		// receive room number and other info
		if (master_client.receive(msg) < 0)
		{
			master_client.close_sock(1);
			return(1);
		}
		messages_received++;
		// create new port
		if (sscanf(msg,"%d %s %s",&room,alias,login) != 3)
		{
			sprintf(tmpstr,"rochatd: bad message received on client master %s",msg);
			error_logger.ap_log(tmpstr);
                                // disconnect
			master_client.close_sock(1);
			return(0);
		}
		#ifdef DEBUG
		printf("information: %s\n",msg);
		fflush(stdout);
		#endif
		// connect to new port, if possible
		clients_wo_connected++; // not connected, but waiting
		x = 0;
		while (x < MAX_CHAT_ROOMS && roomlist[x].room != room)
			x++;
		if (x == MAX_CHAT_ROOMS)// found?
		{                       // nope.  allocate new room
			#ifdef DEBUG
			printf("no room found.  creating...\n");
			fflush(stdout);
			#endif
			x = 0;
			while (roomlist[x].room != -1 && x < MAX_CHAT_ROOMS)
				x++;
			if (x == MAX_CHAT_ROOMS)
			{                   // out of rooms
				error_logger.ap_log("rochatd: out of chat rooms!");
				error_logger.ap_log("rochatd: please recompile with more rooms");
                                // disconnect
				master_client.close_sock(1);
				return(0);
			}
			// init room data
                                // target doesn't count
			roomlist[x].target[0] = 0;
			roomlist[x].room = room;
			roomlist[x].movement = 1;
			roomlist[x].users = 0;
			roomlist[x].avg_participants = 1.0;
			roomlist[x].avg_lurkers = 1.0;
			roomlist[x].output_message = NULL;
			roomlist[x].list = NULL;
		}
		// allocate new client record
		#ifdef DEBUG
		printf("creating new client record...\n");
		fflush(stdout);
		#endif
		roomlist[x].users++;
                                // list not empty
		if (roomlist[x].list != NULL)
		{                       // go to end of list
			tmp = roomlist[x].list;
			while (tmp->next != NULL)
				tmp = tmp->next;
		}
		else
			tmp = roomlist[x].list;
		newrec = (ClientInfo *)malloc(sizeof(ClientInfo));
		if (newrec == NULL)
		{
			error_logger.ap_log("rochatd: out of memory!");
                                // disconnect
			master_client.close_sock(1);
			return(0);
		}
		if (roomlist[x].list == NULL)
	                                // start
			roomlist[x].list = newrec;
		else
			tmp->next = newrec; // add to end
		// add port object
		newrec->port = new bbsipc;
		newrec->mess_sent = 0;
		time(&newrec->connected);
		newrec->online = 0;
		strcpy(newrec->login_name,login);
		strcpy(newrec->alias,alias);
		newrec->next = NULL;
		// select a client port
		#ifdef DEBUG
		printf("selecting port...");
		fflush(stdout);
		#endif
		y = 0;
		while (portlist[y] == 1 && y < MAX_CLIENTS)
			y++;
		if (y == MAX_CLIENTS)
		{
			error_logger.ap_log("rochatd: out of client ports!");
                                // disconnect
			master_client.close_sock(1);
			delete newrec->port;
			free(newrec);
			return(0);
		}
		portlist[y]=1;          // allocate socket
		newrec->socket = y;     // save for de-allocation
		#ifdef DEBUG
		printf("...%d\n",y+CHAT_CLIENT_BASE);
		fflush(stdout);
		#endif
		if (newrec->port->open_sock(NULL, CHAT_CLIENT_BASE + y) != 0)
		{
			error_logger.ap_log("rochatd: unable to open client socket");
			delete newrec->port;
			free(newrec);
			return(0);
		}
		// send back to client
		sprintf(tmpstr,"%d",CHAT_CLIENT_BASE + y);
		master_client.send(tmpstr);
		messages_sent++;
		#ifdef DEBUG
		printf("sending port...%d\n",CHAT_CLIENT_BASE+y);
		fflush(stdout);
		#endif
                                // disconnect
		master_client.close_sock(1);
		#ifdef DEBUG
		printf("send complete.  Closing master.\n");
		fflush(stdout);
		#endif
                                // connection?
		if (newrec->port->do_connect(0) == 0)
		{                       // cool.  quick. (or broken)
			#ifdef DEBUG
			printf("got connect on new socket.\n");
			fflush(stdout);
			#endif
			newrec->online = 1;
                                // connected
			clients_wo_connected--;
		}
		#ifdef DEBUG
		else
		{
			printf("client didn't connect.\n");
			fflush(stdout);
		}
		#endif

	}
	return(0);
}


// Function:   delete_client
// Purpose:    delete a client from the room list
// Input:  room - the room with the bad client
//     bad - the bad client
// Output: the client is deleted.  If the client was the last client in the room, the
//     room is marked as inactive.
// Author: Gregory Shaw
// Created:    4/10/95

void delete_client(RoomInfo *room, ClientInfo *bad)
{
	ClientInfo *tmp;            // tmp buffer
	char tmpstr[100];

	#ifdef DEBUG
	printf("room users: %d bad name: %s\n",room->users,bad->login_name);
	fflush(stdout);
	#endif
	// send a 'goodbye' message to people in room
	tmp = room->list;

	sprintf(tmpstr,HASLEFTROOM,bad->alias);
	while (tmp != NULL)
	{
		if (tmp != bad)
		{
			tmp->port->send(tmpstr);
			messages_sent++;
		}
		tmp = tmp->next;
	}
	// now delete him from the list
	if (room->list == bad)      // trivial case - start of list
	{
		room->list = bad->next;
	}
	else
	{                           // look for the guy
		tmp = room->list;
		while (tmp->next != bad)
			tmp = tmp->next;
		tmp->next = bad->next;
	}
	// now free storage
	portlist[bad->socket] = 0;  // deallocate socket
	bad->port->close_sock(1);   // close sockets
	bad->port->close_sock(0);
	delete bad->port;           // delete ipc obj
	free(bad);                  // free record area
	if (room->users  == 1)      // last guy?
	{
		#ifdef DEBUG
		printf("Deleting room %d.\n",room->room);
		fflush(stdout);
		#endif
		// then mark room empty
		room->room = -1;
	}
	else
		room->users--;
	clients--;
};


// Function:   getout
// Purpose:    get out of the program.  Quick and dirty
// Author: Greg Shaw
// Created:    8/16/93

void getout(int sig)
{
	char   tmpstr[255];         // temporary string
	static int inalready = 0;   // if already in, don't do again
	int    x;

	if (!inalready)
	{
		inalready++;
                                // close for exit
		master_client.close_sock(1);
                                // close for exit
		master_info.close_sock(1);
                                // close for exit
		master_info_admin.close_sock(1);
                                // close for exit
		master_client.close_sock(0);
                                // close for exit
		master_info.close_sock(0);
                                // close for exit
		master_info_admin.close_sock(0);
		// now close the clients
		for (x=0; x<MAX_CHAT_ROOMS; x++)
		{
			if (roomlist[x].list != NULL)
			{
				while (roomlist[x].list != NULL)
				{
					delete_client(&roomlist[x],roomlist[x].list);
				}
			}
		}
		sprintf(tmpstr,"rochatd: Got signal %d.  Exiting.",sig);
		error_logger.ap_log(tmpstr);
		exit(0);                // exit to OS
	}
}


// Function:   init_data
// Purpose:    initialize the data structures used by the program
// Input:  none
// Output: the sockets are created or the program exits
// Author: Gregory Shaw
// Created:    4/9/95

void init_data(void)
{
	int x;

	messages_sent = 0;
	messages_received = 0;
	connections = 0;
	info_connects = 0;
	// init data structures
	for (x=0; x< MAX_CHAT_ROOMS; x++)
	{

		roomlist[x].room = -1;  // not in use
		roomlist[x].list = NULL;// no clients yet
		roomlist[x].users = 0;  // no users
		roomlist[x].movement = 0;
		roomlist[x].avg_participants = 0.0;
		roomlist[x].avg_lurkers = 0.0;
		roomlist[x].output_message = NULL;
	}
}


// Function:   init_masters
// Purpose:    initialize (create) the master sockets for the rochat daemon
// Input:  none
// Output: the sockets are created or the program exits
// Author: Gregory Shaw
// Created:    4/9/95

void init_masters(void)
{
	// create master sockets
	if (master_client.open_sock (NULL, CHAT_MASTER_PORT) != 0)
	{
		error_logger.ap_log("rochatd: Unable to open master client server socket.");
		exit(0);
	}
	if (master_info.open_sock (NULL, CHAT_INFO_MASTER_PORT) != 0)
	{
		error_logger.ap_log("rochatd: Unable to open information server socket.");
		exit(0);
	}
	if (master_info_admin.open_sock (NULL, CHAT_ADMIN_MASTER_PORT) != 0)
	{
		error_logger.ap_log("rochatd: Unable to open admin server socket.");
		exit (0);
	}
}


// Function:   shuffle_data
// Purpose:    look for message from clients and send messages to other clients
// Input:  messages are received from the clients
// Output: messages are sent to other clients in the same room(s)
// Author: Gregory Shaw
// Created:    4/9/95

int shuffle_data(void)
{
	ClientInfo *tmp,*snd;       // tmp buffer
	char   tmpstr[255];
	int    x;


	if (clients>0)
	{
		for (x=0; x< MAX_CHAT_ROOMS; x++)
		{
                                // room in use?
			if (roomlist[x].room != -1)
			{
                                // empty?
				if (roomlist[x].list != NULL)
				{
					tmp = roomlist[x].list;
					while (tmp != NULL)
					{
                                // message available?
						if (tmp->port->msg_avail(0))
						{
							if (tmp->port->receive(tmpstr) < 0)
							{   // port closed
								#ifdef DEBUG
								printf("Connection closed.\n");
								fflush(stdout);
								#endif
								delete_client(&roomlist[x],tmp);
							}
							else// got a message
							{   // send to everybody in group
								messages_received++;
								#ifdef DEBUG
								printf("Got %s.\n",tmpstr);
								fflush(stdout);
								#endif
								tmp->mess_sent++;
								snd = roomlist[x].list;
								while (snd != NULL)
								{
									snd->port->send(tmpstr);
									messages_sent++;
									snd = snd->next;
								}
							}
						}
						tmp = tmp->next;
					}
				}
			}
		}
	}
	return(0);
}


// Function:   main
// Purpose:        the main calling routine for the error logger daemon
// Input:      socket connections will connect with program.
// Output:     all data sent to this daemon will be logged to a generic file
// Author:     Greg Shaw
// Created:        7/11/93

main()                          // no arguments
{
	int    x;
	struct timeval waittime;



	waittime.tv_sec = 0;
	#ifdef DEBUG
	printf("rochatd init...\r\n");
	fflush(stdout);
	#endif
	time(&boot);
	for (x=1; x<15; x++)
		signal(x,&getout);
	for (x=0; x< MAX_CLIENTS; x++)
		portlist[x] = 0;        // port not used yet

	// announce startup
	error_logger.ap_log("rochatd (re) started");

	init_masters();             // initialize master ports
	init_data();                // clear data structures

	// Ok.  Data clean.  Let's start this beast!
	while (1)                   // a shutdown or other signal will kill this beast
	{
		client_master_connect();// check for incoming clients
		check_allocated();      // check for allocated (but not used used) connections
		check_info_connect();   // check for client information connect
		check_admin_connect();  // check for admin information connect
		shuffle_data();         // look for messages and shuffle to clients
		if (clients)	// don't use CPU if no clients
			waittime.tv_usec = 50;     // 50msec
		else
			waittime.tv_usec = 800;     // 800msec
		select(0,NULL,NULL,NULL,&waittime);
	}
}


#endif







