/****************************************************************
 * 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.                                         *
 ****************************************************************/


 /******************************************************
 *                                                     *
 *         FBB Driver for TCP/IP domain sockets       *
 *                                                     *
 *         F6FBB - 1996                                *
 *                                                     *
 ******************************************************/

#include <serv.h>
#include <fbb_drv.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>
#include <arpa/inet.h>

#undef open
#undef read
#undef write
#undef close

#define AX25_CALLSID 10
#define READ_EVENT 1
#define WRITE_EVENT 2
#define EXCEPT_EVENT 4
#define QUEUE_EVENT 8

#define DISCONNECT  0
#define CPROGRESS   1
#define CONNECTED   2
#define WAITINGCALL 3
#define WAITINGPASS 4
#define READONLY    5

#define DISC_EVENT  1
#define CONN_EVENT  2
#define RETR_EVENT  4
#define BUSY_EVENT  8
#define TIME_EVENT  16

#define TCP_MAXCAN 50

typedef struct
{
	int ncan;
	int sock;
	int port;
	int state;
	int paclen;
	int maxframe;
	int netrom;
	int event;
	int queue;
	int nego;
	int cesc;
	int lgial;
	int lpos;
	int lqueue;
	int nb_try;
	int nb_ret;
	int bs;
	int binary;
	long timeout;
	char ial[3];
	indicat callsign;
	char *lbuf;
}
tcan_t;

static tcan_t tcan[TCP_MAXCAN];

static int last_can = 1;

static int msocket = -1;

static int tcp_getline (int can, char *buffer);
static int tcp_snd_dt (int, char *, int);
static int tcp_cmd (int, int, char *);
static int tcp_connect (char *, int);
static int tcp_stat (int, stat_ch *);
static int tcp_paclen (int);
static int s_free (tcan_t *);
static int s_status (tcan_t *);
static int tcp_check_call (int can, char *callsign);
static int tcp_check_pass (int can, char *callsign);
static int tcp_trame (int canal, char *data, int len);

static void clear_can (int canal);
static void read_only_alert (int);

/*
 * Generic functions of the driver
 */

/* Check or change status of a port/channel */
int sta_tcp (int port, int canal, int cmd, void *ptr)
{

	switch (cmd)
	{
	case TNCSTAT:
		return (tcp_stat (canal, (stat_ch *) ptr));
	case PACLEN:
		*((int *) ptr) = tcp_paclen (canal);
		return (1);
	case BSCMD:
		tcan[canal].bs = *((int *) ptr);
		return (1);
	case SNDCMD:
		return (tcp_cmd (port, canal, (char *) ptr));
	}
	return (0);
}

/* Sends data */
int snd_tcp (int port, int canal, int cmd, char *buffer, int len, Beacon * ptr)
{
	int ret = 0;

	switch (cmd)
	{
	case COMMAND:
		break;

	case UNPROTO:
		break;

	case DATA:
		ret = tcp_snd_dt (canal, buffer, len);
		break;
	}
	return (ret);
}

