#!/usr/bin/perl -w

# /*******************************************************************************/
# /*                                                                             */
# /*  Copyright 2004 Pascal Gloor                                                */
# /*                                                                             */
# /*  Licensed under the Apache License, Version 2.0 (the "License");            */
# /*  you may not use this file except in compliance with the License.           */
# /*  You may obtain a copy of the License at                                    */
# /*                                                                             */
# /*     http://www.apache.org/licenses/LICENSE-2.0                              */
# /*                                                                             */
# /*  Unless required by applicable law or agreed to in writing, software        */
# /*  distributed under the License is distributed on an "AS IS" BASIS,          */
# /*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   */
# /*  See the License for the specific language governing permissions and        */
# /*  limitations under the License.                                             */
# /*                                                                             */
# /*******************************************************************************/


require 5.6.1;

use strict;
use File::Basename;
use IO::Socket;
use POSIX 'setsid';


my $non_auth_timeout = 30;
my $debug = 1;

# Saying hello
printf("EIP -- Easy IRC Proxy v1.0\n");


# checking arguments

if ( !defined $ARGV[0] ) { syntax(); }

# ignoring lost childs
$SIG{CHLD} = 'IGNORE';

# loading configuration
my %conf;
load_config();

load_users();

init_socket();


sub syntax
{
	printf("usage: %s <configuration file>\n",$0);
	exit(1);
}

sub init_socket
{
	my $sock = IO::Socket::INET->new (
		Proto        => 'tcp',
		LocalPort    => $conf{port},
		LocalAddr    => $conf{ip_addr},
		Listen       => 5,
		Reuse        => 1,
	) || die "error while binding socket";

	my $vec = "";
	vec($vec, fileno($sock), 1) = 1;

	if ( !$debug ) { daemonize(); }

	while(1)
	{
		# checking if new client
		my $vec2 = $vec;

		if ( select($vec2, undef, undef, 0.2) )
		{
			if ( vec($vec2, fileno($sock), 1) )
			{
				# new client
				client($sock->accept());
			}
		}

		# check if need to reload config
		if ( -r "$conf{user_conf}.reload" )
		{
			load_users();
			unlink("$conf{user_conf}.reload");
		}
	}
}

sub client
{
	my $usersock = (shift);
	my $servsock;
	my $ts = time;

	if ( !defined $usersock )
	{
		slog("warning: received null socket");
		return;
	}

	my $pid = fork();

	if ( !defined $pid )
	{ return; }
	elsif ( $pid )
	{ return; }

	my $userip = $usersock->peerhost;
	my $servip;

	slog(sprintf("%s connect",$usersock->peerhost));

	my $buf4s = "";
	my $buf4u = "";

	my $uservec = '';
	my $servvec = '';
	vec($uservec, fileno($usersock), 1) = 1;

	my $status = 0; # not authenticated
	my $ircuser;
	my $ircnick;

	while(defined $usersock)
	{
		# got user data ?
		my $uservec2 = $uservec;

		if ( select($uservec2, undef, undef, 0.05) )
		{
			if ( vec($uservec2, fileno($usersock), 1) )
			{
				if ( sysread($usersock,$buf4s,65536,length($buf4s)) == 0 )
				{
					slog(sprintf("%s connection end",$userip));
					exit(0);
				}
			}
		}

		if ( defined $servsock )
		{
			# got server data ?
			my $servvec2 = $servvec;

			if ( select($servvec2, undef, undef, 0.05) )
			{
				if ( vec($servvec2, fileno($servsock), 1) )
				{
					if ( sysread($servsock,$buf4u,65536,length($buf4u)) == 0 )
					{
						slog(sprintf("%s lost connection to %s",$userip,$servip));
						$buf4u .= "NOTICE AUTH :*** Lost connection with server\n";
						$status = 99;
					}
				}
			}
		}

		if ( $status == 0 )
		{
			$buf4u .= "NOTICE AUTH :*** Welcome to EIP -- Easy IRC Proxy\n";
			$status = 1;
		}

		if ( $status < 10 )
		{
			if ( length($buf4s) )
			{
				my ($tuser,$tnick,$tserver,$tport,$tpass);
				($buf4u,$buf4s,$status,$tuser,$tnick,$tserver,$tport,$tpass) = client_work($buf4u,$buf4s,$status,$userip);

				if ( defined $tuser ) { $ircuser = $tuser; }
				if ( defined $tnick ) { $ircnick = $tnick; }
				if ( defined $tserver && $tport )
				{
					$servsock = IO::Socket::INET->new(
						Proto        => 'tcp',
						PeerAddr   => $tserver,
						PeerPort   => $tport,
						Reuse        => 1,
					);

					if ( !$servsock )
					{
						$buf4u .= "NOTICE AUTH :*** error while connecting to $tserver:$tport\n";
						slog(sprintf("%s unable to connect to %s:%s",$userip,$tserver,$tport));
						$status = 99;
					}
					else
					{
						vec($servvec,fileno($servsock),1) = 1;
						if ( defined $tpass && $tpass =~ /./ )
						{ $buf4s .= "PASS :$tpass\n"; }

						$buf4s .= "USER $ircuser\n";
						$buf4s .= "NICK $ircnick\n";
						$status = 10;
						$servip = $servsock->peerhost;
						slog(sprintf("%s connected to %s:%s (%s)",$userip,$tserver,$tport,$servip));
					}
				}
			}
			if ( $ts + $non_auth_timeout <= time )
			{
				$buf4u .= "NOTICE AUTH :*** Authentication TIMEOUT\n";
				slog(sprintf("%s authentication timeout",$userip));
				$status = 99;
			}
		}

		if ( length($buf4u) )
		{
			my $len = send($usersock,$buf4u,0);
	
			if ( !defined $len )
			{
				slog(sprintf("%s send() error",$userip));
				exit(0);
			}
			elsif ( $len == length($buf4u) )
			{
				$buf4u = '';
			}
			else
			{
				$buf4u =~ s/^.{$len}//;
			}

		}

		if ( $status == 99 )
		{ exit(0); }

		if ( defined $servsock && length($buf4s) )
		{
			my $len = send($servsock,$buf4s,0);

			if ( !defined $len )
			{
				$buf4u .= "NOTICE AUTH :*** Lost connection with server\n";
				send($usersock,$buf4u,0);
				slog(sprintf("%s server disconnected",$userip));
				exit(0);
			}
			elsif ( $len == length($buf4s) )
			{
				$buf4s = '';
			}
			else
			{
				$buf4s =~ s/^.{$len}//;
			}
		}

	}
	


	exit(0);
}

