#!/usr/bin/perl
# SSL Man-In-The-Middle v0.1. Copyright (C) Vlatko Kosturjak, Kost
# Distributed under GPL v2+.

use strict;
use POSIX;
use IO::Socket::SSL qw(debug3);
use Getopt::Long;
use Time::HiRes qw(time);

my $debug = 0;

my $laddr = "127.0.0.1";
my $lport = 8080;
my $raddr      = "127.0.0.1";
my $rport      = 80;

my $logdir;

my $help;
my $daemon;
my $buffersize = 2048;
my $logtype;
my $daemon;
my $serverkey;
my $servercert;
my $serverdh;

$| = 1;

my $goresult = GetOptions(
	"lport=i"      => \$lport,
	"laddr=s"      => \$laddr,
	"rport=i"      => \$rport,
	"raddr=s"      => \$raddr,
	"logtype=i"    => \$logtype,
	"logdir=s"     => \$logdir,
	"daemon"       => \$daemon,
	"serverkey=s"  => \$serverkey,
	"servercert=s" => \$servercert,
	"serverdh=s"   => \$serverdh,
	"help"         => \$help,
	'debug!'	=> \$debug,
);

if ($help) {
	print <<"END";
SSL Man-In-The-Middle v0.1. Copyright (C) Vlatko Kosturjak, Kost
Distributed under GPL v2+. 

Usage: $0 [OPTIONS]

        --lport <port>          Listening port (default 80)
        --laddr <address>       Listening address (default localhost)
        --rport <port>          Remote port to connect to (default 8080)
        --raddr <address>       Remote address to connect to (default localhost)
	--serverkey <file>	Certificate key file for local SSL server
	--servercert <file>	Certificate file for local SSL server
	--serverdh <file>	Diffie-Helman file for key exchange
        --log <type>            Type of log where 0 is no log (default 0)
        --logdir                Directory to log to (default .)
        --daemon                Daemonize (work in background)
        --help                  Display this help message
END
	exit;
}

$Net::SSLeay::trace = 4 if $debug;

$logdir  ||= "$laddr:$lport-$raddr:$rport";
$serverkey ||= "$logdir/ssl.key";
$servercert ||= "$logdir/ssl.cert";

mkdir $logdir;

system "openssl req -new -x509 -days 365 -nodes -out $servercert -keyout $serverkey"
	if ! -e $serverkey && ! -e $servercert;


if ($daemon) {
	my $pid = fork;
	exit if $pid;
	die "$!" unless defined($pid);
	POSIX::setsid() or die "$!";
}

my $ah = IO::Socket::SSL->new(
	'LocalPort'       => $lport,
	'LocalAddr'       => $laddr,
	'Reuse'           => 1,
	'Proto'           => 'tcp',
	'SSL_verify_mode' => '0',
	'SSLdhfile'       => $serverdh,
	'SSL_cert_file'   => $servercert,
	'SSL_key_file'    => $serverkey,
	'Listen'          => 10,
#	'SSL_version' => 'SSLv3',    # SSLv3, SSLv2, TLSv1
#	'SSL_cipher_list' => 'RC4-MD5',
) || die "$!";

$SIG{'CHLD'} = 'IGNORE';
my $num = 0;

while (1) {
	my $ch = $ah->accept();
	if ( !$ch ) {
		print STDERR "cannot accept: $! ", IO::Socket::SSL::errstr(),
			"\n";
		next;
	}
	if ( !$ch ) { print STDERR "cannot accept: $!\n"; next; }
	++$num;
	my $pid = fork();
	if ( !defined($pid) ) { print STDERR "cannot fork while(1) $!\n"; }
	elsif ( $pid == 0 ) {
		$ah->close( SSL_no_shutdown => 1 );
		Run( $ch, $num );
	} else {
		$ch->close( SSL_no_shutdown => 1 );
	}
}

sub hexdump {
	my $bytes = shift;
	my $hex = unpack('H*', $bytes);
	$hex =~ s/(.{8})/$1 /g;
	return $hex;
}

sub Run {
	my ( $ch, $num ) = @_;
	my $th = IO::Socket::SSL->new(
		'PeerAddr'        => $raddr,
		'PeerPort'        => $rport,
#		'SSL_use_cert'    => '0',
#		'SSL_verify_mode' => '0',

		'SSL_version' => 'SSLv3',    # SSLv3, SSLv2, TLSv1
		'SSL_cipher_list' => 'RC4-MD5',
		'Proto'       => 'tcp'
	);
	if ( !$th ) { print "cannot connect $raddr:$rport th: $!"; exit 0; }
	else        { print "connected to $raddr:$rport\n"; }
	my $fh;
	if ( -d $logdir ) {
		$fh = Symbol::gensym();
		my $path = sprintf("%s/%15.5f", $logdir, Time::HiRes::time() );
		open( $fh, '>', $path ) or die "$!";
	}
	$ch->autoflush();
	$th->autoflush();
	my $httpheader = "";
	my $httpbuf    = "";
	while ( $ch || $th ) {
		my $rin = "";
		vec( $rin, fileno($ch), 1 ) = 1 if $ch;
		vec( $rin, fileno($th), 1 ) = 1 if $th;
		my ( $rout, $eout );
		select( $rout = $rin, undef, $eout = $rin, 120 );
		if ( !$rout && !$eout ) { }
		my $cbuffer = "";
		my $tbuffer = "";

		if ($ch
			&& (   vec( $eout, fileno($ch), 1 )
				|| vec( $rout, fileno($ch), 1 ) )
			)
		{
			my $result = sysread( $ch, $tbuffer, $buffersize );
			if ( !defined($result) ) {
				print STDERR "$!\n";
				exit 0;
			}
			if ( $result == 0 ) { exit 0; }
		}
		if ($th
			&& (   vec( $eout, fileno($th), 1 )
				|| vec( $rout, fileno($th), 1 ) )
			)
		{
			my $result = sysread( $th, $cbuffer, $buffersize );
			if ( !defined($result) ) { print STDERR "$!\n"; exit 0; }
			if ( $result == 0 ) { exit 0; }
		}
		if ( $fh && $tbuffer ) {
			print $fh "\n# <<< client\n$tbuffer";
			warn "C>S ", hexdump($tbuffer), "\n";
		}
		while ( my $len = length($tbuffer) ) {
			my $res = syswrite( $th, $tbuffer, $len );
			if ( $res > 0 ) { $tbuffer = substr( $tbuffer, $res ); }
			else            { print STDERR "$!\n"; }
		}
		if ( $fh && $cbuffer ) {
			print $fh "\n# >>> server\n$cbuffer";
			warn "S>C ", hexdump($cbuffer), "\n";
		}
		while ( my $len = length($cbuffer) ) {
			my $res = syswrite( $ch, $cbuffer, $len );
			if ( $res > 0 ) { $cbuffer = substr( $cbuffer, $res ); }
			else            { print STDERR "$!\n"; }
		}
	}
}