/* receives data */
int rcv_tcp (int *port, int *canal, int *cmd, char *buffer, int *len, ui_header * ui)
{
#define LGBUF 252
	char buf[LGBUF + 2];
	static int can = 0;
	int valid;
	int res;

	*cmd = INVCMD;

	valid = 0;

	/* Teste s'il y a une connection */
	tcan[0].sock = p_port[*port].fd;
	res = s_status (&tcan[0]);

	if (res & READ_EVENT)
	{
		/* static char *TelnetInit = "\377\375\001\377\375\042"; */
		/* static char *TelnetInit = "\377\376\001\377\375\042"; */

		static char *TelnetInit =
		"\377\374\001\000Telnet Initialisation string                                   ";

		int new;
		int i;
		int addr_len;
		struct sockaddr_in sock_addr;

		addr_len = sizeof (sock_addr);

		new = accept (p_port[*port].fd, (struct sockaddr *) &sock_addr, &addr_len);

		/* Affecter le nouveau socket a un canal vide */
		for (i = 1; i < last_can; i++)
		{
			if (tcan[i].state == DISCONNECT)
			{
				break;
			}
		}

		if (i == last_can)
		{
			write (new, TelnetInit, strlen (TelnetInit));

			/* Impossible d'affecter le canal -> deconnexion */
			sprintf (buf, "\r\nSorry, no more channels available\r\n\r\n");
			write (new, buf, strlen (buf));
			close (new);
		}
		else
		{
			int val = 0;
			FILE *fptr;

			tcan[i].state = WAITINGCALL;
			tcan[i].sock = new;
			tcan[i].paclen = (val == 0) ? 250 : val;
			tcan[i].queue = s_free (&tcan[i]);
			tcan[i].timeout = time (NULL) + 120L;

			write (new, TelnetInit, strlen (TelnetInit));

			if ((fptr = fopen (c_disque ("LANG\\TELNET.ENT"), "rt")) == NULL)
			{
				sprintf (buf, "\r\n%s BBS. TELNET Access\r\n\r\n", my_call);
				write (new, buf, strlen (buf));
			}
			else
			{
				int nb;
				char *ptr;

				while (fgets (buf, LGBUF, fptr))
				{
					nb = strlen (buf);
					if (nb)
					{
						buf[nb - 1] = '\r';
						buf[nb] = '\n';
						buf[nb + 1] = '\0';
					}
					ptr = var_txt (buf);
					write (new, ptr, strlen (ptr));
				}
				fclose (fptr);
			}
			sprintf (buf, "Callsign : ");
			write (new, buf, strlen (buf));

			val = p_port[*port].pk_t;

			return (FALSE);
		}
	}

	/* Passe au canal suivant pour le polling */
	++can;
	if (can == last_can)
		can = 1;

	if ((tcan[can].sock == -1) && (tcan[can].state != DISCONNECT))
	{
		sprintf (buffer, "(%d) DISCONNECTED fm TCP", can);
		tcan[can].state = DISCONNECT;
		clear_can (can);
		*len = strlen (buffer);
		*cmd = COMMAND;
		*canal = can;
		return (TRUE);
	}

	/* Canal de communication */
	res = s_status (&tcan[can]);

	if (res & TIME_EVENT)
	{
		sprintf (buf, "Timeout, disconnected !\r\n");
		write (tcan[can].sock, buf, strlen (buf));
		close (tcan[can].sock);
		clear_can (can);
		return (FALSE);
	}

	if (res & WRITE_EVENT)
	{
		/* Can write to the socket... Unused */
	}

	if (res & EXCEPT_EVENT)
	{
		if (tcan[can].event == CONN_EVENT)
		{
			/* Appel sortant connecte */

			/* Le host distant a ete connecte.
			   sprintf(buffer, "*** Connected to %s-%d\r", 
			   tcan[can].callsign.call, tcan[can].callsign.num);
			   in_buf(voie, buffer, strlen(buffer)); */

			/* Connexion a la BBS... */
			sprintf (buffer, "(%d) CONNECTED to %s-%d",
					 can, tcan[can].callsign.call, tcan[can].callsign.num);
			tcan[can].state = CONNECTED;
			tcan[can].nb_try = 0;
			tcan[can].timeout = 0L;
			*len = strlen (buffer);
			*cmd = COMMAND;
			*canal = can;
			return (TRUE);
		}
	}

#define LGTCP 1100

	if ((res & QUEUE_EVENT) || (res & READ_EVENT))
	{
		int nb = 0;
		int i;

		if (tcan[can].sock == -1)
		{
			printf ("read on invalid socket\n");
			return (FALSE);
		}

		/* Alloue le buffer si necessaire */
		if (tcan[can].lbuf == NULL)
		{
			tcan[can].lbuf = calloc (LGTCP, 1);
			tcan[can].lpos = 0;
			tcan[can].lqueue = 0;
			tcan[can].nb_ret = 0;
		}

		if (res & READ_EVENT)
		{
			/* Reste de la place ds le buffer ? */
			nb = ((LGTCP - tcan[can].lqueue) > 256) ? 256 : LGTCP - tcan[can].lqueue;
			if (nb)
			{
				nb = read (tcan[can].sock, buffer, nb);
				if ((nb == 0) || ((nb == -1) && (errno == ENOTCONN)))
				{
					/* Deconnection */
					sprintf (buffer, "(%d) DISCONNECTED fm TCP", can);
					close (tcan[can].sock);
					clear_can (can);
					*len = strlen (buffer);
					*cmd = COMMAND;
					*canal = can;
					return (TRUE);
				}
				else if (nb == -1)
				{
					printf ("errno = %d\n", errno);
					perror ("read");
					return (FALSE);
				}
			}
		}

		{
			int process;
			int pos;
			char *ptr;

			if (nb)
			{
				nb = tcp_trame (can, buffer, nb);

				if (nb == 0)
					return (FALSE);
			}

			pos = tcan[can].lpos + tcan[can].lqueue;
			if (pos > LGTCP)
				pos -= LGTCP;

			for (i = 0; i < nb; i++)
			{
				if (tcan[can].lqueue > (LGTCP - 10))
				{
					++tcan[can].nb_ret;
					break;
				}

				tcan[can].lbuf[pos] = buffer[i];
				if (++pos == LGTCP)
					pos = 0;

				++tcan[can].lqueue;

				if (buffer[i] == '\r')
				{
					++tcan[can].nb_ret;
				}
			}

			nb = tcan[can].lqueue;

			tcan[can].timeout = time (NULL) + 120L;
			process = (tcan[can].nb_ret > 0);

			switch (tcan[can].state)
			{
			case WAITINGCALL:
				if (!process)
					return (FALSE);

				if (!tcp_getline (can, buffer))
					return 0;

				sup_ln (strupr (buffer));

				switch (tcp_check_call (can, buffer))
				{
				case 1:
					{
						info buf_info;
						FILE *fptr;
						unsigned coord;

						if ((coord = chercoord (tcan[can].callsign.call)) == 0xffff)
						{
							close (tcan[can].sock);
							tcan[can].sock = -1;
							break;
						}

						fptr = ouvre_nomenc ();
						fseek (fptr, (long) coord * sizeof (info), 0);
						fread ((char *) &buf_info, sizeof (info), 1, fptr);
						ferme (fptr, 11);

						if (!MOD (buf_info.flags))
						{
							read_only_alert (can);
							tcan[can].state = READONLY;
						}
						else
						{
							sprintf (buf, "Password : ");
							write (tcan[can].sock, buf, strlen (buf));
							tcan[can].state = WAITINGPASS;
							tcan[can].nb_try = 0;
						}
					}
					break;
				case 2:
					if (++tcan[can].nb_try > 3)
					{
						sprintf (buf, "Callsign error, disconnected !\r\n");
						write (tcan[can].sock, buf, strlen (buf));
						close (tcan[can].sock);
						tcan[can].sock = -1;
					}
					else
					{
						if (p_port[*port].moport & 0x40)
						{
							read_only_alert (can);
							tcan[can].state = READONLY;
						}
						else
						{
							char buf[80];

							sprintf (buf, "Unregistered callsign \"%s\" !\r\n", buffer);
							write (tcan[can].sock, buf, strlen (buf));
							sprintf (buf, "Callsign : ");
							write (tcan[can].sock, buf, strlen (buf));
						}
					}
					break;
				case 0:
					if (++tcan[can].nb_try > 3)
					{
						sprintf (buf, "Callsign error, disconnected !\r\n");
						write (tcan[can].sock, buf, strlen (buf));
						close (tcan[can].sock);
						tcan[can].sock = -1;
					}
					else
					{
						char buf[80];

						sprintf (buf, "Invalid callsign \"%s\" !\r\n", buffer);
						write (tcan[can].sock, buf, strlen (buf));
						sprintf (buf, "Callsign : ");
						write (tcan[can].sock, buf, strlen (buf));
					}
					break;
				}
				return (FALSE);

			case WAITINGPASS:
				if (!process)
					return (FALSE);

				if (!tcp_getline (can, buffer))
					return 0;

				sup_ln (buffer);
				if (tcp_check_pass (can, buffer))
				{
					sprintf (buf, "\r\nLogon Ok. Type NP to change password.\r\n\r\n");
					write (tcan[can].sock, buf, strlen (buf));
					sprintf (buffer, "(%d) CONNECTED to %s-%d",
							 can, tcan[can].callsign.call,
							 tcan[can].callsign.num);
					tcan[can].state = CONNECTED;
					tcan[can].nb_try = 0;
					tcan[can].timeout = 0L;
					*len = strlen (buffer);
					*cmd = COMMAND;
					*canal = can;
					return (TRUE);
				}
				else
				{
					if (++tcan[can].nb_try > 3)
					{
						if (p_port[*port].moport & 0x40)
						{
							read_only_alert (can);
							tcan[can].state = READONLY;
						}
						else
						{
							sprintf (buf, "Password error, disconnected !\r\n");
							write (tcan[can].sock, buf, strlen (buf));
							close (tcan[can].sock);
							tcan[can].sock = -1;
						}
					}
					else
					{
						char buf[80];

						sprintf (buf, "Password error !\r\nPassword : ");
						write (tcan[can].sock, buf, strlen (buf));
					}
				}
				return (FALSE);

			case READONLY:
				if (!process)
					return (FALSE);

				if (!tcp_getline (can, buffer))
					return 0;

				if (toupper (*buffer) == 'Y')
				{
					/* Read-Only mode accepted */
					sprintf (buf, "\r\nLogon Ok. You have a read-only access.\r\n\r\n");
					write (tcan[can].sock, buf, strlen (buf));
					sprintf (buffer, "(%d) READONLY to %s-%d",
							 can, tcan[can].callsign.call,
							 tcan[can].callsign.num);
					tcan[can].state = CONNECTED;
					tcan[can].nb_try = 0;
					tcan[can].timeout = 0L;
					*len = strlen (buffer);
					*cmd = COMMAND;
					*canal = can;
					return (TRUE);
				}
				else
				{
					sprintf (buf, "Callsign : ");
					write (tcan[can].sock, buf, strlen (buf));
					tcan[can].state = WAITINGCALL;
				}
				break;

			default:
				pos = tcan[can].lpos;
				ptr = tcan[can].lbuf;
				for (i = 0; i < tcan[can].lqueue; i++)
				{
					buffer[i] = ptr[pos];
					if (++pos == LGTCP)
						pos = 0;
				}
				*len = tcan[can].lqueue;
				*cmd = DATA;
				*canal = can;
				tcan[can].nb_ret = 0;
				tcan[can].lpos = 0;
				tcan[can].lqueue = 0;
				tcan[can].timeout = 0L;
				return (TRUE);
			}
		}
	}
	return (FALSE);
}