sub client_work
{
	my $buf4u = (shift);
	my $buf4s = (shift);
	my $status = (shift);
	my $userip = (shift);
	my ($nick,$user,$server,$port,$pass);

	$buf4u =~ s/\r//g;
	$buf4s =~ s/\r//g;

	if ( $status == 1 || $status == 2 )
	{
		while( $buf4s =~ s/(.*)(\n)// )
		{
			my $line = $1;
			if ( $line =~ /^USER (.+ .+ .+ :{0,1}.*)$/i )
			{
				$user = $1;
				$status++;
			}
			elsif ( $line =~ /^NICK (.*)$/i )
			{
				$nick = $1;
				$status++;
#				$buf4u .= ":EIP 001 $nick :   _|_|_|_|      _|_|_|      _|_|_|    \n";
#				$buf4u .= ":EIP 002 $nick :   _|              _|        _|    _|  \n";
#				$buf4u .= ":EIP 003 $nick :   _|_|_|          _|        _|_|_|    \n";
#				$buf4u .= ":EIP 004 $nick :   _|              _|        _|        \n";
#				$buf4u .= ":EIP 005 $nick :   _|_|_|_|      _|_|_|      _|        \n";
#				$buf4u .= ":EIP 375 * :\n";
#				$buf4u .= ":EIP 376 * :\n";
			}
		}
		if ( $status == 3 )
		{
			$buf4u .= "NOTICE AUTH :*** Please authenticate with the command: /QUOTE AUTH <username> <password>\n";
		}
	}
	elsif ( $status == 3 )
	{
		while( $buf4s =~ s/(.*)(\n)// )
		{
			my $line = $1;
			if ( $line =~ /^AUTH +(.*) +(.*)$/i )
			{
				if ( defined $conf{users}{$1} && $conf{users}{$1}{pass} eq $2 )
				{
					$buf4u .= "NOTICE AUTH :*** Authentication OK\n";
					$buf4u .= "NOTICE AUTH :*** Connect to a remote IRC Server using the command: /QUOTE IRC <hostname> <port> [password]\n";
					slog(sprintf("%s is authenticated as %s",$userip,$1));
					$status = 4;
				}
				else
				{
					$buf4u .= "NOTICE AUTH :*** Authentication FAILED\n";
					$status = 99;
				}
			}
		}
	}
	elsif ( $status == 4 )
	{
		while( $buf4s =~ s/(.*)(\n)// )
		{
			my $line = $1;
			$line =~ s/ +/ /g;
			$line =~ s/$/ /;
			if ( $line =~ /^IRC (.*) (\d+) (.*)$/i )
			{
				$server = $1;
				$port   = $2;
				$pass   = $3;
				$status = 5;
				slog(sprintf("%s will connect to %s:%s",$userip,$server,$port));
			}
		}
	}

	return ($buf4u,$buf4s,$status,$user,$nick,$server,$port,$pass);
}

sub daemonize
{
	open(STDIN,"/dev/null");
	open(STDOUT,">/dev/null");

	my $pid = fork();

	if ( $pid > 0 )
	{
		sleep 1;
		print STDERR sprintf("detaching from terminal, pid %u\n",$$);
		exit;
	}

	setsid;

	open(STDERR,">&STDOUT");
}

sub load_users
{
	if ( !-r $conf{user_conf} )
	{
		slog(sprintf("unable to open user config '%s'\n",$conf{user_conf}));
		return;
	}

	if ( defined $conf{users} ) { delete $conf{users}; }

	my $cnt = 0;
	open(USERS,"$conf{user_conf}");
	while(<USERS>)
	{
		s/(\r|\n)//g;

		if ( /^user:(\w+):(\w+)$/i )
		{
			$conf{users}{$1}{pass} = $2;
			$cnt++;
		}
	}
	close(USERS);

	slog(sprintf("user configuration reloaded. %u users active.",$cnt));
}

sub slog
{
	my $log = (shift);

	if ( $debug )
	{
		my ($s,$m,$h,$D,$M,$Y) = localtime(time);
		printf("%04u-%02u-%02u %02u:%02u:%02u - %5u - %s\n",$Y+1900,$M+1,$D,$h,$m,$s,$$,$log);
		return;
	}

	if ( !-w $conf{logfile} ) { return; }

	open(LOG,">>$conf{logfile}");
	my ($s,$m,$h,$D,$M,$Y) = localtime(time);
	print LOG sprintf("%04u-%02u-%02u %02u:%02u:%02u - %5u - %s\n",$Y+1900,$M+1,$D,$h,$m,$s,$$,$log);
	close(LOG);
}

sub load_config
{
	if ( !-r $ARGV[0] )
	{
		print STDERR sprintf("error: cannot read '%s'\n",$ARGV[0]);
		syntax();
	}

	$conf{path} = dirname($ARGV[0]);
	$conf{file} = $ARGV[0];

	open(CONF,"$ARGV[0]");
	while(<CONF>)
	{
		s/(\r|\n)//g;

		if ( /^PORT:(\d+)$/ )
		{
			$conf{port} = $1;
		}
		elsif ( /^IP_ADDR:(\d+\.\d+\.\d+\.\d+)$/ )
		{
			$conf{ip_addr} = $1;
		}
		elsif ( /^USER_CONF:(.*)$/ )
		{
			$conf{user_conf} = "$conf{path}/$1";
		}
		elsif ( /^LOGFILE:(.*)$/ )
		{
			$conf{logfile} = "$conf{path}/$1";
		}
	}
	close(CONF);

	my $err = 0;

	if ( !defined $conf{port} )
	{
		print STDERR sprintf("error: no PORT directive in configuration file\n");
		$err++;
	}
	if ( !defined $conf{ip_addr} )
	{
		print STDERR sprintf("error: no IP_ADDR directive in configuration file\n");
		$err++;
	}
	if ( !defined $conf{user_conf} )
	{
		print STDERR sprintf("error: no USER_CONF directive in configuration file\n");
		$err++;
	}
	else
	{
		if ( !-r $conf{user_conf} )
		{
			print STDERR sprintf("error: cannot read USER_CONF file '%s'\n",$conf{user_conf});
			$err++;
		}
	}

	if ( !defined $conf{logfile} )
	{
		print STDERR sprintf("error: no LOGFILE directive in configuration file\n");
		$err++;
	}
	else
	{
		if ( !-w $conf{logfile} )
		{
			open(LOG,">$conf{logfile}");
			close(LOG);
		}
		if ( !-w $conf{logfile} )
		{
			print STDERR sprintf("error: cannot write into LOGFILE file '%s'\n",$conf{logfile});
			$err++;
		}
	}


	if ( $err > 0 )
	{
		printf STDERR sprintf("fatal : %u errors found.\n",$err);
		exit(1);
	}

}
