#!/usr/bin/perl
#
# Connects given pseudo-terminal to remote port on a given host via telnet.
# Usage:
#
# PortRedirector [-f configfile] pty_device host [port]
#

use strict;
use POSIX ();
use IPC::Open3 ();
use Fcntl ();

use vars qw( $UPrompt $PPrompt $Username $Password $PidDir );

if( $ARGV[ 0 ] eq '-f' && $ARGV[ 1 ] ){
	shift @ARGV;
	require shift @ARGV;;
}else{
	require '/usr/local/etc/PortRedirector.conf';
}

my $Terminated = 0;
my $TelnetPid = -1;
my ( $Device, $Host, $Port ) = @ARGV;
my $PidFile;

my $in;		# what's have been read
my $pid;
my $unwritten = undef;	# filled before syswrite is called

# Check command line:
die "Bad arguments" unless $Device && $Host;

$PidDir = '/var/run' unless $PidDir;
$PidFile = $Device;
$PidFile =~ s#.*/##;
$PidFile = "$PidDir/PortRedirector-$PidFile.pid";

# install signal handler:
$SIG{'INT'}    = \&handler;
$SIG{'QUIT'}    = \&handler;
$SIG{'HUP'}    = \&handler;
$SIG{'ILL'}    = \&handler;
$SIG{'TRAP'}   = \&handler;
$SIG{'ABRT'}   = \&handler;
$SIG{'EMT'}    = \&handler;
$SIG{'FPE'}    = \&handler;
$SIG{'BUS'}    = \&handler;
$SIG{'SEGV'}   = \&handler;
$SIG{'SYS'}    = \&handler;
$SIG{'PIPE'}   = \&sigpipe_handler;
$SIG{'ALRM'}   = \&handler;
$SIG{'TERM'}   = \&handler;
$SIG{'TSTP'}   = \&handler;
$SIG{'TTIN'}   = \&handler;
$SIG{'TTOU'}   = \&handler;
$SIG{'XCPU'}   = \&handler;
$SIG{'XFSZ'}   = \&handler;
$SIG{'VTALRM'} = \&handler;
$SIG{'PROF'}   = \&handler;
$SIG{'USR1'}   = \&handler;
$SIG{'USR2'}   = \&handler;

# Daemonizing:
exit 0 if fork;
POSIX::setsid();

# Open pty:
sysopen( DEV, $Device, &Fcntl::O_RDWR|&Fcntl::O_NONBLOCK ) || die( "Can't open $Device: $!" );

open_telnet();

# Creating pid file:
if( open( PIDFILE, ">$PidFile" ) ){
	print PIDFILE $$;
	close PIDFILE;
}else{
	die "Can't create $PidFile: $!";
}

print STDERR "PortRedirector started: router $Host, port $Port, device $Device\n";

while( 1 ){
	while( sysread( DEV, $in, 1024 ) ){
		$unwritten = $in;
		syswrite( WRTRFH, $in, length $in );
		if( $Terminated ){
			restart( $unwritten );
			$Terminated = 0;
		}
		$unwritten = undef;
	}
	while( sysread( RDRFH, $in, 1024 ) ){
		syswrite( DEV, $in, length $in );
	}
	# wait a little to prevent CPU consumption:
	select(undef, undef, undef, 0.1);
}

# Called when telnet terminates:
sub sigpipe_handler
{
	$Terminated = 1;
}

# Cleanup
sub handler
{
	my $signal = shift;

	print "Dying on signal ", $signal, "...\n" if $signal;
	close RDRFH;
	close WRTRFH;
	close DEV;
	kill $signal, $TelnetPid if $signal;	# pass signal to child
	unlink "$PidFile";
	exit 0;
}

# Open a telnet session and log in:
sub open_telnet
{
	my $word;

	$TelnetPid = IPC::Open3::open3( \*WRTRFH, \*RDRFH, '', "/usr/bin/telnet $Host $Port" );

	# Enter username:
	if( $Username ){
		$word = '';
		do{
			unless( sysread( RDRFH, $in, 1 ) ){
				print STDERR "$word\n";	# passing telnet's error output
				handler();
			}
			$word = $word . $in;
		}while( $word !~ /$UPrompt$/ );
		syswrite( WRTRFH, "$Username\n", length "$Username\n" );

		if( $Password ){
			# Enter password:
			$word = '';
			do{
				unless( sysread( RDRFH, $in, 1 ) ){
					print STDERR "$word\n";	# passing telnet's error output
					handler();
				}
				$word = $word . $in;
			}while( $word !~ /$PPrompt$/ );
			syswrite( WRTRFH, "$Password\n", length "$Password\n" );
		}
	}

	# Make input from telnet non-blocking:
	fcntl( RDRFH, &Fcntl::F_SETFL, &Fcntl::O_NONBLOCK ) || die "can't fcntl RDRFH: $!";

}

sub restart
{
	my $unwritten = shift;

	close WRTRFH;
	close RDRFH;
	waitpid( $TelnetPid, 0 );	# Prevent telnet from becoming zombie
	open_telnet();
	syswrite( WRTRFH, $unwritten, length $unwritten ) if defined $unwritten;
}
