/****************************************************************/
/**                                                            **/
/**   Direct Communication Chat and Conferencing System        **/
/**   Created May 1993 out of complete boredom.                **/
/**   (c) 1993 Takoyaki Software Ltd.                          **/
/**                                                            **/
/**   Will only run in conjunction with Desqview/X.            **/
/**                                                            **/
/**   File:    CHATSOCK.C                                      **/
/**   Purpose: Controls the channelling of messages to and     **/
/**            from the different users via BSD Sockets        **/
/**   Usage:   Should be linked with CHATHOST                  **/
/**                                                            **/
/****************************************************************/

#include	<sys/errno.h>
#include	<stdio.h>
#include	<sys/ioctl.h>
#include	<sys/time.h>
#include	<sys/socket.h>
#include	<netinet/in.h>
#include	"chatvars.h"
#include	"servcmds.h"
#include	"sockvars.h"


extern unsigned long hostmail;
extern char tmpmsg[2048];
extern char tmpmsg2[128];
extern int global(char *,char *);
extern struct user *finduser(char *);
extern int remuser(struct user *);

extern int sysmsgflag,force_exit;

struct sockaddr_in mysocket;
int mysock;

unsigned long on=1;

struct packet *packets=NULL;

unsigned long *mailboxids=NULL;

/*************************************************************************/
/* get the current message id for the mailbox specified                  */
/* if it doesn't exist then create one                                   */

unsigned long getid(char *mailbox)
	{
	unsigned long *id=mailboxids;

/* scan through for index */

	while (id)
		{
		if (!id[0]) break;
		if (strcmpl((char *)id[0],mailbox) == 0)
			{
			id[1]+=256;
			return id[1]-256;
			}
		id+=2;
		}

/* doesn't exist, so create an index */

	if (mailboxids) mailboxids = realloc(mailboxids,id-mailboxids+256);
	else mailboxids = calloc(8,1);

/* search through to the end */

	id = mailboxids;
	while (id[0])
		id+=2;

	id[0] = (unsigned long)strdup(mailbox);		/* set index        */
	id[1] = 256;					/* initialise count */
	id[2] = 0;						/* mark end of list */

	return id[1]-256;
	}

/*************************************************************************/
struct packet *findpacket(char *mailbox,unsigned long id)
	{
	struct packet *p=packets;

	while (p)
		{
		if (((p->id == id) || id==0xffffffff) && strcmpl(p->mailbox,mailbox)==0) return p;
		p=p->next;
		}
	return NULL;
	}
/*************************************************************************/
struct packet *findoldestpacket(char *mailbox)
	{
	struct packet *p=packets, *o=NULL;
	unsigned long id=0xffffffff;

	while (p)
		{
		if (p->id<id && (strcmpl(mailbox,p->mailbox)==0)) { o=p; id = p->id ;}
		p=p->next;
		}

	if (o==NULL) printf("Woops");
	return o;
	}
/*************************************************************************/
purgemailbox(char *mailbox)
	{
	struct packet *p=packets,*n;
	unsigned long *id=mailboxids;

/* remove all related packets */
	while(p)
		{
		n=p->next;
		if (strcmpl(p->mailbox,mailbox)==0) removepacket(p);
		p=n;
		}

/* zero all id's */
	while (id)
		{
		if (!id[0]) break;
		if (strcmpl((char *)id[0],mailbox) == 0) id[1] = 0;
		id+=2;
		}

	}
/*************************************************************************/
removepacket(struct packet *p)
	{
	if (p->prev) p->prev->next = p->next;
	else packets=p->next;
	if (p->next) p->next->prev = p->prev;
	free(p->mailbox);
	free(p->data);
	free(p);
	}
/*************************************************************************/
acknowledge(char *mailbox,unsigned long id)
	{
	struct packet *p;

/*	printf("%s:%lu Acknowledged!\n",mailbox,id); */
	if (p = findpacket(mailbox,id))
		{
		removepacket(p);
		}
	}
/*************************************************************************/
scanpackets()
	{
	struct packet *p=packets,*n,*o;
	struct user *u;

	while (p)
		{
		n=p->next;
		if (--(p->timeout)==0)
			{
/*			printf("%s:%lu Timeout!  Re-sending (%d/%d).\n",p->mailbox,p->id,p->retries+1,SOCKRETRIES); */
			if (findoldestpacket(p->mailbox)->id != p->id)
				{
				p->timeout++;
				}
			else
				{
				if (p->retries<SOCKRETRIES-1)
					{
					sendtomailbox(p->mailbox,p->data+4,p);
					removepacket(p);
					}
				else
					{
/* time out so remove user */
					if (u = finduser(p->mailbox))
						{
						if (u->ONLINE)
							{
							sysmsgflag |= SYSMSG;
							sprintf(tmpmsg2,"[%s] logged out due to bad connection.",u->username);
							global(tmpmsg2,u->mailbox);
							}
						remuser(u);
						}
/* because list is now invalid we must return */
					return;
					}
				}
			}
		p=n;
		}
	}


