#ident "@(#) aprelay.c 0.1 06/15/1998 "

/********************************************************/
/*      Copyright (C) 1998 ID NET - Nancy - FRANCE      */
/*             P.Toilon (zorthrax@id-net.fr)            */
/*    This relay is released into the Public Domain     */
/*    The original file 'apircrly.c' has 6901 bytes     */
/********************************************************/

#include <stdio.h>
#include <netdb.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define hd(h) ((h<'A')?(h-'0'):(((h&0xdf)-'A')+10))

/* Change it to #undef UNIX if under Windows */
#undef  UNIX
/* Max simultaneous users on the relay */
#define MAXUSERS 128
/* Local listener socket timeout in seconds */
#define LCTIMEOUT 120
/* Max length of negociation frame before connecting to remote */
#define MAXFRAME 128

struct usr
{
	int fd;
	int fdo;
}	usr[MAXUSERS];

char oc;
fd_set rfds,fds;
struct sockaddr_in uin;
int FIRST,mfd,maxfd,osz;
short eoframe[MAXUSERS];
char *cpass,*cserv,*cport,*arg,*strchr();
char *fptr[MAXUSERS],frame[MAXUSERS][MAXFRAME];

void htod4(saddr,i1,i2,i3,i4)
long saddr;
int *i1,*i2,*i3,*i4;
{
	char xsaddr[32];

	sprintf(xsaddr,"%.8x",saddr);

	*i4=hd(xsaddr[0])*16+hd(xsaddr[1]);
	*i3=hd(xsaddr[2])*16+hd(xsaddr[3]);
	*i2=hd(xsaddr[4])*16+hd(xsaddr[5]);
	*i1=hd(xsaddr[6])*16+hd(xsaddr[7]);
}

/*
** addrcmp("194.206.115.0",0x0673cec2) -> 0
** addrcmp("194.206.115.0",0x0673cec2) -> 0
** (0x0673cec2 corresponding to 194.206.115.6)
**
** Allow to select incoming users on an accept(fd,&in,&sz).
** E.g.: addrcmp("194.206.115.0",in.sin_addr.s_addr)
*/
int addrcmp(faddr,saddr)
char *faddr;
long saddr;
{
	int fi[4],si[4],cpt;
	char caddr[32],*cn2,*cn3,*cn4;

	strcpy(caddr,faddr);

	if(!isdigit(*caddr) || !(cn2=strchr(caddr,'.')) || !isdigit(*++cn2) ||
					!(cn3=strchr(cn2,'.')) || !isdigit(*++cn3) ||
						!(cn4=strchr(cn3,'.')) || !isdigit(*++cn4))
							return(-1);

	*(cn2-1)=*(cn3-1)=*(cn4-1)='\0';

	if((fi[0]=atoi(caddr))>255 || (fi[1]=atoi(cn2))>255 ||
		(fi[2]=atoi(cn3))>255 || (fi[3]=atoi(cn4))>255)
			return(-1);

	htod4(saddr,&si[0],&si[1],&si[2],&si[3]);

	for(cpt=0;cpt<4;cpt++)
		if(fi[cpt] && si[cpt]!=fi[cpt])
			return(-1);

	return(0);
}

int open_remote_server_socket(server,port)
char *server;
int port;
{
	int fd;
	struct hostent *rmt;
	struct sockaddr_in out;

	if((fd=socket(AF_INET,SOCK_STREAM,0))<0)
		return(-1);

	if(!(rmt=gethostbyname(server)))
	{
		close(fd);
		return(-1);
	}

	memcpy(&out.sin_addr,rmt->h_addr,rmt->h_length);

	out.sin_family=AF_INET;
	out.sin_port=htons(port);

	if(connect(fd,(struct sockaddr *)&out,sizeof out)<0)
	{
		close(fd);
		return(-1);
	}

	return(fd);
}

int open_listener_socket(port,maxusers,timeout)
int port,maxusers,timeout;
{
	time_t tvey;
	struct sockaddr_in in;
	int fd,rt,sz=sizeof(struct sockaddr_in);

	if((fd=socket(AF_INET,SOCK_STREAM,0))<0)
		return(-1);

	memset(&in,0,sz);
	in.sin_family=AF_INET;
	in.sin_addr.s_addr=INADDR_ANY;
	in.sin_port=htons(port);

	time(&tvey);

	while((rt=bind(fd,(struct sockaddr *)&in,sz))<0 && (time(0)-tvey)<timeout)
		sleep(1);

	if(rt<0 || listen(fd,maxusers)<0)
	{
		close(fd);
		return(-1);
	}

	return(fd);
}

int sd(fd,str)
int fd;
char *str;
{
	return(send(fd,str,strlen(str),0));
}

int getnewusernum()
{
	int i;

	for(i=0;i<MAXUSERS;i++)
		if(usr[i].fd==-1)
			return(i);
	return(-1);
}

int getmaxfdvalue(fd)
int fd;
{
	int i,j;

	for(i=0,j=-1;i<MAXUSERS;i++)
	{
		if(usr[i].fd>j)
			j=usr[i].fd;
		if(usr[i].fdo>j)
			j=usr[i].fdo;
	}

	if(fd>j)
		j=fd;

	return(j);
}

