/********************************************************/
/*                   aprelay.c v1.2                     */
/*             P.Toilon (zorthrax@id-net.fr)            */
/*           29/10/1998 IDNET - Nancy - FRANCE          */
/*    This relay is released into the Public Domain     */
/*    The original file 'aprelay.c' has 8141 bytes      */
/********************************************************/

/*
** This relay is made for Apirc v1.1
** IT WILL NOT WORK ON PREVIOUS VERSIONS
*/

#include <stdio.h>
#include <netdb.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>

/* Change it to #undef UNIX if under Windows */
#define 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
/* Max length of each frame's field */
#define		MAXSE		(MAXFRAME/2)

/*
** Change CLASSIDENT to #define if :
** 1 - You want to have each user with a different username to
**     avoid K-line on some servers.
** 2 - #define UNIX is set (and you are running a Unix/Linux OS)
** 3 - You can have root access and can set the aprelay file uid bit to root
**     (under root, after each compilation, do a 'chmod u+s aprelay')
** On Windows OSes, you can run apidentd for this.
*/
#ifdef UNIX
 #define CLASSIDENT
#endif

/*
** If CLASSIDENT is defined (see above), we set a range of UIDs
** that begin at FIRSTUID. For example if FIRSTUID = 3000 and
** MAXUSERS = 16, users will have 3000@yourmachine.com, 3001@
** yourmachine.com and so on until 3015.
**
** As numbers could not be appreciated by some ircops, it's
** probably better to add corresponding login entries in
** /etc/passwd to get real names.
**
** E.g.: Still with MAXUSERS = 16 and FIRSTUID = 3000, you
** should have to add 16 users on your system, from
** apircuser1 to apircuser16 with UIDs from 3000 to 3015
** (Full Name: "Untrusted chat user").
**
** On RedHat 5.0 you should make : adduser -u 3000 apircuser1
** On Slackware, run adduser and follow the instructions.
** Do not edit directly /etc/passwd if shadow passwords are used.
**
** Users will have apircuser1@yourmachine.com for example.
** If user addresses still remain with UID numbers, verify that
** the auth port 113 (identd) is reachable from outside.
*/
#ifdef CLASSIDENT
 #define FIRSTUID 3000
 #define LASTUID (MAXUSERS+FIRSTUID-1)
#endif

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

fd_set rfds,fds;
long uip[MAXUSERS];
struct sockaddr_in uin;
short eoframe[MAXUSERS];
int FIRST,mfd,maxfd,osz;
char hname[MAXUSERS][MAXFRAME];
char cnick[MAXSE],cserv[MAXSE],cport[MAXSE];
char oc[BUFSIZ],*arg,*strchr(),*inet_ntoa(),tmp[BUFSIZ];

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(0l)-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()
{
	char *pt1,*pt2;
	struct hostent *ht;
	int i,j,si[4],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)
			{
				/*
				** IP Filtering. E.g: 'arg="192.168";' will
				** forbid all 192.168.x.x IP addresses.
				*/
				//arg="192.168.0.43";
				if(arg && !strncmp(arg,inet_ntoa(uin.sin_addr.s_addr),strlen(arg)))
				{
					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;
					eoframe[i]=0;
					sd(usr[i].fd,"Apirc relay v1.2\r\n\n");
					if(!(ht=gethostbyaddr((char *)&uin.sin_addr,sizeof(unsigned long),AF_INET)))
						strcpy(hname[i],inet_ntoa(uin.sin_addr.s_addr));
					else
						strcpy(hname[i],ht->h_name);
					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(!(j=recv(usr[i].fd,oc,BUFSIZ,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,j,0);
				else if(!eoframe[i])
				{
					/* too big negociation frame */
					if(j>=MAXFRAME)
					{
						FD_CLR(usr[i].fd,&fds);
						close(usr[i].fd);
						usr[i].fd=-1;
						maxfd=getmaxfdvalue(mfd);
					}
					else if(j>4 && strchr(oc,'§')) /* "MyNick:irc.id-net.fr:6667:§" */
					{
						pt1=oc;
						*(pt2=strchr(pt1,':'))='\0';
						strcpy(cnick,pt1);
						pt1=pt2+1;
						*(pt2=strchr(pt1,':'))='\0';
						strcpy(cserv,pt1);
						pt1=pt2+1;
						*(pt2=strchr(pt1,':'))='\0';
						strcpy(cport,pt1);
						eoframe[i]=1;
					}
				}
				else	/* We just received the complete frame, let's connect */
				{
#ifdef CLASSIDENT
					setreuid(FIRSTUID+i,NULL);
#endif
					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);
						srand(time(0l));
						sprintf(tmp,"NICK %s\nUSER user%d host server :%s [Real user host]\n",cnick,rand()%9000+1000,hname[i]);
						sd(usr[i].fdo,tmp);
						send(usr[i].fdo,oc,j,0);	/* 1st datas sent to th server */
					}
				}
			}

			if(usr[i].fdo!=-1 && FD_ISSET(usr[i].fdo,&rfds))
			{
				if(!(j=recv(usr[i].fdo,oc,BUFSIZ,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,j,0);
			}
		}
	}
}

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

#ifdef CLASSIDENT
	if(getuid() && setuid(NULL)==-1)
	{
		printf("\nYou must set the binary file uid bit to root. Daemon not ready\n");
		exit();
	}
#endif

	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();
}