/************************************************************************/
/* actually send a packet                                               */

senddata(struct sockaddr_in *to, char *mailbox, char *s, unsigned long id, struct packet *retried)
	{
	struct packet *p;

/* send the data */
/*  don't send the data yet... just queue it if first send!  */

	if (retried)
		{
		if (sendto(mysock,s,strlen(s+4)+1+4,0,(struct sockaddr *)to,sizeof(struct sockaddr_in)) == -1)
			printf("Error sending %d.\n",errno);
		}


/* now set up acknowledged message structure */

	p = calloc(sizeof(struct packet),1);

/* set up data */
	p->id=*((unsigned long *)s);
	if (!retried) p->timeout = 1;
	else p->timeout=SOCKTIMEOUT;
	p->data=s;
	p->mailbox=strdup(mailbox);
	if (retried) p->retries = retried->retries+1;

/* link into list */
	p->next=packets;
	packets=p;
	if (p->next) p->next->prev = p;

	}

/*************************************************************************/

sendtomailbox(unsigned long mailbox, char *msg,struct packet *retried)
	{
	char *s,*m;
	struct sockaddr_in to;
	char *newbox;
	unsigned long id;

/* set destination to send to     */
/* format of mailbox is port@addr */
/* eg. 3444@10,11,1,128           */

	to.sin_family = AF_INET;				/* internet */
	to.sin_addr.s_addr = inet_addr(strchr((char *)mailbox,'@')+1);
	*strchr((char *)mailbox,'@') = '\0';
	to.sin_port = htons(strtol((char *)mailbox,NULL,0));
	((char *)mailbox)[strlen((char *)mailbox)] = '@';

	if (!retried) id = getid((char *)mailbox);
	else id = retried->id;

	if (strlen(msg) <= PACKETSIZE)
		{
		s=malloc(strlen(msg)+1+4);
		*((unsigned long *)s) = id;
		strcpy(s+4,msg);
		senddata(&to, (char *)mailbox, s, id, retried);
		}
	else
		{
		m = msg;
		do
			{
			s = calloc(PACKETSIZE+5,1);
			strncpy(s+4,m,PACKETSIZE);
			*((unsigned long *)s) = ++id;
			senddata(&to, (char *)mailbox, s, id, retried);
			m+=PACKETSIZE;
			} while (strlen(m-PACKETSIZE)>=PACKETSIZE);
		s = calloc(6,1);
		*((unsigned long *)s) = ++id;
		s[4] = ENDPEND;
		senddata(&to, (char *)mailbox, s, id, retried);
		}
	
	}



mailboxread(int (*func)(char *))
	{
#ifdef BSD_43
	int numbytes;
	int fromlen;
	struct sockaddr from;

	while (1)
		{
		fromlen = sizeof(from);
		numbytes = recvfrom(mysock,tmpmsg,sizeof(tmpmsg),0,(struct sockaddr *)&from,&fromlen);
		if ((!numbytes) || (numbytes == -1)) return;
		func(tmpmsg);
		}

#else
	int nummsgs=0;
	int length;
	int stat=0;

	malSizeOf(hostmail,&nummsgs);

	while (nummsgs--)
		{
		length=2047;
		malNRead(hostmail,tmpmsg,&length,&stat);
		if (length<=2047)
			{
			tmpmsg[length] = '\0';
		
			func(tmpmsg);
			}
		}

#endif

	}


initsocket()
	{
#ifdef BSD_43
	int s;

	if (!so_init())
		{
		printf("Socket interface unavailable.\n");
		exit(1);
		}

/* create a DATAGRAM socket (ie. sends asynchronous packets) */
	if ((s = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
		{
		printf("Error %d: Unable to create socket\n",errno);
		exit(1);
		}

/* set up my socket's address */

	mysocket.sin_family = AF_INET;	/* Internet addressing */
	mysocket.sin_addr.s_addr = htonl(INADDR_ANY);	/* messages from anyone */
	mysocket.sin_port = htons(HOSTSOCKET);

/* now try and bind it.. if bind fails then host is already running  */

	if (bind(s, (struct sockaddr *)&mysocket,sizeof(mysocket)) == -1)
		{
		printf("Error: Host is already running! (Unable to bind socket)\n");
		exit(1);
		}

/* set socket NOT to block, ie. if there are no messages in the socket  */
/* then DON'T wait around, just exit with -1                            */


	if (ioctl(s, FIONBIO, (long *)&on) < 0)
		{
		printf("Unable to unblock socket.\n");
		exit(1);
		}


	mysock = s;
	

#else

	if (malNew(&hostmail))
		{
		printf("Error: Unable to open mailbox.\n",hostmail);
		exit(1);
		}

	malName(hostmail,HOSTNAME,strlen(HOSTNAME));

	malOpen(hostmail);

#endif
	}



closesockets()
	{
#ifdef BSD_43

	so_close(mysock);
	so_exit();

#else

	malFree(hostmail);

#endif

	}
