/* AmiTrackServer.c -- a program to keep a running list of available Amigas. */

#define __stdargs

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include <exec/types.h>
#include <exec/io.h>
#include <exec/lists.h>
#include <exec/memory.h>

#include <clib/exec_protos.h>
#include <clib/alib_protos.h>
#include <clib/dos_protos.h>

#include <errno.h>
#include <inetd.h>
#include <sys/types.h>

#include <proto/socket.h>

#include <sys/errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/syslog.h>
#include <netdb.h>

#include <pragmas/socket_pragmas.h>
#include <devices/timer.h>

#include "AmiTrackShared.h"

struct Library * SocketBase = NULL;
struct Library * CxBase = NULL;  /* not used */

static ULONG ulPurgeDelay = AMITRACK_TIMEOUT_MINUTES * 60;
static struct TimerStuff  * Timer = NULL;
static struct SocketStuff * TCPStuff = NULL, * UDPStuff = NULL;
static struct ClientList  * clientList = NULL;

static void Cleanup(void);
static void HandleClientRequest(struct ClientList *, struct SocketStuff * sSocket);
static void HandleListRequest(struct ClientList *, struct SocketStuff * sSocket);
static void PurgeOldClients(struct ClientList *);

int main(int argc, char **argv)
{	
	BOOL BDone = FALSE, BPrintList;
	int nPort = SERVER_TCP_PORT;
	
	if ((argc == 2)&&(strcmp(argv[1],"?")==0))
	{
		printf("Usage:  AmiTrackServer [Port]\n");
		exit(5);
	}
	atexit(Cleanup);
	
	if (argc >= 2) nPort = atoi(argv[1]);
	nPort = nPort ? nPort : SERVER_TCP_PORT;
	
	UNLESS(SocketBase = OpenLibrary("bsdsocket.library", 2))
		TrackExit("Couldn't open socket.library v2+",RETURN_ERROR);
	UNLESS(Timer = SetupTimer(NULL)) 
		TrackExit("Couldn't setup timer.device.",RETURN_ERROR);
	UNLESS(clientList = SetupClientList(NULL))
		TrackExit("Couldn't create client list.",RETURN_ERROR);
	UNLESS(UDPStuff = SetupSocket(NULL, nPort, SOCK_DGRAM))
		TrackExit("Couldn't setup UDP socket.",RETURN_ERROR);
	UNLESS(TCPStuff = SetupSocket(NULL, nPort, SOCK_STREAM))
		TrackExit("Couldn't setup TCP socket.",RETURN_ERROR);
	listen(TCPStuff->fd,5);
	
	Signal(FindTask(NULL),SIGBREAKF_CTRL_D);
	SetTimer(Timer, ulPurgeDelay, 0);

	while(1)
	{
	  if (TrackFlagsSet() == FALSE) 
	      TrackWait(TCPStuff, UDPStuff, Timer, NULL, NULL, NULL);

	  BPrintList = FALSE;

      if (CheckTrackFlag(CODE_QUIT)) break;
	  if (CheckTrackFlag(CODE_TCP_SOCKET)) HandleListRequest(clientList,TCPStuff);
	  if (CheckTrackFlag(CODE_UDP_SOCKET)) 
	  {
		HandleClientRequest(clientList,UDPStuff);
		BPrintList = TRUE;
	  } 	
	  if (CheckTrackFlag(CODE_TIMER_EXPIRED))
	  {
 		PurgeOldClients(clientList); 	
		SetTimer(Timer, ulPurgeDelay,0);
		BPrintList = TRUE;
	  }
	  if (CheckTrackFlag(CODE_REFRESH)) BPrintList = TRUE;
	
	  if (BPrintList) 
	  {
		ClearTrackScreen();
		printf("%i Clients currently logged in to this server (port %i):\n",clientList->nListLength,nPort);
		PrintClientList(clientList);
	  } 
	}
}







/* Always called when AmiTrack exits, does any necessary cleanup */
void Cleanup(void)
{
	/* Shuts everything down (don't let the calls to Setup*() mislead you) */
	if (Timer) 	SetupTimer(Timer);
	if (TCPStuff) 	SetupSocket(TCPStuff,0,0);
	if (UDPStuff) 	SetupSocket(UDPStuff,0,0);
	if (clientList) SetupClientList(clientList);
	if (SocketBase) CloseLibrary(SocketBase);
}




/* Called when someone attempts to connect to us via a TCP stream.
   Dumps the current client list out to the connectee, then closes
   the stream so he knows we're done outputting. */
void HandleListRequest(struct ClientList * ClientList, struct SocketStuff * sSocket)
{
	struct sockaddr_in saFrom;
	LONG sSizeOf = sizeof(struct sockaddr_in);
	LONG sNewSocket = accept(sSocket->fd, &saFrom, &sSizeOf);
	struct Client * client = (struct Client *) (ClientList->list.lh_Head);
	
	printf("                                                             \r");
	printf("Sending Client list to [%s]...\r", Inet_NtoA(saFrom.sin_addr.s_addr));
	fflush(stdout);

	/* Now send the list */
	while (client->node.ln_Succ)
	{
		send(sNewSocket, client->hostname, strlen(client->hostname)+1, 0L);
		send(sNewSocket, client->comment,  strlen(client->comment) +1, 0L);
		send(sNewSocket, (char *) &client->ulIPAddress, sizeof(ULONG), 0L);
		client = (struct Client *)client->node.ln_Succ;
	}
	CloseSocket(sNewSocket);
}


/* Go through the client list, and remove anyone who has a timestamp
   that is older than tCutoff. */
void PurgeOldClients(struct ClientList * ClientList)
{
	time_t tCutOff = time(NULL) - ulPurgeDelay;
	struct Client * client = (struct Client *) ClientList->list.lh_Head;
	struct Client * next;
	
	while (client->node.ln_Succ)
	{
		next = client->node.ln_Succ;
		if (client->tDateStamp < tCutOff) 
		{
			Remove((struct Node *)client); FreeClient(client); ClientList->nListLength--;
		}
		client = next;
	}
}


/* Called when we are sent a UDP packet by some client */
void HandleClientRequest(struct ClientList * ClientList, struct SocketStuff * sSocket)
{
	char sRecvBuf[1500], * clientname;
	static struct sockaddr_in saFrom;
	LONG lAddrSize = sizeof(struct sockaddr_in), lReqs;
	struct Client * client;
	struct hostent * hp;
	int nBufLen;
	ULONG ulIPAddress;
	
	if ((nBufLen = recvfrom(sSocket->fd, sRecvBuf, sizeof(sRecvBuf), 0L, &saFrom, &lAddrSize)) < 0) 
	{
		printf("Recvfrom failed.\n");
		return;
	}
	
	ulIPAddress = saFrom.sin_addr.s_addr;
	hp = gethostbyaddr((caddr_t)&saFrom.sin_addr, sizeof(saFrom.sin_addr), AF_INET);
    clientname = ((hp)&&(hp->h_name)) ? hp->h_name : Inet_NtoA(ulIPAddress);
	memcpy(&lReqs, sRecvBuf, sizeof(ULONG));

	UNLESS(client = GetClient(ClientList, ulIPAddress, clientname, 
      (lReqs & REQ_COMMENT) ? PastSpaces(RemoveUnprintableChars(&sRecvBuf[sizeof(ULONG)])) : NULL))
	{
		printf("Add/retrieve of client listing failed.\n");
		return;
	}

	if (lReqs & REQ_BYE) RemoveClient(ClientList, clientname);
	client->tDateStamp = time(NULL);
}




