/* $Id: srvclient.c,v 1.23 2000/09/07 19:30:47 jared Exp $ */
#include "config.h"

bool inhere = FALSE;

void client_poll();

void send_stat_start(struct clientstatus client, struct hostinfo *here)
{
	char buff[256];

	if (here == NULL)
		return; /* shouldn't happen, just doing sanity checking */

	if (here->lastcheck != 0)
	{
		/* down in some fashion */
		snprintf(buff, 256, "%s:%d:%d:%d:%ld:%d:%ld", here->hostname,
			here->type, here->port, here->lastcheck,
			here->downct, here->contacted, here->deathtime);

		/* write it out the socket */
		sendline(client.filedes, buff);
	}  else if (here->child != NULL) {
		/* stat of the kids only if parent is up */
		send_stat_start(client, here->child);
	}

	if (here->sibling != NULL)
	{
		/* stat of the siblings */
		send_stat_start(client, here->sibling);
	}
	return;
}

int	send_stat(struct clientstatus client)
{
	send_stat_start(client, currenthead);
	if (sendline(client.filedes, "333") == -1)
	{
		print_err(0, "unable to send message to client");
		client.filedes = -1;
		return -1;
	}

	return 0;
}

void	send_conf_det(int cli, struct hostinfo *here)
{
	char buffer[32768];
	char buffer2[32768];

	if (here == NULL)
		return;

	snprintf(buffer, 32767, "%s %s %d \"%s\" \"%s\" \"%s\" ", here->hostname, 
		type_to_name(here->type), here->port, here->username, 
		here->password, here->message);
	if (here->command != NULL)
	{
		snprintf(buffer2, 32767, "%s\"%s\" ", buffer, here->command);
		memcpy(buffer, buffer2, 32768);
	}
	if (here->contact != NULL)
	{
		snprintf(buffer2, 32767, "%s%s", buffer, here->contact);
		memcpy(buffer, buffer2, 32768);
	}

	if (here->child != NULL)
		strcat(buffer, " {");

	sendline(cli, buffer);
	
	send_conf_det(cli, here->child);

	if (here->child != NULL)
		sendline(cli, "}");

	send_conf_det(cli, here->sibling);
}

int	send_conf(struct clientstatus *client)
{
	if (client->authlvl  <= 0)
	{
		sendline(client->filedes, "444 Permission Denied");
		return 0;
	}
	send_conf_det(client->filedes, currenthead);

	/* done printing config */
	if (sendline(client->filedes, "333") == -1)
	{
		print_err(0, "unable to send message to client");
		return -1;
	}
	return 1;
}

int	do_upd(struct clientstatus *client, char *buff)
{
	if (sendline(client->filedes, "444") == -1)
	{
		print_err(0, "unable to send message to client");
		return -1;
	}
	return 0;
}

/*
 * AUTH xxx
 *
 * compares xxx against authkey to determine if they should be able
 * to authenticate and perform some functions.
 */ 
int	do_auth(struct clientstatus *client, char *buff)
{
	if (authkey != NULL)
	{
		if (strcmp(authkey, buff+5) == 0)
		{
			client->authlvl=1;
			sendline(client->filedes, "333 Good Authentication");
			return 1;
		}
	}
        if (sendline(client->filedes, "444") == -1)
        {
                print_err(0, "unable to send message to client");
                return -1;
        }

        return 0;
}

void send_uptime(struct clientstatus *client)
{
	char buffer[256];

	snprintf(buffer, 256, "Uptime = %s", str_difftime_sec(boottime, time(NULL)));
	sendline(client->filedes, buffer);
}

/* CLIENT managment code */
void	do_service(struct clientstatus *here, char *buff)
{
	/* Local buffer */
	char lb[256];
	if (debug)
	{
		sprintf(lb, "[%s]:%s",here->ip, buff);
		print_err(0, "%s", lb);
	}

	if (strncmp(buff, "STAT", 4) == 0)
	{
		send_stat(*here);
	}
	else if (strncmp(buff, "UPTIME", 6) ==0)
	{
		send_uptime(here);
	}
#ifdef DEBUG
	else if (strncmp(buff, "NFD", 3) == 0)
	{
		snprintf(lb, 256, "%d is the next FD- %d queued", nextfd(), numqueued);
		sendline(here->filedes, lb);
		sendline(here->filedes, "what do you think about that?");
	}
	else if (strncmp(buff, "ABORT", 5) == 0)
	{
		/* dump a core or something */
		ABORT();
	}
	else if (strncmp(buff, "KILLIT", 6) == 0)
	{
		stop_daemon();
	}
#endif /* DEBUG */
	else if (strncmp(buff, "QUIT", 4) == 0)
	{
		/* Send a quit message to far side */
		sendline(here->filedes, "333 Good Bye, please come again");
		/* Disallow any further communication */
		shutdown(here->filedes, 2);
		close(here->filedes);
		here->filedes = -1;
	}
	else if (strncmp(buff, "DEBUG", 5) == 0)
	{
		/* Toggle Debugging */
		if (debug == 1)
			debug = 0;
		else 
			debug = 1;
		sendline(here->filedes, "Toggled debugging");
		print_err(0, "Toggled debugging at client request");
	}
	else if (strncmp(buff, "CONF", 4) == 0)
	{
		send_conf(here);
	}
#ifdef DEBUG
	else if (strncmp(buff, "EXPIREDNS", 9) == 0)
	{
		expire_dns();
	}
	else if (strncmp(buff, "PRINTQ", 6) == 0)
	{
		print_queue(here->filedes);
	}
#endif /* DEBUG */
	else if (strncmp(buff, "VERS", 4) == 0)
	{
		sendline(here->filedes, VERS);
	}
	else if (strncmp(buff, "AUTH", 4) == 0)
	{
		do_auth(here, buff);
	}
	else if (strncmp(buff, "UPD", 3) == 0)
	{
		do_upd(here, buff);
	}
	else
	{
		if (sendline(here->filedes, "444 - Unk")  == -1)
		{
			print_err(0, "error sending message to client");
			close(here->filedes);
			here->filedes = -1;
		}
	}
}

