/****************************************************************
 * This software is Copyright (C) 1986-1998 by                  *
 *                                                              *
 * F6FBB - Jean-Paul ROUBELAT,  jpr@f6fbb.org                   *
 * 6, rue George Sand                                           *
 * 31120 - Roquettes - France                                   *
 *                                                              *
 * License to copy and use this software is granted for         *
 * non-commercial use provided that it is identified as         *
 *                                                              *
 * "FBB packet-radio BBS software by Jean-Paul ROUBELAT, F6FBB" *
 *                                                              *
 * in all material mentioning or referencing this software      *
 * or this function.                                            *
 *                                                              *
 * These notices must be retained in any copies of any part of  *
 * this documentation and/or software.                          *
 *                                                              *
 * Parts of code have been taken from many other softwares.     *
 * Thanks for the help.                                         *
 ****************************************************************/


/*
 * Serveur de communication socket pour connection console
 *
 */

#include <serv.h>

#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <net/if.h>
#include <linux/if_ether.h>
#include <netinet/in.h>

#define WAITINFO	0
#define WAITPASS	1
#define CONNECTED	2
#define DISCONNECT	3

#define ORB_MSGS	1
#define ORB_STATUS	2
#define ORB_NBCNX	4
#define ORB_LISTCNX	8
#define ORB_MONITOR	16
#define ORB_CONSOLE	32
#define ORB_CHANNEL	64
#define ORB_XFBBX	128

extern void process (void);

/* extern void usleep(unsigned long); */

static int orb_fd = -1;

typedef struct _OrbClient
{
	int fd;
	int state;
	int monitor;
	int channel;
	long mask;
	time_t cle;
	char callsign[12];
	struct _OrbClient *next;
}
OrbClient;

static OrbClient *client_head = NULL;

static OrbClient *orb_add_client (void)
{
	OrbClient *sptr;

	sptr = calloc (1, sizeof (OrbClient));
	if (sptr == NULL)
		return (NULL);

	sptr->next = client_head;
	client_head = sptr;

	return (sptr);
}

static void orb_del_client (OrbClient * cptr)
{
	OrbClient *sptr;
	OrbClient *prev;

	prev = NULL;
	sptr = client_head;
	while (sptr)
	{
		if (sptr == cptr)
		{
			if (prev)
				prev->next = sptr->next;
			else
				client_head = sptr->next;
			free (sptr);
			break;
		}
		prev = sptr;
		sptr = sptr->next;
	}
}

static int orb_new_connection (int fd)
{
	int addr_len;
	struct sockaddr_in sock_addr;
	OrbClient *sptr;

	sptr = orb_add_client ();

	addr_len = sizeof (sock_addr);

	sptr->fd = accept (fd, (struct sockaddr *) &sock_addr, &addr_len);
	sptr->state = WAITINFO;

	return (sptr->fd);
}

