// Filename:   robcastd.C
// Contents:   the broadcast daemon
// Author:     Gregory Shaw
// Created:    4/17/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 _ROBCASTD_C_
#define _ROBCASTD_C_

#include "bbshdr.h"

#undef DEBUG

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

int add_broadcast(char *, char *);
int check_admin_connect(void);
int check_broadcast_connect(void);
void delete_broadcast(BroadCast *);
void expire_messages(void);
void getout(int);
void init_data(void);
void init_masters(void);

// declared global so that the 'getout' function can close the socket
bbsipc master_info_admin;       // master admin information port
bbsipc master_broadcast;        // master broadcast port
errlog error_logger;            // error logging connection
BroadCast *bclist;              // list of broadcasts
long   broadcast_sent;
long   broadcast_received;
long   private_broadcast_received;
long   private_broadcast_sent;
long   polls;
time_t  boot;                   // startup time

// Function:   add_broadcast
// Purpose:    add a broadcast to the broadcast list
// Input:  to - the recipient of the message ('all' or a name)
//     msg - the message to send
// Output: none
// Author: Gregory Shaw
// Created:    4/9/95

int add_broadcast(char *to, char *msg)
{
	BroadCast *tmp;

	if (tmp = (BroadCast *)malloc(sizeof(BroadCast)), tmp == NULL)
	{
		error_logger.ap_log("robcastd: Out of memory!");
	}
	else
	{
                                // save time
		time(&tmp->when_received);
		// save to and message
		if (tmp->to = (char *)malloc(strlen(to)+1), tmp->to == NULL)
		{
			error_logger.ap_log("robcastd: Out of memory!");
			return(-1);
		}
		strcpy(tmp->to,to);
		if (tmp->msg = (char *)malloc(strlen(msg)+1), tmp->msg == NULL)
		{
			error_logger.ap_log("robcastd: Out of memory!");
			return(-1);
		}
		strcpy(tmp->msg,msg);
		// add to list
		tmp->sent = NULL;       // no receivers yet
		if (bclist == NULL)     // empty list?
		{
			bclist = tmp;
			tmp->next = NULL;
		}
		else
		{
			tmp->next = bclist;
			bclist = tmp;
		}
	}
	return(0);
}


// 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)
{
	char msg[255];
	char *timestr;              // boot time

                                // connection?
	if (master_info_admin.do_connect(0) == 0)
	{
		// build information message
		// send to client
		timestr = ctime(&boot);
		master_info_admin.send("The rocat BBS System: robcastd\n");
		sprintf(msg,"Up since: %s\n",timestr);
		master_info_admin.send(msg);
		master_info_admin.send("Broadcasts:\n");
		sprintf(msg,"    Received: %ld\n",broadcast_received);
		master_info_admin.send(msg);
		sprintf(msg,"    Sent: %ld\n",broadcast_sent);
		master_info_admin.send(msg);
		sprintf(msg,"    Private Received: %ld\n",private_broadcast_received);
		master_info_admin.send(msg);
		sprintf(msg,"    Private Sent: %ld\n",private_broadcast_sent);
		master_info_admin.send(msg);
		sprintf(msg,"\nPolls: %ld\n",polls);
		master_info_admin.send(msg);
                                // disconnect the bugger
		master_info_admin.close_sock(1);
	}
	return(0);
}


// Function:   check_broadcast_connect
// Purpose:    check for an broadcast connection and handle appropriately
// Input:  none
// Output: checks for connect.  if successful, a broadcast is sent to
//     either all users or a particular user
// Author: Gregory Shaw
// Created:    4/9/95
// Note:   this is the first incarnation.  It will be necessary to put
//     in a second information check to send messages to users
//     who are not in the chat subsystem.