void timeout_clients()
{
	struct clientstatus *here;
	time_t now;

	time(&now);
	
	if (clienthead == NULL)
		return;

	for (here = clienthead->next; here != NULL ; here = here->next)
		if ((now - here->lastactivity) > inactivetime )
		{
			sendline(here->filedes, "444 - Timed out");
			if (here->filedes != -1)
				if (close(here->filedes) == -1)
					perror("waah! closing stuff\n");
			here->filedes = -1;
		}
}

void	dead_client_cleanup()
{
	/* free clients with filedes = -1 */

	struct clientstatus *here, *last, *freeme;

	if (clienthead == NULL)
		return;

	last = clienthead;
	freeme = NULL;
        for (here = clienthead->next; here != NULL ; here = here->next)
        {
		if (freeme != NULL)
		{
			FREE(freeme);
			freeme = NULL;
		}
		
		if (here->filedes == -1)
		{
			freeme = here;	
			last->next = here->next;
		} else {
			last = here;
		}
	}
	if (freeme != NULL)
	{
		FREE(freeme);
		freeme = NULL;
	}
}

void	setup_client()
{
        int msgsock; /* message socket for talking back and forth */
        struct sockaddr_in remote;
        int size;
	struct clientstatus *thisclient, *linkafter;

        size = sizeof (remote );

        /* do the accept() on the socket, then take care of it */
        msgsock = accept(clienthead->filedes,(struct sockaddr *)&remote,&size);

        if (msgsock == -1)
        {
                perror("accept");
                return;
        }

        /* figure out who's connecting and log it */
        print_err(0, "accepting connection from %s", inet_ntoa(remote.sin_addr));


        /* print our greeting banner */
        if (debug)
	{
		print_err(0, "sending hello banner");
	}

        sendline(msgsock, "111 - v1.0 Ready");

	if (debug)
	{
		print_err(0, "setting up client structure");
	}

	thisclient = MALLOC(sizeof(struct clientstatus), "new_client");

	memset(thisclient, 0, sizeof(struct clientstatus));

	time(&thisclient->lastactivity); /* set current time */
	thisclient->filedes = msgsock; /* save accept()'ed fd */
	strcpy(thisclient->ip, inet_ntoa(remote.sin_addr)); /* save source ip */
	thisclient->authlvl = 0; /* no auth yet */
	thisclient->clientver = 0; /* no client vers yet, sent ours */
	thisclient->next = NULL; /* next client = NOTHING */

	linkafter = clienthead; /* start to walk client list */

	while (linkafter->next != NULL) /* until next client is null */
		linkafter = linkafter->next; /* do walk */
	linkafter->next = thisclient; /* link us at the end of it */
}

void	check_for_new_clients()
{
	/* check the specified socket for awating connections, if so, take
	   care of them */
	fd_set rd, wr, except;
	struct timeval local_timeout;
	
	if (clienthead->filedes == -1)
	/* do nothing */
		return;

	local_timeout.tv_sec = 0;
	local_timeout.tv_usec = 0;

	FD_ZERO(&wr);
	FD_ZERO(&except);
	FD_ZERO(&rd);
	FD_SET(clienthead->filedes, &rd);

	if (debug)
	{
		print_err(0, "Checking for a client");
	}

	while (select(clienthead->filedes+1,&rd,&wr,&except,&local_timeout)!=0)
		setup_client();

}

void	service_clients()
{
	struct clientstatus *here;
	char throwmeout[256], buff[256];
	int did_service = 1;
	
	while  (did_service == 1)
	{
		did_service = 0;
		/* FIXME */
		/* needs to do FD_ISSET with a select instead of
			using data_waiting_read */

		for (here=clienthead->next;here!=NULL;here=here->next)
		{
			if (here->filedes == -1)
				continue;
			if (data_waiting_read(here->filedes, 0) != 0)
			{
				
				getline_tcp(here->filedes, buff);
				getline_tcp(here->filedes, throwmeout);
#ifdef BUG /* Possible bug */
				did_service = 1;
#endif /* BUG */
				do_service(here, buff);
				time(&here->lastactivity);
			}
		}
	}
}

void	client_poll()
{
        fd_set rd, wr, except;
        struct timeval local_timeout;
	struct clientstatus *here;

	if (inhere)
	{
		return;
	}

	inhere = TRUE;

        if (clienthead->filedes == -1)
	{
	        /* do nothing */
                return;
	}

        local_timeout.tv_sec = 0;
        local_timeout.tv_usec = 0;

        FD_ZERO(&wr);
        FD_ZERO(&except);
        FD_ZERO(&rd);

	/* check for new clients */
	check_for_new_clients();

	/* process old clients new data */
	service_clients();

	/* timeout old clients */
	timeout_clients();

	/* free dead client memory */
	dead_client_cleanup();

	for (here = clienthead; here != NULL; here = here->next)
		if (here->filedes != -1)
	        	FD_SET(here->filedes, &rd);

	if (select(clienthead->filedes+1,&rd,&wr,&except,&local_timeout) != 0)
	{
		if (FD_ISSET(clienthead->filedes, &rd))
		{
		        check_for_new_clients();
		}
		service_clients();
	}

	inhere = FALSE;
}