static void orb_process_data (OrbClient * sptr)
{
	int i, j;
	int nb;
	static char buffer[1024];
	static int nbrcv = 0;
	char call[256];
	char pass[256];
	char *ptr;

	nb = read (sptr->fd, buffer + nbrcv, sizeof (buffer) - nbrcv);
	if (nb == -1)
		return;

	if (nb == 0)
	{
		/* Client is disconnected */
		if (sptr->mask & ORB_CONSOLE)
		{
			/* Disconnect from console */
			deconnexion (CONSOLE, 0);
		}
		else
		{
			close (sptr->fd);
			orb_del_client (sptr);
		}
		nbrcv = 0;
		return;
	}

	nbrcv += nb;

	if ((nbrcv == 3) && (buffer[0] == '\0'))
	{
		int mask;
		int old_mask = sptr->mask;
		int ack;

		/*commande */
		switch (buffer[1])
		{
		case 0:
			/* Changement de masque */
			mask = buffer[2] & 0xff;
			if ((mask & ORB_CONSOLE) != (sptr->mask & ORB_CONSOLE))
			{
				if (mask & ORB_CONSOLE)
				{
					FILE *fptr;

					if ((fptr = fopen (d_disque ("etat.sys"), "r+t")) != NULL)
					{
						fprintf (fptr, "%-6s-%X\n",
								 cons_call.call, cons_call.num);
						ferme (fptr, 74);
					}

					/* Console connection */
					if (v_tell && connect_tell ())
					{
						music (0);
						ack = 1;
					}
					else if (!connect_console ())
					{
						ack = 2;
					}
					else
					{
						ack = 1;
					}
				}
				else
				{
					/* Console deconnection */
					deconnexion (CONSOLE, 0);
					ack = 0;
					nbrcv = 0;
					return;
				}
				buffer[0] = ORB_XFBBX;
				buffer[1] = 0;
				buffer[2] = 1;
				buffer[3] = 0;
				buffer[4] = ack;
				write (sptr->fd, buffer, 5);
			}
			sptr->mask = mask;
			if ((mask & ORB_MSGS) != (old_mask & ORB_MSGS))
			{
				reset_msgs ();
				aff_msg_cons ();
			}
			if ((mask & ORB_STATUS) != (old_mask & ORB_STATUS))
				FbbMem (1);
			if ((mask & ORB_NBCNX) != (old_mask & ORB_NBCNX))
				fbb_list (1);
			if ((mask & ORB_LISTCNX) != (old_mask & ORB_LISTCNX))
				fbb_list (1);
			break;
		}
		nbrcv = 0;
		return;
	}

	/* Check if the whole line is received */
	buffer[nbrcv] = '\0';
	if (strchr (buffer, '\n') == NULL)
		return;

	switch (sptr->state)
	{
	case WAITINFO:

		/* Mask of services */
		sscanf (buffer, "%ld %d %s", &sptr->mask, &sptr->channel, call);
		call[11] = '\0';
		strcpy (sptr->callsign, call);
		sptr->cle = time (NULL);
		sprintf (call, "%010ld", sptr->cle);
		write (sptr->fd, call, strlen (call));
		sptr->state = WAITPASS;
		break;

	case WAITPASS:

		/* Callsign sans SSID */
		strcpy (call, sptr->callsign);
		if ((ptr = strchr (call, '-')))
			*ptr = '\0';

		/* Password */
		sscanf (buffer, "%s", pass);

		if (comp_passwd (call, pass, sptr->cle))
		{
			cons_call.num = extind (strupr (sptr->callsign), cons_call.call);
			reset_msgs ();
			FbbMem (1);
			aff_msg_cons ();
			fbb_list (1);
			if (sptr->mask & ORB_CONSOLE)
			{
				FILE *fptr;

				if ((fptr = fopen (d_disque ("etat.sys"), "r+t")) != NULL)
				{
					fprintf (fptr, "%-6s-%X\n",
							 cons_call.call, cons_call.num);
					ferme (fptr, 74);
				}

				/* Console connection */
				if (v_tell && connect_tell ())
				{
					music (0);
				}
				else if (!connect_console ())
				{
					char *txt = "Console already connected !\n\n";
					int len = strlen (txt) + 3;

					strcpy (buffer + 7, txt);

					/* Header */
					buffer[0] = ORB_CONSOLE;
					buffer[1] = 0;
					buffer[2] = (len) & 0xff;
					buffer[3] = (len) >> 8;

					buffer[4] = CONSOLE;
					buffer[5] = W_CNST;
					buffer[6] = 0;

					write (sptr->fd, buffer, len + 4);
					close (sptr->fd);
					orb_del_client (sptr);
				}
			}
			sptr->state = CONNECTED;
		}
		else
		{
			char *txt = "Callsign/Password error !\n\n";
			int len = strlen (txt) + 3;

			strcpy (buffer + 7, txt);

			/* Header */
			buffer[0] = ORB_CONSOLE;
			buffer[1] = 0;
			buffer[2] = (len) & 0xff;
			buffer[3] = (len) >> 8;

			buffer[4] = CONSOLE;
			buffer[5] = W_CNST;
			buffer[6] = 0;

			write (sptr->fd, buffer, len + 4);
			close (sptr->fd);
			orb_del_client (sptr);
		}
		break;

	case CONNECTED:

		if (sptr->mask & ORB_CONSOLE)
		{
			for (i = 0, j = 0; i < nbrcv; i++)
			{
				if (buffer[i] == '\r')
					continue;

				if (buffer[i] == '\n')
					buffer[j++] = '\r';
				else
					buffer[j++] = buffer[i];
			}

			console_inbuf (buffer, j);
		}

		break;
	}

	nbrcv = 0;
}