/* Open port */
int opn_tcp (int port, int nb)
{
	int old_can;
	int i;
	int val;
	int len;
	int ok = TRUE;
	int tcp_port;
	char s[80];
	struct sockaddr_in sock_addr;
	char *ptr;

	sock_addr.sin_family = AF_INET;
	sock_addr.sin_addr.s_addr = 0;	/* inet_addr("192.9.200.10"); */

	/* Test if portname is hex number */
	ptr = p_com[(int) p_port[port].ccom].name;

	if (strcmp (ptr, "0") == 0)
	{
		tcp_port = p_com[(int) p_port[port].ccom].port;
	}
	else if (strspn (ptr, "0123456789abcdefABCDEF") != strlen (ptr))
	{
		/* It may be tcp address. Port number is in port */
		if (inet_aton (ptr, &sock_addr.sin_addr))
			tcp_port = p_com[(int) p_port[port].ccom].port;
		else
			tcp_port = p_com[(int) p_port[port].ccom].cbase;
	}
	else
	{
		/* for up compatibility */
		tcp_port = p_com[(int) p_port[port].ccom].cbase;
	}

	sock_addr.sin_port = htons (tcp_port);

	sprintf (s, "Init PORT %d COM%d-%d",
			 port, p_port[port].ccom, p_port[port].ccanal);
	InitText (s);

	old_can = last_can;
	last_can += nb;

	if (last_can > TCP_MAXCAN)
		last_can = TCP_MAXCAN;

	for (i = old_can; i < last_can; i++)
	{
		clear_can (i);
	}

	/* Socket reception d'appels */
	if (p_port[port].fd == 0)
	{

		sprintf (s, "Open PORT %d COM%d-%d",
				 port, p_port[port].ccom, p_port[port].ccanal);
		InitText (s);
		sleep (1);

		if ((p_port[port].fd = socket (AF_INET, SOCK_STREAM, 0)) < 0)
		{
			perror ("socket_r");
			return (0);
		}

		val = 1;
		len = sizeof (val);
		if (setsockopt (p_port[port].fd, SOL_SOCKET, SO_REUSEADDR, (char *) &val, len) == -1)
		{
			perror ("opn_tcp : setsockopt SO_REUSEADDR");
		}

		if (bind (p_port[port].fd, (struct sockaddr *) &sock_addr, sizeof (sock_addr)) != 0)
		{
			perror ("opn_tcp : bind");
			close (p_port[port].fd);
			p_port[port].fd = -1;
			return (0);
		}

		if (listen (p_port[port].fd, SOMAXCONN) == -1)
		{
			perror ("listen");
			close (p_port[port].fd);
			p_port[port].fd = -1;
			return (0);
		}

		memset (&tcan[0], 0, sizeof (tcan_t));

	}

	sprintf (s, "Prog PORT %d COM%d-%d",
			 port, p_port[port].ccom, p_port[port].ccanal);
	InitText (s);

	return (ok);
}