/*
** accept sent a broken pipe (signal 13)
** We go back into accept_user()
*/
void b_accept()
{
	FIRST=0;
	accept_user();
}

accept_user()
{
	int i,newfd;

	/*
	** To avoid the accept() brokenpipe
	*/
	signal(13,b_accept);

	if(FIRST)
	{
		for(i=0;i<MAXUSERS;i++)
		{
			usr[i].fd=-1;
			usr[i].fdo=-1;
		}

		FD_ZERO(&fds);
		FD_ZERO(&rfds);
		FD_SET((maxfd=mfd),&fds);
	}

	while(1)
	{
		rfds=fds;
		select(maxfd+1,&rfds,NULL,NULL,0);

		if(FD_ISSET(mfd,&rfds))
		{
			if((newfd=accept(mfd,(struct sockaddr *)&uin,&osz))>=0)
			{
				if(arg && addrcmp(arg,uin.sin_addr.s_addr)==-1)
				{
					sd(newfd,"Sorry, your host is not allowed !\n\r");
					close(newfd);
				}
				else if((i=getnewusernum())==-1)
				{
					sd(newfd,"Sorry, all relay sockets are busy !\n\r");
					close(newfd);
				}
				else
				{
					usr[i].fd=newfd;
					usr[i].fdo=-1;
					fptr[i]=frame[i];
					eoframe[i]=0;
					sd(usr[i].fd,"Apirc relay v1.0\r\n\n");
					FD_SET(usr[i].fd,&fds);
					maxfd=getmaxfdvalue(mfd);
				}
			}
		}
		else for(i=0;i<MAXUSERS;i++)
		{
			if(usr[i].fd!=-1 && FD_ISSET(usr[i].fd,&rfds))
			{
				if(!recv(usr[i].fd,&oc,1,0))	/* If disconnected */
				{
					FD_CLR(usr[i].fd,&fds);
					close(usr[i].fd);
					usr[i].fd=-1;
					if(usr[i].fdo!=-1)
					{
						FD_CLR(usr[i].fdo,&fds);
						close(usr[i].fdo);
						usr[i].fdo=-1;
					}
					maxfd=getmaxfdvalue(mfd);
				}
				else if(usr[i].fdo!=-1)	/* connected to remote serv */
					send(usr[i].fdo,&oc,1,0);
				else if(!eoframe[i])	/* remove this cond for direct relay */
				{
					/* too big negociation frame */
					if((fptr[i]-frame[i])>=MAXFRAME)
					{
						FD_CLR(usr[i].fd,&fds);
						close(usr[i].fd);
						usr[i].fd=-1;
						maxfd=getmaxfdvalue(mfd);
					}
					else if(oc=='§')
					{
						*(fptr[i]-1)='\0';	/* On the last ':' */
						cpass=frame[i];
						*(cserv=strchr(frame[i],':'))='\0';
						*(cport=strchr(++cserv,':'))='\0';
						cport++;
						eoframe[i]=1;
					}
					/* Still not § in "xUi.HjGF/g:irc.id-net.fr:6667:§" */
					else if(oc>0)
						*(fptr[i])++ = oc;
				}
				else	/* We just received the complete frame, let's connect */
				{
					if((usr[i].fdo=open_remote_server_socket(cserv,atoi(cport)))<0)
					{
						sd(usr[i].fd,"Server unavailable\r\n");
						FD_CLR(usr[i].fd,&fds);	/* On libere */
						close(usr[i].fd);
						usr[i].fd=-1;
						maxfd=getmaxfdvalue(mfd);
					}
					else
					{
						FD_SET(usr[i].fdo,&fds);
						maxfd=getmaxfdvalue(mfd);
						send(usr[i].fdo,&oc,1,0);	/* 1st byte sent to th server */
					}
				}
			}

			if(usr[i].fdo!=-1 && FD_ISSET(usr[i].fdo,&rfds))
			{
				if(!recv(usr[i].fdo,&oc,1,0))	/* If disconnected */
				{
					FD_CLR(usr[i].fd,&fds);
					FD_CLR(usr[i].fdo,&fds);
					close(usr[i].fd);
					close(usr[i].fdo);
					usr[i].fd=-1;
					usr[i].fdo=-1;
					maxfd=getmaxfdvalue(mfd);
				}
				else
					send(usr[i].fd,&oc,1,0);
			}
		}
	}
}

main(argc,argv)
int argc;
char **argv;
{
	arg=(argc==2)?argv[1]:NULL;

	printf("Trying to open the listener socket ... ");
	fflush(stdout);

	if((mfd=open_listener_socket(8888,MAXUSERS,LCTIMEOUT))<0)
	{
		printf("\nSocket not openable. Daemon not ready\n");
		exit();
	}

	printf("\nListener socket ready\n");
	fflush(stdout);

#ifdef UNIX
	if(fork())
		exit();
#endif

	osz=sizeof(struct sockaddr_in);
	FIRST=1;
	accept_user();
}