static void orb_process (void)
{
	int nb;
	int maxfd;
	fd_set tcp_read;
	fd_set tcp_write;
	fd_set tcp_excep;
	struct timeval to;
	OrbClient *sptr;

	if (orb_fd == -1)
		return;

	to.tv_sec = to.tv_usec = 0;

	FD_ZERO (&tcp_read);
	FD_ZERO (&tcp_write);
	FD_ZERO (&tcp_excep);

	maxfd = orb_fd;
	FD_SET (orb_fd, &tcp_read);

	sptr = client_head;
	while (sptr)
	{
		FD_SET (sptr->fd, &tcp_read);
		if (sptr->fd > maxfd)
			maxfd = sptr->fd;
		sptr = sptr->next;
	}

	nb = select (maxfd + 1, &tcp_read, NULL, NULL, &to);

	if (nb == -1)
	{
		perror ("orb_select");
		return;
	}

	if (nb == 0)
	{
		/* Rien a traiter */
		return;
	}

	if (FD_ISSET (orb_fd, &tcp_read))
	{
		/* Nouvelle connection client */
		orb_new_connection (orb_fd);
	}

	sptr = client_head;
	while (sptr)
	{
		/* On sauvegarde le next car le pointeur de client peut etre
		   * libere pendant le traitement en cas de deconnexion */
		OrbClient *ptemp = sptr->next;

		if (FD_ISSET (sptr->fd, &tcp_read))
		{
			orb_process_data (sptr);
			is_idle = 0;
		}
		sptr = ptemp;
	}

	return;
}

int fbb_orb (char *service, int port)
{
	int val;
	int len;
	struct sockaddr_in sock_addr;

	if ((orb_fd = socket (AF_INET, SOCK_STREAM, 0)) < 0)
	{
		perror ("socket_r");
		return (0);
	}

	val = 1;
	len = sizeof (val);
	if (setsockopt (orb_fd, SOL_SOCKET, SO_REUSEADDR, (char *) &val, len) == -1)
	{
		perror ("opn_tcp : setsockopt SO_REUSEADDR");
	}

	sock_addr.sin_family = AF_INET;
	sock_addr.sin_addr.s_addr = 0;
	sock_addr.sin_port = htons (port);

	if (bind (orb_fd, (struct sockaddr *) &sock_addr, sizeof (sock_addr)) != 0)
	{
		perror ("opn_tcp : bind");
		close (orb_fd);
		return (0);
	}

	if (listen (orb_fd, SOMAXCONN) == -1)
	{
		perror ("listen");
		close (orb_fd);
		return (0);
	}

	fprintf (stderr, "xfbbC/X server running ...\n");
	fprintf (stderr, "xfbbd ready and running ...\n");
	fflush (stderr);

	for (;;)
	{
		orb_process ();
		process ();
		if (is_idle)
		{
			usleep (10);
		}
		else
		{
			is_idle = 1;
		}
	}

	return 1;
}

static void orb_send_data (char *buffer, int lg, int mask)
{
	OrbClient *sptr = client_head;

	while (sptr)
	{
		if (sptr->mask & mask)
		{
			switch (mask)
			{
			case ORB_MONITOR:
			case ORB_CONSOLE:
			case ORB_CHANNEL:
				if (((mask == ORB_MONITOR) && (buffer[4] == 0xff)) ||
					((mask == ORB_CONSOLE) && (buffer[4] == 0)))
				{
					write (sptr->fd, buffer, lg);
				}
				if ((mask == ORB_CHANNEL) && (buffer[4] > 0) && (buffer[4] < 0xff))
				{
					if ((sptr->channel == 0) || (sptr->channel == buffer[4]))
						write (sptr->fd, buffer, lg);
				}
				break;
			default:
				write (sptr->fd, buffer, lg);
				break;
			}
		}
		sptr = sptr->next;
	}
}