/* Close port */
int cls_tcp (int port)
{
	int i;

	for (i = 0; i < last_can; i++)
	{
		if (tcan[i].sock != -1)
		{
			close (tcan[i].sock);
		}
		tcan[i].state = DISCONNECT;
	}

	if (msocket != -1)
		close (msocket);
	msocket = -1;

	if ((p_port[port].typort == TYP_SCK) && (p_port[port].fd))
	{
		close (p_port[port].fd);
		p_port[port].fd = 0;
	}

	return (1);
}


/* 

 * Static functions
 *
 */

#define LF	0x0a
#define CR	0x0d
#define SE      0xf0
#define NOP     0xf1
#define DM      0xf2
#define BRK     0xf3
#define IP      0xf4
#define AO      0xf5
#define AYT     0xf6
#define EC      0xf7
#define EL      0xf8
#define GA      0xf9
#define SB      0xfa
#define WILL	0xfb
#define WONT	0xfc
#define DO	0xfd
#define DONT	0xfe
#define IAC     0xff

/* Telnet options */
#define TN_TRANSMIT_BINARY	0
#define TN_ECHO			1
#define TN_SUPPRESS_GA		3
#define TN_STATUS		5
#define TN_TIMING_MARK		6

/* Linemode options */
#define TN_LINEMODE		34
#define TN_LINEMODE_MODE	1
#define TN_LINEMODE_MODE_EDIT	1