int check_broadcast_connect(void)
{
	char msg[255];
	char login[MAX_LOGIN_LENGTH];
	BroadCast *bctmp,*bctmp2;
	BCUser     *butmp;
	int    sent;

                                // connection?
	if (master_broadcast.do_connect(0) == 0)
	{
		#ifdef DEBUG
		printf("Got connect.\n");
		fflush(stdout);
		#endif
		sent = 0;
		// get new message
		if (master_broadcast.receive(login) < 0)
		{
			master_broadcast.close_sock(1);
			return(1);
		}
		if (master_broadcast.receive(msg) < 0)
		{
			master_broadcast.close_sock(1);
			return(1);
		}
		if (!strcmp(msg,BROADCAST_POLL))
		{                       // someone is polling for a message
			#ifdef DEBUG
			printf("poll from %s\n",login);
			fflush(stdout);
			#endif
			polls++;
			bctmp = bclist;
			while (bctmp != NULL)
			{
				if (!strcmp(bctmp->to,login))
				{               // to this user
					#ifdef DEBUG
					printf("sending message directly to %s.\n",login);
					fflush(stdout);
					#endif
					private_broadcast_sent++;
					master_broadcast.send(bctmp->msg);
					bctmp2 = bctmp->next;
					delete_broadcast(bctmp);
                                // only send 1 message
					bctmp = NULL;
					sent++;
					continue;   // skip increment
				}
				else if (!strcmp(bctmp->to,"all"))
				{               // message to all
					#ifdef DEBUG
					printf("message to all.\n");
					fflush(stdout);
					#endif
					butmp = bctmp->sent;
					while (butmp != NULL)
					{
						if (!strcmp(butmp->login,login))
							break;
						butmp = butmp->next;
					}
                                // not on list
					if (butmp == NULL)
					{
						#ifdef DEBUG
						printf("sending to user.\n");
						fflush(stdout);
						#endif
						butmp = (BCUser *)malloc(sizeof(BCUser));
						if (butmp == NULL)
						{
							error_logger.ap_log("robcastd: out of memory!");
							break;
						}
						strcpy(butmp->login,login);
						// add to start of list
						butmp->next = bctmp->sent;
						bctmp->sent = butmp;
						master_broadcast.send(bctmp->msg);
						broadcast_sent++;
						sent++;
                                // sent something, now stop
						bctmp = NULL;
						continue;
					}

				}
				bctmp = bctmp->next;
			}
			if (!sent)          // nothing sent?
			{                   // send a null message
				master_broadcast.send(NO_BROADCAST);
			}
		}
		else                    // message coming in
		{
			broadcast_received++;
			#ifdef DEBUG
			printf("got new message %s for %s\n",msg,login);
			fflush(stdout);
			#endif
			if (!strcmp(login,"all"))
				broadcast_received++;
			else
				private_broadcast_received++;
			add_broadcast(login, msg);
		}
		#ifdef DEBUG
		printf("closing socket\n");
		fflush(stdout);
		#endif
                                // disconnect the bugger
		master_info_admin.close_sock(1);
	}
	return(0);
}


// Function:   delete_broadcast
// Purpose:    delete a broadcast message
// Input:  bad - the broadcast to delete
// Output: a message is deleted
// Author: Gregory Shaw
// Created:    4/9/95

void delete_broadcast(BroadCast *bad)
{
	BroadCast  *tmp;
	BCUser     *bctmp,*bctmp2;

	if (bad == bclist)          // head of list
	{
		bclist = bad->next;
	}
	else
	{
		tmp = bclist;
		while (tmp->next != bad)
			tmp = tmp->next;
		tmp->next = bad->next;
	}
	bctmp = bad->sent;
	while (bctmp != NULL)
	{
		bctmp2 = bctmp;
		bctmp = bctmp->next;
		free(bctmp2);
	}
	free(bad->msg);
	free(bad->to);
	free(bad);
}


// Function:   expire_message
// Purpose:    expire broadcasts after a default time period
// Input:  bad - the broadcast to delete
// Output: a message is deleted
// Author: Gregory Shaw
// Created:    4/9/95

void expire_messages(void)
{
	BroadCast  *tmp,*tmp2;

	tmp = bclist;
	while (tmp != NULL)
	{
		if (time(NULL) - tmp->when_received > MAX_BROADCAST_TIMEOUT)
		{                       // old -- delete broadcast
			#ifdef DEBUG
			printf("deleting broadcast..\n");
			fflush(stdout);
			#endif
			tmp2 = tmp->next;   // save
                                // nuke
			delete_broadcast(tmp);
			tmp = tmp2;         // go on with list
		}
		else
			tmp = tmp->next;
	}
}


// 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
	BroadCast  *tmp,*tmp2;

	if (!inalready)
	{
		inalready++;
                                // close for exit
		master_info_admin.close_sock(1);
                                // close for exit
		master_broadcast.close_sock(1);
                                // close for exit
		master_broadcast.close_sock(0);
		// nuke data
		tmp = bclist;
		while (tmp != NULL)
		{
			free(tmp->msg);
			free(tmp->to);
			tmp2 = tmp;
			tmp = tmp->next;
			free(tmp2);
		}
		sprintf(tmpstr,"robcastd: 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)
{
	// init data structures
	bclist = NULL;              // nothing yet.
	broadcast_sent = 0;
	broadcast_received = 0;
	private_broadcast_sent = 0;
	private_broadcast_received = 0;
	polls = 0;
}


// 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_info_admin.open_sock (NULL, CHAT_BROADCAST_ADMIN_MASTER_PORT) != 0)
	{
		error_logger.ap_log("robcastd: Unable to open admin server socket.");
		exit (0);
	}
	if (master_broadcast.open_sock (NULL, CHAT_BROADCAST_PORT) != 0)
	{
		error_logger.ap_log("robcastd: Unable to open broadcast server socket.");
		exit (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("robcastd init...\r\n");
	fflush(stdout);
	#endif
	time(&boot);
	for (x=1; x<15; x++)
		signal(x,&getout);

	// announce startup
	error_logger.ap_log("robcastd (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
	{
                                // check for broadcast
		check_broadcast_connect();
		check_admin_connect();  // check for admin information connect
		expire_messages();      // expire broadcasts
		waittime.tv_usec = 300;     // 300msec
                select(0,NULL,NULL,NULL,&waittime);

	}
}


#endif