void orb_write (int channel, char *data, int len, int color, int header)
{
	int i, j;
	char *buffer;

	buffer = malloc (len + 8);
	if (buffer == NULL)
		return;

	for (i = 0, j = 7; i < len; i++)
	{
		if (data[i] == '\n')
			continue;

		if (data[i] == '\r')
			buffer[j++] = '\n';
		else
			buffer[j++] = data[i];
	}

	if (channel == CONSOLE)
	{
		/* Header */
		buffer[0] = ORB_CONSOLE;
		buffer[1] = 0;
		buffer[2] = (j - 4) & 0xff;
		buffer[3] = (j - 4) >> 8;

		/* Envoie les data de console au client */
		buffer[4] = CONSOLE;
		buffer[5] = color;
		buffer[6] = header;
		orb_send_data (buffer, j, ORB_CONSOLE);
	}
	else if (channel == MMONITOR)
	{
		/* Header */
		buffer[0] = ORB_MONITOR;
		buffer[1] = 0;
		buffer[2] = (j - 4) & 0xff;
		buffer[3] = (j - 4) >> 8;

		/* Envoie les data de monitoring au client */
		buffer[4] = 0xff;
		buffer[5] = color;
		buffer[6] = header;
		orb_send_data (buffer, j, ORB_MONITOR);
	}
	else
	{
		/* Header */
		buffer[0] = ORB_CHANNEL;
		buffer[1] = 0;
		buffer[2] = (j - 4) & 0xff;
		buffer[3] = (j - 4) >> 8;

		/* Envoie les data de monitoring au client */
		buffer[4] = channel - 1;
		buffer[5] = color;
		buffer[6] = header;
		orb_send_data (buffer, j, ORB_CHANNEL);
	}
	free (buffer);
}

void orb_disc (void)
{
	OrbClient *sptr = client_head;

	/* Deconnecte un client */
	while (sptr)
	{
		if (sptr->mask & ORB_CONSOLE)
		{
			if (sptr->mask & ORB_XFBBX)
			{
				char buffer[5];

				/* Envoie la commande de deconnection */
				buffer[0] = ORB_XFBBX;
				buffer[1] = 0;
				buffer[2] = 1;
				buffer[3] = 0;
				buffer[4] = 0;
				write (sptr->fd, buffer, 5);
				sptr->mask &= ~ORB_CONSOLE;
			}
			else
			{
				close (sptr->fd);
				orb_del_client (sptr);
			}
			break;
		}
		sptr = sptr->next;
	}
}

void orb_con_list (int channel, char *ligne)
{
	char buffer[256];
	int len = strlen (ligne);

	/* Header */
	buffer[0] = ORB_LISTCNX;
	buffer[1] = 0;
	buffer[2] = (len) & 0xff;
	buffer[3] = (len) >> 8;

	/* Envoie la ligne de connection du canal */
	strcpy (buffer + 4, ligne);
	orb_send_data (buffer, len + 4, ORB_LISTCNX);
}

void orb_con_nb (int nb)
{
	char buffer[80];
	int len;

	sprintf (buffer + 4, "%d", nb);
	len = strlen (buffer + 4);

	/* Header */
	buffer[0] = ORB_NBCNX;
	buffer[1] = 0;
	buffer[2] = (len) & 0xff;
	buffer[3] = (len) >> 8;

	/* Envoie le nombre de connections */
	orb_send_data (buffer, len + 4, ORB_NBCNX);
}

void orb_nb_msg (int priv, int hold, int nbmess)
{
	char buffer[80];
	int len;

	sprintf (buffer + 4, "%d %d %d", priv, hold, nbmess);
	len = strlen (buffer + 4);

	/* Header */
	buffer[0] = ORB_MSGS;
	buffer[1] = 0;
	buffer[2] = (len) & 0xff;
	buffer[3] = (len) >> 8;

	/* Envoie le nombre de messages */
	orb_send_data (buffer, len + 4, ORB_MSGS);
}

void orb_status (long lmem, long gmem, long disk1, long disk2)
{
	char buffer[80];
	int len;

	sprintf (buffer + 4, "%ld %ld %ld %ld", lmem, gmem, disk1, disk2);
	len = strlen (buffer + 4);

	/* Header */
	buffer[0] = ORB_STATUS;
	buffer[1] = 0;
	buffer[2] = (len) & 0xff;
	buffer[3] = (len) >> 8;

	/* Envoie les infos de status */
	orb_send_data (buffer, len + 4, ORB_STATUS);
}