static int tcp_trame (int canal, char *data, int len)
{
	int i;
	int carac;
	int olg = 0;

	char *optr = data;

	int pial = tcan[canal].lgial;

	if (tcan[canal].binary)
	{
		return (len);
	}

	for (i = 0; i < len; i++)
	{
		carac = data[i] & 0xff;

		switch (pial)
		{
		case 0:
			if (carac == IAC)
				tcan[canal].ial[pial++] = carac;
			else
			{
				if (tcan[canal].nego == 0)
				{
					optr[olg++] = carac;
					if ((carac == CR) && (tcan[canal].bs))
						pial = 10;
				}
				else
				{
					printf ("%02x ", carac);
				}
			}
			break;
		case 1:
			if (carac == IAC)
			{
				optr[olg++] = carac;
				pial = 0;
			}
			else if (tcan[canal].nego)
			{
				printf ("%02x...\n", carac);
			}
			else
			{
				switch (carac)
				{

				case WILL:
					printf ("WILL ");
					break;
				case WONT:
					printf ("WONT ");
					break;
				case DO:
					printf ("DO ");
					break;
				case DONT:
					printf ("DONT ");
					break;

				case SB:
					printf ("SUBN ");
					tcan[canal].nego = 1;
					pial = 0;
					break;
				case GA:
					printf ("GA\n");
					pial = 0;
					break;
				case EL:
					printf ("EL\n");
					pial = 0;
					break;
				case EC:
					printf ("EC\n");
					pial = 0;
					break;
				case AYT:
					printf ("AYT\n");
					pial = 0;
					break;
				case AO:
					printf ("AO\n");
					pial = 0;
					break;
				case IP:
					printf ("IP\n");
					pial = 0;
					break;
				case BRK:
					printf ("BRK\n");
					pial = 0;
					break;
				case DM:
					printf ("DM\n");
					pial = 0;
					break;
				case NOP:
					printf ("NOP\n");
					pial = 0;
					break;
				case SE:
					printf ("SE\n");
					/* l = 0; */
					tcan[canal].nego = 0;
					pial = 0;
					break;
				default:
					printf ("%02x...\n", carac);
					break;
				}
				if (pial)
					tcan[canal].ial[pial++] = carac;
			}
			break;
		case 2:
			{
				char buf[80];

				printf ("%02x...\n", carac);
				switch (tcan[canal].ial[1])
				{
				case WILL:
					if (carac == TN_LINEMODE)
					{
						/* write(tcan[canal].sock, buffer, strlen(buffer)); */
					}
					printf (" -> DONT %02x\n", carac);
					sprintf (buf, "%c%c%c", IAC, DONT, carac);
					write (tcan[canal].sock, buf, strlen (buf));
					break;
				case DO:
					if (carac != TN_ECHO)
					{
						printf (" -> WONT %02x\n", carac);
						sprintf (buf, "%c%c%c", IAC, WONT, carac);
						write (tcan[canal].sock, buf, strlen (buf));
					}
					break;
				case WONT:
				case DONT:
					break;

				case SB:
					break;
				}
				pial = 0;
			}
			break;
		case 10:
			{
				/* Teste le CRLF -> CR */
				if (carac != LF)
					optr[olg++] = carac;
				pial = 0;
			}
			break;
		}
	}

	tcan[canal].lgial = pial;

	return (olg);
}

static int tcp_check_call (int can, char *callsign)
{
	if (*callsign == '.')
	{
		tcan[can].binary = 1;
		++callsign;
	}
	if (find (callsign))
	{
		tcan[can].callsign.num = extind (callsign, tcan[can].callsign.call);
		if (chercoord (tcan[can].callsign.call) != 0xffff)
		{
			return (1);
		}
		else
			return (2);
	}
	return (0);
}

static int tcp_check_pass (int can, char *passwd)
{
	unsigned record;

	record = chercoord (tcan[can].callsign.call);

	if (record != 0xffff)
	{
		FILE *fptr;
		info frec;

		fptr = ouvre_nomenc ();
		
			fseek (fptr, ((long) record * sizeof (info)), 0);
		
			fread (&frec, sizeof (info), 1, fptr);
		
			ferme (fptr, 92);

		if (strcasecmp (passwd, frec.pass) == 0)
		{
			return (TRUE);
		}
	}

	return (FALSE);
}

static int tcp_snd_dt (int canal, char *buffer, int len)
{
	int i;
	int nb;
	int lg;
	char *ptr;

	char buf[600];

	if (tcan[canal].sock == -1)
		return (FALSE);

	for (ptr = buf, nb = 0, i = 0; i < len; i++)
	{

		*ptr++ = buffer[i];
		++nb;
		if (!tcan[canal].binary)
		{
			if ((buffer[i] == '\r') && (tcan[canal].bs))
			{
				*ptr++ = '\n';
				++nb;
			}
			else if (buffer[i] == IAC)
			{
				*ptr++ = IAC;
				++nb;
			}
		}
	}

	lg = write (tcan[canal].sock, buf, nb);
	if (nb == -1)
	{
		printf ("Error %d on socket %d\n", errno, tcan[canal].sock);
		perror ("write on socket");
	}
	else if (lg < nb)
	{
		printf ("Cannot write %d bytes on socket %d\n", nb - lg, tcan[canal].sock);
	}
	return (TRUE);
}

static int tcp_cmd (int port, int canal, char *cmd)
{
	switch (*cmd++)
	{
	case 'I':
		/* source callsign */
		while (isspace (*cmd))
			++cmd;
		tcan[canal].callsign.num = extind (cmd, tcan[canal].callsign.call);
		break;
	case 'D':
		/* Deconnection */
		if (tcan[canal].sock != -1)
		{
			close (tcan[canal].sock);
			tcan[canal].sock = -1;
		}
		break;
	case 'C':
		/* Connection */
		while (isspace (*cmd))
			++cmd;

		tcan[canal].paclen = p_port[port].pk_t;
		tcan[canal].maxframe = p_port[port].frame;
		tcan[canal].port = port;

		tcp_connect (cmd, canal);
		break;
	default:
		return (0);
	}
	return (1);
}

static int tcp_connect (char *commande, int can)
{
	int fd;
	int ioc;
	int tcp_port;
	char tcp_add[80];
	char indic[80];

	struct sockaddr_in sock_addr;

	if (tcan[can].state != DISCONNECT)
		return (0);

	tcp_add[0] = '\0';
	tcp_port = 23;

	commande[79] = '\0';
	sscanf (commande, "%s %s %i", indic, tcp_add, &tcp_port);

	tcan[can].callsign.num = extind (indic, tcan[can].callsign.call);

	if (!find (tcan[can].callsign.call))
	{
		fprintf (stderr, "connect : invalid callsign %s\n", indic);
		return (0);
	}

	if (tcp_add[0] == '\0')
	{
		fprintf (stderr, "connect : tcp address missing\n");
		return (0);
	}

	if ((fd = socket (AF_INET, SOCK_STREAM, 0)) < 0)
	{
		perror ("socket_ax");
		clear_can (can);
		return (0);
	}

	sock_addr.sin_family = AF_INET;
	sock_addr.sin_addr.s_addr = inet_addr (tcp_add);
	sock_addr.sin_port = htons (tcp_port);

	ioc = 1;
	ioctl (fd, FIONBIO, &ioc);
	if (connect (fd, (struct sockaddr *) &sock_addr, sizeof (sock_addr)) == -1)
	{
		if (errno != EINPROGRESS)
		{
			printf ("\n");
			perror ("connect");
			clear_can (can);
			close (fd);
			return (0);
		}
	}

	printf ("*** Connect in progress %s canal %d\n", commande, can);

	tcan[can].sock = fd;
	tcan[can].state = CPROGRESS;
	tcan[can].queue = s_free (&tcan[can]);

	return (TRUE);
}

static int tcp_stat (int canal, stat_ch * ptr)
{
	int val;

	if ((canal == 0) || (tcan[canal].sock == -1))
		return (0);

	ptr->mem = 100;

	val = s_free (&tcan[canal]);

	if (tcan[canal].state != CONNECTED)
		ptr->ack = 0;
	else
	{
		ptr->ack = (tcan[canal].queue - val) / tcan[canal].paclen;
		if ((tcan[canal].queue - val) && (ptr->ack == 0))
			ptr->ack = 1;
	}

	return (1);
}

static int s_status (tcan_t * can)
{
	int nb;
	int res = 0;
	fd_set tcp_read;
	fd_set tcp_write;
	fd_set tcp_excep;
	struct timeval to;

	if (can->sock == -1)
		return (0);

	if ((can->timeout) && (can->timeout < time (NULL)))
	{
		res |= TIME_EVENT;
		can->timeout = 0L;
		return (res);
	}

	if (can->lqueue)
	{
		res |= QUEUE_EVENT;
	}

	to.tv_sec = to.tv_usec = 0;
	can->event = 0;

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

	FD_SET (can->sock, &tcp_read);
	FD_SET (can->sock, &tcp_write);
	FD_SET (can->sock, &tcp_excep);

	nb = select (can->sock + 1, &tcp_read, &tcp_write, &tcp_excep, &to);
	if (nb == -1)
	{
		perror ("select");
		return (res);
	}
	else if (nb == 0)
	{
		return (res);
	}
	else
	{
		if (FD_ISSET (can->sock, &tcp_read))
		{
			res |= READ_EVENT;
		}
		if (FD_ISSET (can->sock, &tcp_write))
		{
			if (can->state == CPROGRESS)
			{
				can->state = CONNECTED;
				write (can->sock, &res, 0);
				res |= EXCEPT_EVENT;
				can->event = CONN_EVENT;
			}
			else
				res |= WRITE_EVENT;
		}
		if (FD_ISSET (can->sock, &tcp_excep))
		{
			res |= EXCEPT_EVENT;
		}
	}
	return (res);
}

static int tcp_paclen (int canal)
{
	if (tcan[canal].sock == -1)
		return (0);

	return (tcan[canal].paclen);
}

static int s_free (tcan_t * can)
{
	int queue_free;

	if (ioctl (can->sock, TIOCOUTQ, &queue_free) == -1)
	{
		perror ("ioctl : TIOCOUTQ");
		return (0);
	}
	return (queue_free);
}

static void clear_can (int canal)
{
	if (tcan[canal].lbuf)
		free (tcan[canal].lbuf);
	memset (&tcan[canal], 0, sizeof (tcan_t));
	tcan[canal].sock = -1;
	tcan[canal].bs = 1;
	tcan[canal].binary = 0;
	tcan[canal].state = DISCONNECT;
}

#define READ_ONLY "\r\nLogin in read-only mode.\r\nYou may leave a message to SYSOP.\r\n\r\nGo on anyway (Y/N) ? "

static void read_only_alert (int can)
{
	char buf[256];

	sprintf (buf, READ_ONLY);
	write (tcan[can].sock, buf, strlen (buf));
}

/* Copie une ligne dans le buffer */
static int tcp_getline (int can, char *buffer)
{
	int i = 0;
	int c;
	int pos;
	char *ptr;

	pos = tcan[can].lpos;
	ptr = tcan[can].lbuf;

	while (tcan[can].lqueue)
	{
		c = ptr[pos];
		if (++pos == LGTCP)
			pos = 0;

		--tcan[can].lqueue;

		if ((c == '\n') && (tcan[can].bs))
			continue;

		buffer[i++] = c;

		if (c == '\r')
		{
			--tcan[can].nb_ret;
			break;
		}
	}

	buffer[i] = '\0';

	tcan[can].lpos = pos;

	return (i);
}
