#!/usr/bin/perl

##################################################################
# This file is part of PAC( Perl Auto Connector)
#
# Copyright (C) 2010-2015  David Torrejon Vaquerizas
# 
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
###################################################################

$|++;

###################################################################
# START : Modules import

use strict;
use warnings;

use FindBin qw ( $RealBin $Bin $Script );
use lib $RealBin, $RealBin . '/ex', $RealBin . '/edit';
use Storable qw ( dclone thaw retrieve fd_retrieve nstore_fd );
use Expect;
use Gnome2::GConf;
use POSIX qw ( strftime );
use Encode qw ( encode decode );
use IO::Socket::INET;
use Encode;
use KeePass;

use Gtk2 '-init';

# END OF : Modules import
########################################################

########################################################
# START : Variables declaration/initialization
my $APPNAME     = 'PAC';
my $APPICON		= $RealBin . '/../res/pac64x64.png';
my $CFG_DIR		= $ENV{'HOME'} . '/.config/pac';
my $SCRIPTS_DIR	= $CFG_DIR . '/scripts';

my @CMD;
my $CFG_FILE;
my $CFG;
my $UUID;
my $GETCMD				= 0;
my $CONNECTED			= 0;
my $PASSWORD_COUNT		= 0;
my $SUDO_PASSWORD_COUNT	= 0;
my $USER_COUNT			= 0;
my %PROXY;
my $PROXYPID;
my $SOCKET;
my $SOCKET_EXEC;
my $PROXYFIED		= 0;

my @KPXWHERE = ( 'comment', 'created', 'password', 'title', 'url', 'username' );

# Define some ANSI colors
my %COLOR = (
	'norm'	=> "\033[m",			# black
	'log'	=> "\033[33m",			# yellow
	'recv'	=> "\033[35m",			# magenta
	'sent'	=> "\033[0m\033[32m",	# green
	'err'	=> "\033[31m"			# red
);

# Create the Expect object
$Expect::Multiline_Matching = 1;
my $EXP = new Expect;
eval { $EXP -> slave -> clone_winsize_from( \*STDIN ); };

# Connect to Gnome's GConf
my $GCONF = Gnome2::GConf::Client -> get_default;

# END OF : Variables declaration/initialization
########################################################

########################################################
# START : Signal handling
sub __disconnect {
	my $signal = shift;
	ctrl( "DISCONNECTING" );
	defined $PROXYPID and kill( 15, $PROXYPID );
	$EXP -> hard_close;
	ctrl( "DISCONNECTED" );
	exit 0;
}
$SIG{'INT'}		= \&__disconnect;
$SIG{'TERM'}	= \&__disconnect;
$SIG{'QUIT'}	= \&__disconnect;
# END OF : Signal handling
########################################################

########################################################
# START : Main program

# Retrieve command line options
$CFG_FILE	= shift;
$UUID		= shift or die $COLOR{'err'} . "ERROR: You must provide a profile to connect to!!" . $COLOR{'norm'};
$GETCMD		= shift // 0;

# Get PROXY from environment
$PROXY{'env'}{'ip'}		= $GCONF -> get_string( '/system/http_proxy/host' ) || $GCONF -> get_string( '/system/proxy/http/host' );
$PROXY{'env'}{'port'}	= $GCONF -> get_int( '/system/http_proxy/port' ) || $GCONF -> get_string( '/system/proxy/http/port' );
$PROXY{'env'}{'user'}	= $GCONF -> get_string( '/system/http_proxy/authentication_user' ) || $GCONF -> get_string( '/system/proxy/http/host/authentication-user' );
$PROXY{'env'}{'pass'}	= $GCONF -> get_string( '/system/http_proxy/authentication_password' ) || $GCONF -> get_string( '/system/proxy/http/host/authentication-password' );

# Load the 'freezed' configuration
$CFG = retrieve( $CFG_FILE ) or die "ERROR: Could not load config file '$CFG_FILE': $!";

# Check some command line options
defined $$CFG{'environments'}{$UUID} or die( "ERROR: Profile '$UUID' does not exist!!" );

`which autossh 1>/dev/null 2>&1`;
my $autossh_bin = ! $?;

# Shorten some variables
my $DEBUG				= $$CFG{'defaults'}{'debug'};
my $COMMAND_PROMPT		= $$CFG{'defaults'}{'command prompt'};
my $USERNAME_PROMPT		= $$CFG{'defaults'}{'username prompt'};
my $PASSWORD_PROMPT		= $$CFG{'defaults'}{'password prompt'};
my $HOSTCHANGE_PROMPT	= $$CFG{'defaults'}{'hostkey changed prompt'};
my $ANYKEY_PROMPT		= $$CFG{'defaults'}{'press any key prompt'};
my $REMOTEHOST_PROMPT	= $$CFG{'defaults'}{'remote host changed prompt'};
my $ACCEPT_KEY			= $$CFG{'defaults'}{'auto accept key'};
my $TIMEOUT_CONNECT		= $$CFG{'defaults'}{'timeout connect'} || undef;
my $TIMEOUT_CMD			= $$CFG{'defaults'}{'timeout command'} || undef;
my $AUTOSSH				= $$CFG{'environments'}{$UUID}{'autossh'} && $autossh_bin;
my $METHOD				= $AUTOSSH ? 'autossh' : $$CFG{'environments'}{$UUID}{'method'};
my $SUDO				= $$CFG{'environments'}{$UUID}{'use sudo'};
my $SUDO_PROMPT			= $$CFG{'defaults'}{'sudo prompt'};
my $SUDO_PASSWORD		= $$CFG{'defaults'}{'sudo password'};
my $IP					= $$CFG{'environments'}{$UUID}{'ip'};
my $PORT				= $$CFG{'environments'}{$UUID}{'port'};
my $USER				= $$CFG{'environments'}{$UUID}{'user'};
my $PASS				= $$CFG{'environments'}{$UUID}{'pass'};
my $AUTH				= $$CFG{'environments'}{$UUID}{'auth type'};
my $PUBKEY				= $$CFG{'environments'}{$UUID}{'public key'};
my $PASSPHRASE			= $$CFG{'environments'}{$UUID}{'passphrase'};
my $PASSPHRASE_USER		= $$CFG{'environments'}{$UUID}{'passphrase user'};
my $AUTHFALLBACK		= $$CFG{'environments'}{$UUID}{'auth fallback'};
my $NAME				= $$CFG{'environments'}{$UUID}{'name'};
my $RESTART				= $$CFG{'environments'}{$UUID}{'autoreconnect'} // 0;
my $SEND_SLOW			= ( $$CFG{'environments'}{$UUID}{'send slow'} // 0 ) / 1000;
my $TITLE				= $$CFG{'environments'}{$UUID}{'title'};
my $EXPECT				= defined $$CFG{'environments'}{$UUID}{'expect'};
my $USE_PROXY			= $$CFG{'defaults'}{'use proxy'};
my $USE_SYSTEM_PROXY	= $$CFG{'defaults'}{'use system proxy'};
my $FORCE_USE_PROXY		= $$CFG{'environments'}{$UUID}{'use proxy'} || '0';
my $USE_PREPEND_COMMAND	= $$CFG{'environments'}{$UUID}{'use prepend command'};
my $PREPEND_COMMAND		= $$CFG{'environments'}{$UUID}{'prepend command'};
my $QUOTE_COMMAND		= $$CFG{'environments'}{$UUID}{'quote command'} && $USE_PREPEND_COMMAND;
my $MANUAL				= $$CFG{'environments'}{$UUID}{'auth type'} eq 'manual';
my $TMP_UUID			= $$CFG{'tmp'}{'uuid'};
my $LOG_FILE			= $$CFG{'tmp'}{'log file'};
my $SOCK_FILE			= $$CFG{'tmp'}{'socket'};
my $SOCK_EXEC_FILE		= $$CFG{'tmp'}{'socket exec'};
my $REMOVE_CTRL_CHARS	= $$CFG{'environments'}{$UUID}{'save session logs'} ? $$CFG{'environments'}{$UUID}{'remove control chars'} : $$CFG{'defaults'}{'remove control chars'};
my @KEEPASS				= @{ $$CFG{'keepass'} // [] };
$PROXY{'cfg'}{'ip'}		= $$CFG{'defaults'}{'proxy ip'};
$PROXY{'cfg'}{'port'}	= $$CFG{'defaults'}{'proxy port'};
$PROXY{'cfg'}{'user'}	= $$CFG{'defaults'}{'proxy user'};
$PROXY{'cfg'}{'pass'}	= $$CFG{'defaults'}{'proxy pass'};

$IP						= subst( $IP );
$PORT					= subst( $PORT );
if ( $$CFG{'environments'}{$UUID}{'infer user pass from KPX'} && $$CFG{'defaults'}{'keepass'}{'use_keepass'} ) {
	if ( ! $$CFG{'defaults'}{'keepass'}{'use_keepass'} ) {
		msg( "ERROR: KeePassX can not be used because 'KeePassX' is not enabled under 'Preferences -> KeePass Options'" );
		exit 1;
	}
	
	my $re = $$CFG{'environments'}{$UUID}{'KPX title regexp'};
	my $where = $KPXWHERE[ $$CFG{'environments'}{$UUID}{'infer from KPX where'} ];
	my @found = findKP( \@KEEPASS, $where, qr/$re/ );
	if ( ! scalar @found)			{ msg( "ERROR: No entry '$where' found on KeePassX matching '$re'" ); exit 1; }
	elsif ( ( ( scalar @found ) > 1 ) && $$CFG{'defaults'}{'keepass'}{'ask_user'} )	{
		msg( "INFO: Found " . ( scalar @found ) . " entries with '$where' matching '$re' while inferring. Asking user..." );
		my $tmp = "<ASK:KeePass '$where' matching '$TITLE':"; foreach my $hash ( @found ) { $tmp .= '|' . $$hash{$where}; } $tmp .= '>';
		my ( $str, $pos ) = subst( $tmp );
		if ( ! defined $str ) {
			msg( "INFO: Connection canceled by user" );
			exit 1;
		}
		( $USER, $PASS ) = ( $found[$pos]{username}, $found[$pos]{password} );
	} else {
		msg( "INFO: Using with $where '$found[0]{$where}' matching '$re' (username: $USER, password: hidden!!) while inferring..." );
		( $USER, $PASS ) = ( $found[0]{username}, $found[0]{password} );
	}
} else {
	$USER				= subst( $USER);
	$PASS				= subst( $PASS );
}
$PASSPHRASE				= subst( $PASSPHRASE );
$PASSPHRASE_USER		= subst( $PASSPHRASE_USER );
$PROXY{'cfg'}{'ip'}		= subst( $PROXY{'cfg'}{'ip'} );
$PROXY{'cfg'}{'port'}	= subst( $PROXY{'cfg'}{'port'} );
$PROXY{'cfg'}{'user'}	= subst( $PROXY{'cfg'}{'user'} );
$PROXY{'cfg'}{'pass'}	= subst( $PROXY{'cfg'}{'pass'} );

# Build initial SSH connection command
my $CONNECT_OPTS		= $$CFG{'environments'}{$UUID}{'options'} || '';

my $proxy_ip	= $PROXY{ ( $USE_SYSTEM_PROXY ) ? 'env' : 'cfg' }{'ip'};
my $proxy_port	= $PROXY{ ( $USE_SYSTEM_PROXY ) ? 'env' : 'cfg' }{'port'};
my $proxy_user	= $PROXY{ ( $USE_SYSTEM_PROXY ) ? 'env' : 'cfg' }{'user'} // '';
my $proxy_pass	= $PROXY{ ( $USE_SYSTEM_PROXY ) ? 'env' : 'cfg' }{'pass'} // '';
if ( $$CFG{'environments'}{$UUID}{'use proxy'} == 1 ) {
	$proxy_ip	= $$CFG{'environments'}{$UUID}{'proxy ip'};
	$proxy_port	= $$CFG{'environments'}{$UUID}{'proxy port'};
	$proxy_user	= $$CFG{'environments'}{$UUID}{'proxy user'};
	$proxy_pass	= $$CFG{'environments'}{$UUID}{'proxy pass'};
}

if ( ! $GETCMD ) {
	$SOCKET = IO::Socket::UNIX -> new(
		Type	=> SOCK_STREAM,
		Peer	=> $SOCK_FILE
	) or die "ERROR: Could not open SOCKET file '$SOCK_FILE' for connecting: $!";
	$SOCKET -> autoflush;
	
	die "ERROR: Service listening at file '$SOCK_FILE' is not PAC" unless auth( $SOCKET );
	
	$SOCKET_EXEC = IO::Socket::UNIX -> new(
		Type	=> SOCK_STREAM,
		Peer	=> $SOCK_EXEC_FILE
	) or die "ERROR: Could not open SOCKET file '$SOCK_EXEC_FILE' for connecting: $!";
	$SOCKET_EXEC -> autoflush;
}

my %_W;
my $INT = 0;

########################################################
# START : Procedures definition

sub _removeEscapeSeqs {
	my $string = shift // '';
	
	$string =~ s/(\x1B\[\d+(;\d+)?m)|(\cM)//go;
	$string =~ s/(\x1B|\x08)\[.//go;
	$string =~ s/\x1B|\x08//go;
	
	return $string;
}

sub msg {
	my $msg = shift or die $COLOR{'err'} . "ERROR: You must provide a message to 'msg'!!" . $COLOR{'norm'};
	
	print $COLOR{'log'} . "[$Script($$)][$NAME][$TITLE]: $msg\r\f" . $COLOR{'norm'};
	return 1;
}

sub ctrl {
	my $msg = shift or die "ERROR: You must provide a message to 'ctrl'!!";
	
	$DEBUG and msg( $msg );
	return 1 unless defined $SOCKET;
	
	my $wout = '';
	vec( $wout, fileno( $SOCKET ), 1 ) = 1;
	select( undef, $wout, undef, 5 ) or die "ERROR: Could not write to PACMain SOCKET at $SOCK_FILE: $!";
	
	$SOCKET -> send( 'PAC_MSG_START[' . encode( 'UTF-16', $msg ) . ']PAC_MSG_END' );
	return 1;
} 
sub auth {
	my $socket = shift;
	
	&ctrl( "!!_PAC_AUTH_[$$CFG{tmp}{uuid}]!!" );
	my $auth = '';
	sysread( $socket, $auth, 1024 );
	return $auth eq "!!_PAC_AUTH_[$$CFG{tmp}{uuid}]!!";
}

sub subst {
	my $string = shift // '';
	
	my $pos = -1;
	
	return $string unless defined $$CFG{'environments'}{$UUID};
	
	$string =~ s/\<\<proxy_host\>\>/$proxy_ip/g;
	$string =~ s/\<\<proxy_port\>\>/$proxy_port/g;
	$string =~ s/\<\<proxy_user\>\>/$proxy_user/g;
	$string =~ s/\<\<proxy_pass\>\>/$proxy_pass/g;
	
	my $tstamp = time;
	my ( $dy, $dm, $dd, $th, $tm, $ts ) = split( '_', strftime( "%Y_%m_%d_%H_%M_%S", localtime ) );
	my $name	= $$CFG{'environments'}{$UUID}{name};
	my $title	= $$CFG{'environments'}{$UUID}{title};
	my $ip		= $$CFG{'environments'}{$UUID}{ip};
	my $user	= $$CFG{'environments'}{$UUID}{user};
	my $pass	= $$CFG{'environments'}{$UUID}{pass};
	
	while ( $string =~ /<UUID>/go )			{ $string =~ s/<UUID>/$UUID/g;			}
	while ( $string =~ /<TIMESTAMP>/go )	{ $string =~ s/<TIMESTAMP>/$tstamp/g;	}
	while ( $string =~ /<DATE_Y>/go )		{ $string =~ s/<DATE_Y>/$dy/g;			}
	while ( $string =~ /<DATE_M>/go )		{ $string =~ s/<DATE_M>/$dm/g;			}
	while ( $string =~ /<DATE_D>/go )		{ $string =~ s/<DATE_D>/$dd/g;			}
	while ( $string =~ /<TIME_H>/go )		{ $string =~ s/<TIME_H>/$th/g;			}
	while ( $string =~ /<TIME_M>/go )		{ $string =~ s/<TIME_M>/$tm/g;			}
	while ( $string =~ /<TIME_S>/go )		{ $string =~ s/<TIME_S>/$ts/g;			}
	while ( $string =~ /<NAME>/go )			{ $string =~ s/<NAME>/$name/g;			}
	while ( $string =~ /<TITLE>/go )		{ $string =~ s/<TITLE>/$title/g; 		}
	while ( $string =~ /<IP>/go )			{ $string =~ s/<IP>/$ip/g; 				}
	while ( $string =~ /<USER>/go )			{ $string =~ s/<USER>/$user/g; 			}
	while ( $string =~ /<PASS>/go )			{ $string =~ s/<PASS>/$pass/g;			}
	
	# Replace '<command prompt>' with user defined value for command prompt
	while ( $string =~ /<command prompt>/go ) { $string = $COMMAND_PROMPT; }
	
	# Replace <KPXRE_GET_(title|username|password|url)_WHERE_(title|username|password|url)==(.+?)==> with KeePassX value
	while ( $string =~ /<KPXRE_GET_(title|username|password|url)_WHERE_(title|username|password|url)==(.+?)==>/go ) {
		my $what	= $1;
		my $where	= $2;
		my $var		= $3;
		my $regexp	= qr/$var/;
		
		if ( ! $$CFG{'defaults'}{'keepass'}{'use_keepass'} ) {
			msg( "WARNING: KeePassX variable '$var' can not be used because 'KeePassX' is not enabled under 'Preferences -> KeePass Options'" );
			next;
		}
		
		my @found = findKP( \@KEEPASS, $where, $regexp );
		if ( ! scalar @found)			{ msg( "ERROR: No entry '$where' found on KeePassX matching '$var'" ); exit 1; }
		elsif ( ( ( scalar @found ) > 1 ) && $$CFG{'defaults'}{'keepass'}{'ask_user'} )	{
			msg( "INFO: Found more than one entry for '$where' with value '$var'. Asking user..." );
			my $tmp = "<ASK:KeePass Passwords matching '$where' like '$var':"; foreach my $hash ( @found ) { $tmp .= '|' . $$hash{$what}; } $tmp .= '>';
			$string =~ s/<KPXRE_GET_${what}_WHERE_${where}==\Q$var\E==>/$tmp/g;
		} else {
			msg( "INFO: Found " . ( scalar( @found ) ) ." entries for '$where' with value '$var'. Selected first entry..." ) if ( scalar( @found ) > 1 ) && ! $$CFG{'defaults'}{'keepass'}{'ask_user'};
			$string =~ s/<KPXRE_GET_${what}_WHERE_${where}==\Q$var\E==>/$found[0]{$what}/g;
		}
	}
	
	# Replace <KPX_(title|username|url):*> with KeePassX password value
	while ( $string =~ /<KPX_(title|username|url):(.+?)>/go ) {
		my $type	= $1;
		my $var		= $2;
		
		if ( ! $$CFG{'defaults'}{'keepass'}{'use_keepass'} ) {
			msg( "WARNING: KeePassX variable '$var' can not be used because 'KeePassX' is not enabled under 'Preferences -> KeePass Options'" );
			next;
		}
		
		my @found = findKP( \@KEEPASS, $type, $var );
		if ( ! scalar @found)			{ msg( "ERROR: No entry '$type' found on KeePassX matching '$var'" ); exit 1; }
		elsif ( ( ( scalar @found ) > 1 ) && $$CFG{'defaults'}{'keepass'}{'ask_user'} )	{
			msg( "WARNING: Found more than one entry for '$type' with value '$var'. Asking user..." );
			my $tmp = "<ASK:KeePass Passwords matching '$type' like '$var':"; foreach my $hash ( @found ) { $tmp .= '|' . $$hash{password}; } $tmp .= '>';
			$string =~ s/<KPX_$type:\Q$var\E>/$tmp/g;
		} else {
			$string =~ s/<KPX_$type:\Q$var\E>/$found[0]{password}/g;
		}
	}
	
	# Replace '<GV:.+>' with user saved global variables for '$connection_cmd' execution
	while ( $string =~ /<GV:(.+?)>/go ) {
		my $var = $1;
		if ( defined $$CFG{'defaults'}{'global variables'}{ $var } ) {
			my $val = $$CFG{'defaults'}{'global variables'}{ $var }{'value'} // '';
			$string =~ s/<GV:$var>/$val/g;
		}
	}
	
	# Replace '<V:#>' with user saved variables for '$connection_cmd'
	while ( $string =~ /<V:(\d+?)>/go ) {
		my $var = $1;
		if ( defined $$CFG{'environments'}{$UUID}{'variables'}[ $var ] ) {
			my $val = $$CFG{'environments'}{$UUID}{'variables'}[ $var ] // '';
			$string =~ s/<V:$var>/$val/g;
		}
	}
	
	# Replace '<ENV:#>' with environment variables for '$connection_cmd'
	while ( $string =~ /<ENV:(.+?)>/go ) {
		my $var = $1;
		defined $ENV{$var} and $string =~ s/<ENV:\Q$var\E>/$ENV{$var}/g;
	}
	
	# Replace '<ASK:#>' with user provided data for 'cmd' execution
	while ( $string =~ /<ASK:(\d+?)>/go ) {
		my $var = $1;
		my $val = wEnterValue( undef, "<b>Variable substitution '$var'</b>" , $string ) // return undef;
		last unless defined $val;
		$string =~ s/<ASK:\Q$var\E>/$val/g;
	}
	
	# Replace '<ASK:*>' with user provided data for 'cmd' execution
	while ( $string =~ /<ASK:(.+\|.+?)>/go ) {
		my $var = $1;
		my @list;
		@list = split( '\|', $var );
		my $desc = shift @list;
		my $ret;
		( $ret, $pos ) = wEnterValue( undef, "<b>Choose variable value:</b>" , $desc, \@list );
		defined $ret or return undef;
		$string =~ s/<ASK:(.+\|.+?)>/$ret/g;
	}
	
	# Replace '<ASK:*>' with user provided data for 'cmd' execution
	while ( $string =~ /<ASK:(.+?)>/go ) {
		my $var = $1;
		my $val = wEnterValue( undef, "<b>Variable substitution</b>" , $var ) // return undef;
		last unless defined $val;
		$string =~ s/<ASK:\Q$var\E>/$val/g;
	}
	
	# Replace '<CMD:#>' with the result of executing 'cmd'
	while ( $string =~ /<CMD:(.+?)>/go ) {
		my $var = $1;
		my $output = `$var 2>&1`;
		chomp $output;
		$string =~ s/<CMD:\Q$var\E>/$output/g;
	}
	
	return wantarray ? ( $string, $pos ) : $string;
}

sub wEnterValue {
	my $self	= shift;
	my $lblup	= shift;
	my $lbldown	= shift;
	my $default	= shift;
	my $visible	= shift // 1;
	
	my @list;
	my $pos = -1;
	
	if ( ! defined $default ) { $default = ''; }
	elsif ( ref( $default ) ) { @list = @{ $default }; }
	
	# Create the dialog window,
	$_W{window}{data} = Gtk2::Dialog -> new_with_buttons(
		"$APPNAME : Enter data",
		undef,
		'modal',
		'gtk-ok'		=> 'ok',
		'gtk-cancel'	=> 'cancel'
	);
	# and setup some dialog properties.
	$_W{window}{data} -> set_default_response( 'ok' );
	$_W{window}{data} -> set_position( 'center' );
	$_W{window}{data} -> set_icon_from_file( $APPICON );
	$_W{window}{data} -> set_size_request( -1, -1 );
	$_W{window}{data} -> set_resizable( 0 );
	$_W{window}{data} -> set_border_width( 5 );
		
		# Create an HBox to contain a picture and a label
		$_W{window}{gui}{hbox} = Gtk2::HBox -> new( 0, 0 );
		$_W{window}{data} -> vbox -> pack_start( $_W{window}{gui}{hbox}, 1, 1, 5 );
		$_W{window}{gui}{hbox} -> set_border_width( 5 );
			
			# Create image
			$_W{window}{gui}{img} = Gtk2::Image -> new_from_stock( 'gtk-edit', 'dialog' );
			$_W{window}{gui}{hbox} -> pack_start( $_W{window}{gui}{img}, 0, 1, 5 );
			
			# Create 1st label
			$_W{window}{gui}{lblup} = Gtk2::Label -> new;
			$_W{window}{gui}{hbox} -> pack_start( $_W{window}{gui}{lblup}, 1, 1, 5 );
			$_W{window}{gui}{lblup} -> set_markup( $lblup );
		
		# Create 2nd label
		$_W{window}{gui}{lbldwn} = Gtk2::Label -> new;
		$_W{window}{data} -> vbox -> pack_start( $_W{window}{gui}{lbldwn}, 1, 1, 5 );
		$_W{window}{gui}{lbldwn} -> set_text( $lbldown );
		
		if ( @list ) {
			# Create combobox widget
			$_W{window}{gui}{comboList} = Gtk2::ComboBox -> new_text;
			$_W{window}{data} -> vbox -> pack_start( $_W{window}{gui}{comboList}, 0, 1, 0 );
			$_W{window}{gui}{comboList} -> set_property( 'can_focus', 0 );
			foreach my $text ( @list ) { $_W{window}{gui}{comboList} -> append_text( $text ) };
			$_W{window}{gui}{comboList} -> set_active( 0 );
		} else {
			# Create the entry widget
			$_W{window}{gui}{entry} = Gtk2::Entry -> new;
			$_W{window}{data} -> vbox -> pack_start( $_W{window}{gui}{entry}, 0, 1, 5 );
			$_W{window}{gui}{entry} -> set_text( $default );
			$_W{window}{gui}{entry} -> set_activates_default( 1 );
			$_W{window}{gui}{entry} -> set_visibility( $visible );
		}
	
	# Show the window (in a modal fashion)
	$_W{window}{data} -> show_all;
	my $ok = $_W{window}{data} -> run;
	
	my $val;
	if ( @list )	{ $val = ( $ok eq 'ok' ) ? $_W{window}{gui}{comboList} -> get_active_text : undef; $pos = $_W{window}{gui}{comboList} -> get_active; }
	else			{ $val = ( $ok eq 'ok' ) ? $_W{window}{gui}{entry} -> get_chars( 0, -1 ) : undef; }
	
	$_W{window}{data} -> destroy;
	Gtk2 -> main_iteration while Gtk2 -> events_pending;
	undef %_W;
	
	return wantarray ? ( $val, $pos ) : $val;
}

sub send_slow {
	if ( $SEND_SLOW )	{ $_[0] -> send_slow( $SEND_SLOW, encode( 'utf8', $_[1] ) ); }
	else				{ $_[0] -> send( encode( 'utf8', $_[1] ) ); }
}

sub _getPrompt {
	# Delete any data accumulated before launching the command
	$EXP -> clear_accum;
	$EXP -> restart_timeout_upon_receive( 1 );
	
	send_slow( $EXP, "\n" );
	my ( $matched_pattern_position, $error, $successfully_matching_string, $before_match, $after_match ) = $EXP -> expect( 0.5, [ timeout => sub { 1; } ], [ /\R.+\R/ => sub { 1; } ] );
	$before_match =~ s/\R|\r|\f|\n//go;
	$before_match =~ s/(^\s*)|(\s*$)//go;
	return $before_match;
}

sub _execAndCapture {
	my $tmp = shift;
	
	my $pipe	= $$tmp{pipe};
	my $tee		= $$tmp{tee};
	my $cmd		= $$tmp{cmd};
	my $pattern	= $$tmp{prompt};
	my $capture	= $$tmp{capture};
	my $ctrl	= $$tmp{ctrl};
	my $lines	= $$tmp{lines};
	my $intro	= $$tmp{intro};
	
	$cmd = subst( $cmd );
	$cmd ||= $$ctrl{cmd};
	
	# Delete any data accumulated before launching the command
	$EXP -> clear_accum;
	$EXP -> restart_timeout_upon_receive( 1 );
	
	if ( $intro ) { send_slow( $EXP, $cmd . ( ( $METHOD =~ /^.*3270.*$/ ) || ( $METHOD eq 'IBM 3270/5250' ) ? "\r\f" : "\n" ) ); }
	else { send_slow( $EXP, $cmd ); }
	( ! defined $pattern && ( $pipe || $tee || $capture || defined $ctrl ) ) and send_slow( $EXP, '##__PAC__PIPE__##' );
	
	$TIMEOUT_CMD = $$CFG{'environments'}{$UUID}{'terminal options'}{'use personal settings'} ?
		( $$CFG{'environments'}{$UUID}{'terminal options'}{'timeout command'} || undef )
		:
		( $$CFG{'defaults'}{'timeout command'} || undef );
	
	$CONNECTED = 1;
	
	ctrl( "PIPE_WAIT[" . ( $TIMEOUT_CMD // 'indefinitely' ) . "][" . ( ( ! defined $pattern && ( $pipe || $tee || $capture || defined $ctrl ) ) ? '##__PAC__PIPE__##' : $pattern // '' ) . "]" );
	
	my $ok = 0;
	# Wait for pattern prompt before continue...
	my ( $matched_pattern_position, $error, $successfully_matching_string, $before_match, $after_match ) = $EXP -> expect(
		
		$TIMEOUT_CMD,
		
		[ timeout => sub { ctrl( "ERROR:$TIMEOUT_CMD seconds waiting for expected input" ); }],
		
		[ eof => sub {
			ctrl( "ERROR:Connection ended by remote peer!! " . $EXP -> set_accum() );
			$CONNECTED = 0;
			$EXP -> hard_close;
		}],
		
		[ ( ( ! defined $pattern && ( $pipe || $tee || $capture || defined $ctrl ) ) ? '##__PAC__PIPE__##' : $pattern ) => sub { $ok = 1; } ]
	);
	
	if ( $ok && ( $pipe || $tee || $capture || defined $ctrl ) ) {
		( ! defined $pattern && ( $pipe || $tee || $capture || defined $ctrl ) ) and send_slow( $EXP, "\x08"x17 );
		
		# Separate lines (\R matches *all kind* of new-lines: \n, \f, \r, ...)
		my @out_lines = split( /\R/, $before_match );
		# Remove last (prompt) line from output
		pop @out_lines;
		# Re-join in a single string
		my $out = join( "\n", @out_lines );
		
		if ( $capture ) { return $out; }
		elsif ( defined $ctrl ) {
			$lines //= 1;
			my $tmp = join( "\n", splice( @out_lines, 1, $lines ) );
			# Line #0 uses to be the executed command; line #1 uses to be the output of that command (one line expected!!)
			ctrl( "$$ctrl{ctrl}:$tmp" );
		} elsif ( $tee ) {
			$tee =~ /^>{1,2}(.+)$/go or $tee = '>' . $tee;
			open F, "$tee" or return 1;
			print F $out;
			close F;
		} else {
			# Advise PAC to receive command output
			ctrl( "EXEC:RECEIVE_OUT" );
			nstore_fd( \$out, $SOCKET_EXEC ) or die "ERROR:$!";
		}
	} else {
		# Advise PAC NOT to receive command output
		ctrl( "EXEC:DISCARD_OUT" );
	}
	
	return undef;
}

sub _execScript {
	my $tmp = shift;
	
	return 0 unless defined $tmp;
	
	my $name	= $$tmp{name};
	my $script	= $$tmp{script};
	return 0 unless defined $script && defined $name;
	
	our %COMMON;	undef %COMMON;
	our %PAC;		undef %PAC;
	our %TERMINAL;	undef %TERMINAL;
	our %SHARED;	undef %SHARED;
	
	$COMMON{subst}		= sub {
		my $txt = shift // '';
		ctrl( "SCRIPT_SUB_SUBST[NAME:$name][PID:$$][PARAMS:$txt]" );
		return subst( $txt );
	};
	$COMMON{cfg}		= sub { my $ref = shift // 0; return $ref ? $CFG : dclone( $CFG ); };
	$COMMON{cfg_sanity}	= sub { _cfgSanityCheck( shift ); };
	$COMMON{del_esc}	= sub { return _removeEscapeSeqs( shift // '' ); };
	
	$TERMINAL{exp}		= $EXP;
	$TERMINAL{name}		= $NAME;
	$TERMINAL{uuid}		= $UUID;
	$TERMINAL{tmp_uuid}	= $TMP_UUID;
	$TERMINAL{error}	= '';
	$TERMINAL{ask}		= sub {
		my $txt		= shift;
		my $visible	= shift // 1;
		
		ctrl( "SCRIPT_SUB_ASK[NAME:$name][PID:$$][PARAMS:$txt, $visible]" );
		
		return wEnterValue( undef, "<b>PAC SCRIPT USER INPUT</b>" , $txt, undef, $visible );
	};
	$TERMINAL{msg}		= sub { msg( subst( shift ) ); };
	$TERMINAL{log}		= sub {
		my $file = shift // '';
		$file = subst( $file );
		
		ctrl( "SCRIPT_SUB_LOG[NAME:$name][PID:$$][PARAMS:$file]" );
		
		$EXP -> log_file -> flush;
		$EXP -> log_file( $file );
		return $EXP -> log_file;
	};
	$TERMINAL{send}		= sub {
		my $txt = shift // '';
		$txt = subst( $txt );
		
		ctrl( "SCRIPT_SUB_SEND[NAME:$name][PID:$$][PARAMS:$txt]" );
		
		$EXP -> clear_accum;
		send_slow( $EXP, $txt );
	};
	$TERMINAL{get_prompt}	= sub {
		my $del_esq = shift // 1;
		ctrl( "SCRIPT_SUB_GET_PROMPT[NAME:$name][PID:$$][PARAMS:]" );
		my $prompt = _getPrompt();
		$del_esq and $prompt = _removeEscapeSeqs( $prompt );
		return "\Q$prompt\E";
	};
	$TERMINAL{send_get}	= sub {
		my $txt		= shift // '';
		my $intro	= shift // 1;
		
		ctrl( "SCRIPT_SUB_SEND_GET[NAME:$name][PID:$$][PARAMS:$txt]" );
		
		my $out = _execAndCapture( { 'capture' => 1, 'cmd' => $txt, 'intro' => $intro } );
		return undef unless defined $out;
		$out =~ s/^.+?\R//go;
		return _removeEscapeSeqs( $out );
	};
	$TERMINAL{expect}	= sub {
		my $pattern	= shift // '';
		my $tmout	= shift // 1;
		
		ctrl( "SCRIPT_SUB_EXPECT[NAME:$name][PID:$$][PARAMS:$pattern, $tmout]" );
		
		$pattern = subst( $pattern );
		$tmout = subst( $tmout );
		
		$TERMINAL{out1}		= undef;
		$TERMINAL{out2}		= undef;
		$TERMINAL{error}	= '';
		
		my $wait = $tmout;
		while ( $wait > 0 ) {
			$EXP -> expect(
				1,
				[ eof		=> sub { $TERMINAL{error} = "Connection closed while waiting for pattern '$pattern'", exit 1; } ],
				[ $pattern	=> sub {
					$TERMINAL{out1} = $EXP -> before;
					$TERMINAL{out2} = $EXP -> after;
					$TERMINAL{error} = '';
					last;
				} ]
			);
			$wait--;
		}
		if ( ! $wait ) {
			$TERMINAL{error} = "Timeout ($tmout seconds) waiting for pattern '$pattern'";
			return 0;
		}
		return $TERMINAL{error} eq '';
	};
	
	# Start PAC Script
	ctrl( "SCRIPT_START[NAME:$name]" );
    
	no warnings ( 'redefine' );
	
    *OLDERR = *STDERR;
	open STDERR, ">/dev/null";
	
	eval $script;
	%SHARED = %{ $$tmp{shared} };
	
	*STDERR = *OLDERR;
	
	$@ and return 0;
	use warnings;
	
	return 1 unless defined &CONNECTION;
	eval {
		local $SIG{'TERM'}	= sub {
			ctrl( "SCRIPT_STOPPED_MANUALLY[NAME:$name]" );
			msg( "PAC Script '$name' terminated by user request (TERM(15) signal)" );
			defined $_W{window}{data} and $_W{window}{data} -> destroy;
			undef %_W;
			Gtk2 -> main_iteration while Gtk2 -> events_pending;
			die;
		};
		
		&CONNECTION;
	};
	
	ctrl( "SCRIPT_STOP[NAME:$name]" );
	
	return 1;
}

sub findKP {
	my $kp		= shift;
	my $where	= shift // 'title';
	my $what	= shift // qr/.*/;
	
	my @found;
	foreach my $hash ( @{ $kp } ) {
		if ( ref( $what ) eq 'Regexp' )	{ next unless $$hash{$where} =~ /^$what$/; }
		else							{ next unless $$hash{$where} eq $what; }
		push( @found, {
			title		=> $$hash{title},
			url			=> $$hash{url},
			username	=> $$hash{username},
			password	=> $$hash{password},
			created		=> $$hash{created},
			comment		=> $$hash{comment}
		} );
	}
	
	return wantarray ? @found : scalar( @found );
}

# END OF : Procedures definition
########################################################

# Prepare the threaded proxy tunnel
if ( ( ( $USE_PROXY && $FORCE_USE_PROXY < 2 || ( $FORCE_USE_PROXY == 1 ) ) ) && $proxy_ip && $proxy_port ) {
	require IO::Socket;
	require IO::Handle; # Just for autoflush... :(
    
	socketpair( CHILD, PARENT, AF_UNIX, SOCK_STREAM, PF_UNSPEC ) or  die "socketpair: $!";
    CHILD -> autoflush( 1 );
    PARENT -> autoflush( 1 );
	
	my $LOCAL_PORT = 0;
	
	if ( $PROXYPID = fork ) {
		# Parent
		close PARENT;
		
		# Wait until read $LOCAL_PORT
		ctrl( "PROXY_THREAD:Waiting for the thread to start the proxy tunnel..." );
		chomp( $LOCAL_PORT = <CHILD> );
		ctrl( "PROXY_THREAD:Built tunnel to $IP:$PORT through proxy $proxy_ip:$proxy_port ($proxy_user/$proxy_pass) using local port $LOCAL_PORT..." );
		
		select( undef, undef, undef, 0.5 );
		
		# If every thing went ok, modify IP and PORT to map the tunneled connection
		$IP		= 'localhost';
		$PORT	= $LOCAL_PORT;
		
	} elsif ( defined $PROXYPID ) {
		# Children
		close CHILD;
		
		local $SIG{'INT'}	= sub { exit; };
		local $SIG{'TERM'}	= $SIG{'INT'};
		local $SIG{'QUIT'}	= $SIG{'INT'};
		local $SIG{'HUP'}	= $SIG{'INT'};
		local $SIG{'USR1'}	= $SIG{'INT'};
		local $SIG{'USR2'}	= $SIG{'INT'};
		
		require Net::Proxy;
		my $proxy;
		
		do {
			$proxy = Net::Proxy -> new( {
				in => {
					type		=> 'tcp',
					port		=> $LOCAL_PORT = 60000 + int( rand( 1000 ) ),
				},  
				out => {
					type		=> 'tcp', # 'tcp' method allows using SOCKs proxies!! :)
					host		=> $IP,
					port		=> $PORT,
					proxy_host	=> $proxy_ip,
					proxy_port	=> $proxy_port,
					proxy_user	=> $proxy_user,
					proxy_pass	=> $proxy_pass,
					proxy_agent	=> "PAC Manager (http://sourceforge.net/projects/pacmanager)"
				}
			} );
		} until $proxy;
		
		$proxy -> set_verbosity( 0 );
		$proxy -> register;
		
		# Send the parent process the new created local port
		print PARENT "$LOCAL_PORT\n";
		
		Net::Proxy -> mainloop( 1 );
		
		exit 0;
	} else {
		$CONNECTED = 0;
		ctrl( "ERROR:Could not 'fork' to create proxy object ($!)" );
		ctrl( "CLOSE" );
		exit 1;
	}
}

# Choose and prepare the connection method
my $connection_cmd = '';
my $connection_txt = '';

if ( defined $METHOD ) {
	##############################################
	# TERMINAL METHODS (ssh, telnet, etc)
	##############################################
	if ( ( $METHOD =~ /^.*ssh.*$/ ) || ( $METHOD eq 'SSH' ) ) {
		$METHOD = 'ssh' unless $METHOD eq 'autossh';
		my $key = '';
		if ( $AUTH eq 'publickey' ) {
			$key = "-i \"$PUBKEY\"" . ( $AUTHFALLBACK ? '' : ' -o "PreferredAuthentications=publickey"' );
			$USER = $PASSPHRASE_USER;
			$PASS = $PASSPHRASE;
		} elsif ( ! $AUTHFALLBACK ) {
			$key = '-o "PreferredAuthentications=password,keyboard-interactive"';
		}
		$connection_cmd = "$METHOD -p $PORT $key $CONNECT_OPTS " . ( $USER ? "-l $USER " : '' ) . "$IP";
		$connection_txt = $connection_cmd;
	} elsif ( ( $METHOD =~ /^.*mosh.*$/ ) || ( $METHOD eq 'MOSH' ) ) {
		$METHOD = 'mosh';
		$PORT != 22 and $CONNECT_OPTS .= " --ssh=\"ssh -p $PORT\"";
		$connection_cmd = "$METHOD $CONNECT_OPTS $USER\@$IP";
		$connection_txt = $connection_cmd;
		print "*$connection_cmd*\n";
	} elsif ( ( $METHOD =~ /^.*sftp.*$/ ) || ( $METHOD eq 'SFTP' ) ) {
		$METHOD = 'sftp';
		my $key = '';
		if ( $AUTH eq 'publickey' ) {
			$key = "-i \"$PUBKEY\"" . ( $AUTHFALLBACK ? '' : ' -o "PreferredAuthentications=publickey"' );
			$USER = $PASSPHRASE_USER;
			$PASS = $PASSPHRASE;
		} elsif ( ! $AUTHFALLBACK ) {
			$key = '-o "PreferredAuthentications=password,keyboard-interactive"';
		}
		$connection_cmd = "$METHOD $key -P $PORT $CONNECT_OPTS " . ( $USER ? "$USER@" : '' ) . "$IP";
		$connection_txt = $connection_cmd;
	} elsif ( ( $METHOD =~ /^.*telnet.*$/ ) || ( $METHOD eq 'Telnet' ) ) {
		$METHOD = 'telnet';
		my $port = ( $PORT == 23 ) ? '' : $PORT;
		$connection_cmd = "$METHOD $CONNECT_OPTS $IP $port";
		$connection_txt = $connection_cmd;
	} elsif ( ( $METHOD =~ /^.*ftp*$/ ) || ( $METHOD eq 'FTP' ) ) {
		$METHOD = 'ftp';
		my $port = ( $PORT == 21 ) ? '' : $PORT;
		$connection_cmd = "$METHOD $CONNECT_OPTS $IP $port";
		$connection_txt = $connection_cmd;
	} elsif ( ( $METHOD =~ /^.*cadaver*$/ ) || ( $METHOD eq 'WebDAV' ) ) {
		$METHOD = 'cadaver';
		$connection_cmd = "$METHOD $CONNECT_OPTS $IP";
		$connection_txt = $connection_cmd;
	} elsif ( ( $METHOD =~ /^.*cu$/ ) || ( $METHOD eq 'Serial (cu)' ) ) {
		$METHOD = 'cu';
		$connection_cmd = "$METHOD $CONNECT_OPTS $IP";
		$connection_txt = $connection_cmd;
	} elsif ( ( $METHOD =~ /^.*remote-tty$/ ) || ( $METHOD eq 'Serial (remote-tty)' ) ) {
		$METHOD = 'remote-tty';
		$connection_cmd = "$METHOD $CONNECT_OPTS" . ( $USER ? " -l $USER" : '' ) . " $IP";
		$connection_txt = $connection_cmd;
	} elsif ( ( $METHOD =~ /^.*3270.*$/ ) || ( $METHOD eq 'IBM 3270/5250' ) ) {
		$METHOD = 'c3270';
		$CONNECT_OPTS =~ s/\s+-prepend_([P|S|N|L])//go;
		my $modifier = $1;
		$connection_cmd = "$METHOD $CONNECT_OPTS " . ( $modifier ? "$modifier:" : '' ) . "[$IP]:$PORT";
		$connection_txt = $connection_cmd;
	}
	##############################################
	# TERMINAL DESKTOP METHODS (rdp, vnc, etc)
	##############################################
	elsif	( $METHOD =~ /^.*RDP \((.+)\).*$/ ) {
		$METHOD = $1;
		if ( ( defined $$CFG{'tmp'}{'xid'} ) && ( $METHOD eq 'rdesktop' ) ) {
			$connection_cmd = "$METHOD -X $$CFG{'tmp'}{'xid'} -g $$CFG{'tmp'}{'width'}x$$CFG{'tmp'}{'height'} $CONNECT_OPTS" . ( $MANUAL ? '' : " -u $USER -p -" ) . " $IP:$PORT";
			$connection_txt = "$METHOD -X $$CFG{'tmp'}{'xid'} -g $$CFG{'tmp'}{'width'}x$$CFG{'tmp'}{'height'} $CONNECT_OPTS" . ( $MANUAL ? '' : " -u $USER -p -" ) . " $IP:$PORT";
		} elsif ( defined $$CFG{'tmp'}{'xid'} ) {
			$connection_cmd = "$METHOD -g $$CFG{'tmp'}{'width'}x$$CFG{'tmp'}{'height'} $CONNECT_OPTS" . ( $MANUAL ? '' : " -u $USER" ) . " $IP:$PORT";
			$connection_txt = "$METHOD -g $$CFG{'tmp'}{'width'}x$$CFG{'tmp'}{'height'} $CONNECT_OPTS" . ( $MANUAL ? '' : " -u $USER" ) . " $IP:$PORT";
		} elsif ( $METHOD eq 'rdesktop' ) {
			$connection_cmd = "$METHOD $CONNECT_OPTS" . ( $MANUAL ? '' : " -u $USER -p -" ) . " -T \"$TITLE\" $IP:$PORT";
			$connection_txt = "$METHOD $CONNECT_OPTS" . ( $MANUAL ? '' : " -u $USER -p -" ) . " -T \"$TITLE\" $IP:$PORT";
		} else {
			$connection_cmd = "$METHOD $CONNECT_OPTS" . ( $MANUAL ? '' : " -u $USER" ) . " $IP:$PORT -T \"$TITLE\"";
			$connection_txt = "$METHOD $CONNECT_OPTS" . ( $MANUAL ? '' : " -u $USER" ) . " $IP:$PORT -T \"$TITLE\"";
		}
	}
	elsif ( ( $METHOD =~ /^.*vncviewer$/ ) || ( $METHOD eq 'VNC' ) ) {
		$METHOD = 'vncviewer';
		if ( ( $PASS eq '<<ASK_PASS>>' ) || ( $MANUAL ) ) {
			ctrl( "PASSWORD:Asking user for password..." );
			$PASS = wEnterValue( undef, '<b>Manual Password requested</b>', "Enter Password for '$NAME'", '', 0 ) // '';
		}
		
		$CONNECT_OPTS =~ s/\s*-embed//go;
		if ( `vncviewer --help 2>&1 | /bin/grep TigerVNC` ) {
			my $pfile = $CFG_DIR . '/tmp/pac_conn_' . $$ . '_' . $UUID;
			system( "echo \"$PASS\" | vncpasswd -f > $pfile" );
			if ( defined $$CFG{'tmp'}{'xid'} ) {
				$connection_cmd = "$METHOD $CONNECT_OPTS -PasswordFile=$pfile -Parent=$$CFG{'tmp'}{'xid'} $IP:$PORT";
				$connection_txt = "$METHOD $CONNECT_OPTS -PasswordFile=$pfile -Parent=$$CFG{'tmp'}{'xid'} $IP:$PORT";
			} else {
				$connection_cmd = "$METHOD $CONNECT_OPTS -PasswordFile=$pfile $IP:$PORT";
				$connection_txt = "$METHOD $CONNECT_OPTS -PasswordFile=$pfile $IP:$PORT";
			}
		} else {
			my $user = '';
			
			$connection_cmd = "echo \"$PASS\" | $METHOD $CONNECT_OPTS $user $IP:$PORT";
			$connection_txt = "echo \"<<hidden_password>>\" | $METHOD $CONNECT_OPTS $user $IP:$PORT";
		}
	}
	##############################################
	# GENERIC METHOD (simple command line)
	##############################################
	elsif	( ( $METHOD =~ /^.*generic$/ ) || ( $METHOD eq 'Generic Command' ) ) {
		$connection_cmd = "$IP";
		$connection_txt = "$IP";
	}
	elsif ( $METHOD eq 'PACShell' ) {
		$connection_cmd = "$$CFG{'defaults'}{'shell binary'} $$CFG{'defaults'}{'shell options'}";
		$connection_txt = "$$CFG{'defaults'}{'shell binary'} $$CFG{'defaults'}{'shell options'}";
	}
	##############################################
	# UNKNOWN METHOD (error!)
	##############################################
	else {
		my $string = "Unsupported connection method '$METHOD'.";
		$string .= 'Report this problem to perseo22@users.sourceforge.net';
		$string .= 'Sorry for the inconvenience!! :(';
		msg( $string );
		ctrl( "ERROR:$string" );
		ctrl( "DISCONNECTED" );
		exit 1;
	}
}

# Check for prepend commands
$QUOTE_COMMAND and $connection_cmd = '"' . $connection_cmd . '"';
$QUOTE_COMMAND and $connection_txt = '"' . $connection_cmd . '"';
$USE_PREPEND_COMMAND and $connection_cmd = $PREPEND_COMMAND . ' ' . $connection_cmd;
$USE_PREPEND_COMMAND and $connection_txt = $PREPEND_COMMAND . ' ' . $connection_txt;

# Check for 'sudo' use
if ( $SUDO ) {
	$connection_cmd = "sudo -p '$SUDO_PROMPT' " . $connection_cmd;
	$connection_txt = "sudo -p '$SUDO_PROMPT' " . $connection_txt;
}

# Check if there are non-generic terminal settings
if ( $$CFG{'environments'}{$UUID}{'terminal options'}{'use personal settings'} ) {
	$TIMEOUT_CONNECT	= $$CFG{'environments'}{$UUID}{'terminal options'}{'timeout connect'} || undef;
	$TIMEOUT_CMD		= $$CFG{'environments'}{$UUID}{'terminal options'}{'timeout command'} || undef;
	$COMMAND_PROMPT		= $$CFG{'environments'}{$UUID}{'terminal options'}{'command prompt'};
	$USERNAME_PROMPT	= $$CFG{'environments'}{$UUID}{'terminal options'}{'username prompt'};
	$PASSWORD_PROMPT	= $$CFG{'environments'}{$UUID}{'terminal options'}{'password prompt'};
}

$connection_cmd = subst( $connection_cmd );
if ( $GETCMD ) {
	print $connection_cmd;
	$PROXYPID and kill( 9, $PROXYPID );
	exit 0;
}

# Set log file
$EXP -> log_file( $LOG_FILE );

# Spawn the session
ctrl( "SPAWNING:$connection_txt" );
$EXP -> spawn( $connection_cmd . ' 2>&1' ) or die "Cannot spawn '$connection_cmd: $!\n";
ctrl( "SPAWNED:'$connection_txt' (PID:$$)" );

$EXP -> exp_internal( $DEBUG ); # EXPECT DEBUG!!

if ( ( $METHOD =~ /^.*rdesktop$/ ) || ( $METHOD =~ /^.*freerdp.*/ ) || ( $METHOD eq 'RDP (Windows)' ) ) {
	$CONNECTED = 1;
	ctrl( "CONNECTED" );
	
	# No way to expect anything from 'rdesktop' command line...
	$EXP -> expect( $TIMEOUT_CONNECT,
		
		[ eof => sub {
			
			$CONNECTED = 0;
			ctrl( "CLOSE:Connection ended by remote peer!! " . $EXP -> set_accum() );
			$EXP -> hard_close;
		}],
		
		# Found certificate verification string
		[ '^.+WARNING: CERTIFICATE NAME MISMATCH!.+$', sub {
			if ( $ACCEPT_KEY ) {
				send_slow( $EXP, "Y\n" );
				ctrl( "HOSTKEY:accepted by configuration (sent 'Y')" );
				exp_continue;
			} else {
				$CONNECTED = 0;
				send_slow( $EXP, "N\n" );
				ctrl( "CLOSE:HOSTKEY:rejected by configuration (sent 'N')" );
			}
		}],
		
		# Found login string
		[ $USERNAME_PROMPT, sub {
			
			if ( ( $USER eq '' ) && ( $USER_COUNT ) ) {
				ctrl( "CLOSE:LOGIN:Empty username is not valid to login here" );
				$EXP -> hard_close();
			}
			$USER_COUNT++;
			$USER = subst( $USER );
			ctrl( "LOGIN:$USER" );
			send_slow( $EXP, "$USER\n" );
			exp_continue;
		}],
		# Found password/passphrase string
		[ $PASSWORD_PROMPT, sub {
			
			# First password attempt...
			if ( ! $PASSWORD_COUNT ) {
				$PASSWORD_COUNT++;
				if ( ( $PASS eq '<<ASK_PASS>>' ) || ( $MANUAL ) ) {
					ctrl( "PASSWORD:Asking user for password..." );
					$PASS = wEnterValue( undef, '<b>Manual Password requested</b>', "Please, enter Password", '', 0 );
				}
				if ( defined $PASS ) {
					$EXP -> log_stdout( 0 );
					$PASS = subst( $PASS );
					send_slow( $EXP, "$PASS\n", 'hide' );
					$EXP -> log_stdout( 1 );
					ctrl( "PASSWORD:Sent (not shown)" );
					exp_continue;
				} else {
					ctrl( "CLOSE:PASSWORD:Password input cancelled by user" );
					$CONNECTED = 0;
					$EXP -> hard_close;
				}
			}
			# ... second password attempt: provided password is no longer valid!!
			else {
				ctrl( "CLOSE:PASSWORD:Provided username/password '$USER/<<hidden_password>>' was rejected" );
				$EXP -> hard_close;
			}
		}],
	)
}
elsif ( ( $METHOD =~ /^.*vncviewer$/ ) || ( $METHOD eq 'VNC' ) ) {
	# Expect authentication confirmation...
	my ( $matched_pattern_position, $error, $successfully_matching_string, $before_match, $after_match ) = $EXP -> expect( $TIMEOUT_CONNECT,
		
		[ timeout => sub {
			$CONNECTED = 0;
			ctrl( "CLOSE:TIMEOUT:$TIMEOUT_CONNECT seconds trying to connect or get prompt!!" );
			$EXP -> hard_close;
		}],
		
		[ eof => sub {
			
			$CONNECTED = 0;
			ctrl( "CLOSE:Connection ended by remote peer!! " . $EXP -> set_accum() );
			$EXP -> hard_close;
		}],
		
		# Found login string
		[ '^.*Authentication successful.*$', sub {
			$CONNECTED = 1;
			ctrl( "CONNECTED" );
		}],
		
		# Found login string
		[ '^.*Conn:\s+connected to host\s+.+\s+port\s+\d+.*$', sub {
			$CONNECTED = 1;
			ctrl( "CONNECTED" );
		}],
		
		# Found login string
		[ $USERNAME_PROMPT, sub {
			
			if ( ( $USER eq '' ) && ( $USER_COUNT ) ) {
				ctrl( "CLOSE:LOGIN:Empty username is not valid to login here" );
				$EXP -> hard_close();
			}
			$USER_COUNT++;
			$USER = subst( $USER );
			ctrl( "LOGIN:$USER" );
			send_slow( $EXP, "$USER\n" );
			exp_continue;
		}],
		
		# Found password/passphrase string
		[ $PASSWORD_PROMPT, sub {
			
			my $user = $EXP -> before // '';
			$user =~ s/^(.+?)@.+/$1/go;
			
			ctrl( "PASSWORD:Asking user for gateway's password..." );
			$PASS = wEnterValue( undef, "<b>Gateway Password requested</b>", "Enter Password for gateway's user '$user'", '', 0 );
			
			if ( defined $PASS ) {
				$EXP -> log_stdout( 0 );
				$PASS = subst( $PASS );
				send_slow( $EXP, "$PASS\n", 'hide' );
				$EXP -> log_stdout( 1 );
				ctrl( "PASSWORD:Sent (not shown)" );
				exp_continue;
			} else {
				ctrl( "CLOSE:PASSWORD:Password input cancelled by user" );
				$EXP -> hard_close;
			}
		}],
		
		# Found any other string (special case for the '-via' option)
		[ '.*((open|connect) failed)|refused|(server closed).*', sub {
			ctrl( "DISCONNECTED" );
			$CONNECTED = 0;
		}],
		
	)
}
elsif ( ( $METHOD =~ /^.*generic$/ ) || ( $METHOD eq 'Generic Command' ) || ( $METHOD =~ /^.*3270.*$/ ) || ( $METHOD eq 'IBM 3270/5250' ) ) {
	$CONNECTED = 1;
	
	# Do we have to do complex expects?
	return 1 unless ( $EXPECT && $CONNECTED );
	
	my $end = 0;
	my $avoid_first_expectation = 0;
	$EXP -> restart_timeout_upon_receive( 1 );
	for( my $i = 0; $i < scalar( @{ $$CFG{'environments'}{$UUID}{'expect'} } ); $i++ ) {
		my $hash		= $$CFG{'environments'}{$UUID}{'expect'}[$i];
		my $pattern		= $$hash{'expect'}		// '';
		my $command		= $$hash{'send'}		// '';
		my $hide		= $$hash{'hidden'}		// 0;
		my $active		= $$hash{'active'}		// 0;
		my $return		= $$hash{'return'}		// 1;
		my $on_match	= $$hash{'on_match'}	// -1;
		my $on_fail		= $$hash{'on_fail'}		// -1;
		my $time_out	= $$hash{'time_out'}	// -1;
		
		next unless $active;
		my $jump = 0;
		
		$time_out >= 0 and $TIMEOUT_CMD = $time_out;
		
		$pattern = subst( $pattern );
		
		ctrl( "EXPECT:WAITING:$pattern" );
		
		# Wait for pattern prompt before continue...
		$EXP -> expect( $TIMEOUT_CMD,
			
			[ timeout => sub {
				if ( $on_fail == -1 ) {
					ctrl( "CLOSE:TIMEOUT:$TIMEOUT_CMD seconds expecting pattern '$pattern'!!" );
					$CONNECTED = 0;
					$EXP -> hard_close;
				} elsif ( $on_fail == -2 ) {
					ctrl( "EXPECT:ON_FAIL:timeout expecting '$pattern'. Finishing Expect" );
					$CONNECTED = 1;
					$end = 1;
				} else {
					ctrl( "EXPECT:ON_FAIL:timeout expecting '$pattern'. Jumping to '$on_fail'" );
					$i = --$on_fail;
					$jump = 1;
				}
			}],
			
			[ eof => sub {
				
				$CONNECTED = 0;
				ctrl( "CLOSE:Connection ended by remote peer!! " . $EXP -> set_accum() );
				$EXP -> hard_close();
			}],
			
			[ ( $avoid_first_expectation ) ? '' : $pattern, sub {
				if ( $on_match == -1 ) {
					$jump = 0;
				} elsif ( $on_match == -2 ) {
					$end = 1;
					$CONNECTED = 1;
					ctrl( "EXPECT:ON_MATCH:found pattern '$pattern'. Stopping by config" );
				} else {
					ctrl( "EXPECT:ON_MATCH:found pattern '$pattern'. Jumping to '$on_match'" );
					$avoid_first_expectation = 1;
					$i = --$on_match;
				}
			}]
		);
		last if $end;
		next if $jump;
		$avoid_first_expectation = 0;
		last unless $CONNECTED;
		
		$command = subst( $command );
		
		# ... and launch command
		if ( $hide ) {
			ctrl( "EXPECT:SENDING:<<HIDDEN STRING>>" );
			$EXP -> log_stdout( 0 );
		} else {
			my $cmd_str = $command;
			$cmd_str =~ s/\n$//go;
			ctrl( "EXPECT:SENDING:$cmd_str" . ( $return ? '\n' : '' ) );
		}
		
		send_slow( $EXP, $command . ( $return ? ( ( $METHOD =~ /^.*3270.*$/ ) || ( $METHOD eq 'IBM 3270/5250' ) ? "\r\f" : "\n" ) : '' ) );
		$hide and $EXP -> log_stdout( 1 );
	}
	
	$EXP -> restart_timeout_upon_receive( 0 );
	
	if ( $CONNECTED ) {
		ctrl( "TITLE:$TITLE" );
		ctrl( "CONNECTED" );
	} else {
		$CONNECTED = 0;
		ctrl( "DISCONNECTED:" . ( $EXP -> error ) );
	}

}
elsif ( $METHOD eq 'PACShell' ) {
	$CONNECTED = 1;
	ctrl( "CONNECTED" );
	send_slow( $EXP, "cd $$CFG{'defaults'}{'shell directory'}\n" );
}
elsif ( ! $MANUAL ) {
	my $end = 0;
	
	# Expect password/confirmation prompt...
	my ( $matched_pattern_position, $error, $successfully_matching_string, $before_match, $after_match ) = $EXP -> expect( $TIMEOUT_CONNECT,
		
		[ timeout => sub {
			$CONNECTED = 0;
			ctrl( "CLOSE:TIMEOUT:$TIMEOUT_CONNECT seconds trying to connect or get prompt!!" );
			$EXP -> hard_close();
		}],
		
		[ eof => sub {
			
			$CONNECTED = 0;
			ctrl( "CLOSE:Connection ended by remote peer!! " . $EXP -> set_accum() );
			$EXP -> hard_close();
		}],
		
		# Found sudo prompt
		[ "\Q$SUDO_PROMPT\E", sub {
			
			$CONNECTED and $EXP -> exp_continue;
			
			# First 'sudo' password attempt...
			if ( ! $SUDO_PASSWORD_COUNT ) {
				$SUDO_PASSWORD_COUNT++;
				if ( ( defined $SUDO_PASSWORD ) && ( $SUDO_PASSWORD eq '<<ASK_PASS>>' ) ) {
					ctrl( "PASSWORD:Asking user '$ENV{'USER'}' for 'sudo' password..." );
					$SUDO_PASSWORD = wEnterValue( undef, '<b>Password requested</b>', "Enter 'sudo' password for '$ENV{'USER'}'", '', 0 );
				}
				if ( defined $SUDO_PASSWORD ) {
					$EXP -> log_stdout( 0 );
					$SUDO_PASSWORD = subst( $SUDO_PASSWORD );
					send_slow( $EXP, "$SUDO_PASSWORD\n", 'hide' );
					$EXP -> log_stdout( 1 );
					ctrl( "PASSWORD:'sudo' password sent (not shown)" );
					exp_continue;
				} else {
					ctrl( "CLOSE:PASSWORD:Password for 'sudo' input cancelled by user" );
					$EXP -> hard_close;
				}
			}
			# ... second 'sudo' password attempt: provided password is no longer valid!!
			else {
				ctrl( "CLOSE:PASSWORD:Provided 'sudo' password was rejected" );
				$EXP -> hard_close;
			}
		}],
		
		# Found Host-Key verification string
		[ $HOSTCHANGE_PROMPT, sub {
			my $match = $EXP -> match;
			$match =~ /$HOSTCHANGE_PROMPT/g;
			my ( $yes, $no ) = ( $1, $2 );
			if ( $ACCEPT_KEY ) {
				send_slow( $EXP, $yes . "\n" );
				ctrl( "HOSTKEY:accepted by configuration (sent '$yes')" );
				exp_continue;
			} else {
				$CONNECTED = 0;
				send_slow( $EXP, $no . "\n" );
				ctrl( "CLOSE:HOSTKEY:rejected by configuration (sent '$no')" );
			}
		}],
		
		# Found "Press any key to continue" string
		[ $ANYKEY_PROMPT, sub {
			send_slow( $EXP, "\n" );
			ctrl( "PRESSKEY:sending 'return' to continue connecting" );
			exp_continue;
		}],
		
		# Found WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED
		[ $REMOTEHOST_PROMPT, sub {
			my $match = $EXP -> match;
			chomp $match;
			$match =~ /$REMOTEHOST_PROMPT/go;
			my ( $known_hosts, $line ) = ( $1, $2 );
			$CONNECTED = 0;
			if ( $ACCEPT_KEY ) {
				ctrl( "HOSTKEY:deleting offending hostkey by configuration and Restarting connection" );
				open( F, `echo $known_hosts 2>&1` ) or die "ERROR: could not open for reading '$known_hosts' ($!)";
				my @data = <F>;
				close F;
				splice( @data, $line - 1, 1 );
				open( F, '>' . `echo $known_hosts 2>&1` ) or die "ERROR: could not open for writting '$known_hosts' ($!)";
				print F @data;
				close F;
				ctrl( 'RESTART' );
			} else {
				ctrl( "CLOSE:HOSTKEY:ignoring offending hostkey by configuration" );
			}
		}],
		
		# Found login string
		[ $USERNAME_PROMPT, sub {
			
			if ( ( $USER eq '' ) && ( $USER_COUNT ) ) {
				ctrl( "CLOSE:LOGIN:Empty username is not valid to login here" );
				$EXP -> hard_close();
			}
			$USER_COUNT++;
			$USER = subst( $USER );
			ctrl( "LOGIN:$USER" );
			send_slow( $EXP, "$USER\n" );
			exp_continue;
		}],
		
		# Found password/passphrase string
		[ $PASSWORD_PROMPT, sub {
			
			# First password attempt...
			if ( ! $PASSWORD_COUNT ) {
				$PASSWORD_COUNT++;
				if ( defined $PASS && $PASS eq '<<ASK_PASS>>' ) {
					ctrl( "PASSWORD:Asking user for password..." );
					$PASS = wEnterValue( undef, '<b>Manual Password requested</b>', "Enter Password for '$NAME'", '', 0 );
				}
				if ( defined $PASS ) {
					$EXP -> log_stdout( 0 );
					$PASS = subst( $PASS );
					send_slow( $EXP, "$PASS\n", 'hide' );
					$EXP -> log_stdout( 1 );
					ctrl( "PASSWORD:Sent (not shown)" );
					exp_continue;
				} else {
					ctrl( "CLOSE:PASSWORD:Password input cancelled by user" );
					$EXP -> hard_close;
				}
			}
			# ... second password attempt: provided password is no longer valid!!
			else {
				ctrl( "CLOSE:PASSWORD:Provided username/password '$USER/<<hidden_password>>' was rejected" );
				$EXP -> hard_close;
			}
		}],
		
		# Found user prompt
		[ $COMMAND_PROMPT, sub {
			
			$CONNECTED = 1;
			my $avoid_first_expectation = 1;
			
			# Do we have to do complex expects?
			return 1 unless ( $EXPECT && $CONNECTED );
			
			$EXP -> restart_timeout_upon_receive( 1 );
			for( my $i = 0; $i < scalar( @{ $$CFG{'environments'}{$UUID}{'expect'} } ); $i++ ) {
				my $hash		= $$CFG{'environments'}{$UUID}{'expect'}[$i];
				my $pattern		= $$hash{'expect'} // '';
				my $command		= $$hash{'send'} // '';
				my $hide		= $$hash{'hidden'} // 0;
				my $active		= $$hash{'active'} // 0;
				my $return		= $$hash{'return'} // 1;
				my $on_match	= $$hash{'on_match'} // -1;
				my $on_fail		= $$hash{'on_fail'} // -1;
				my $time_out	= $$hash{'time_out'} // -1;
				
				next unless $active;
				
				my $jump = 0;
				
				$time_out >= 0 and $TIMEOUT_CMD = $time_out;
				$pattern = subst( $pattern );
				
				ctrl( "EXPECT:WAITING:$pattern" );
				
				# Wait for pattern prompt before continue...
				$EXP -> expect( $TIMEOUT_CMD,
					
					[ timeout => sub {
						if ( $on_fail == -1 ) {
							ctrl( "CLOSE:TIMEOUT:$TIMEOUT_CMD seconds expecting pattern '$pattern'!!" );
							$CONNECTED = 0;
							$EXP -> hard_close;
						} elsif ( $on_fail == -2 ) {
							ctrl( "EXPECT:ON_FAIL:timeout expecting '$pattern'. Finishing Expect" );
							$CONNECTED = 1;
							$end = 1;
						} else {
							ctrl( "EXPECT:ON_FAIL:timeout expecting '$pattern'. Jumping to '$on_fail'" );
							$i = --$on_fail;
							$jump = 1;
						}
					}],
					
					[ eof => sub {
						
						$CONNECTED = 0;
						ctrl( "CLOSE:Connection ended by remote peer!! " . $EXP -> set_accum() );
						$EXP -> hard_close();
					}],
					
					# Found Host-Key verification string
					#[ '^.+continue connecting \((.+)\/(.+)\)\?\s*$', sub {
					[ $HOSTCHANGE_PROMPT, sub {
						
						my $match = $EXP -> match;
						#$match =~ /^.+continue connecting \((.+)\/(.+)\)\?\s*$/go;
						$match =~ /$HOSTCHANGE_PROMPT/g;
						my ( $yes, $no ) = ( $1, $2 );
						if ( $ACCEPT_KEY ) {
							send_slow( $EXP, $yes . "\n" );
							ctrl( "EXPECT:HOSTKEY:accepted by configuration (sent '$yes')" );
							exp_continue;
						} else {
							send_slow( $EXP, $no . "\n" );
							$CONNECTED = 0;
							$EXP -> hard_close();
							ctrl( "CLOSE:EXPECT:HOSTKEY:rejected by configuration (sent '$no')" );
						}
					}],
					
					[ ( $avoid_first_expectation ) ? '' : $pattern, sub {
						if ( $on_match == -1 ) {
							$jump = 0;
						} elsif ( $on_match == -2 ) {
							$end = 1;
							$CONNECTED = 1;
							ctrl( "EXPECT:ON_MATCH:found pattern '$pattern'. Stopping by config" );
						} else {
							ctrl( "EXPECT:ON_MATCH:found pattern '$pattern'. Jumping to '$on_match'" );
							$avoid_first_expectation = 1;
							$i = --$on_match;
						}
					}]
				);
				last if $end;
				next if $jump;
				$avoid_first_expectation = 0;
				last unless $CONNECTED;
				
				$command = subst( $command );
				
				# ... and launch command
				if ( $hide ) {
					ctrl( "EXPECT:SENDING:<<HIDDEN STRING>>" . ( $return ? '\n' : '' ) );
					$EXP -> log_stdout( 0 );
				} else {
					my $cmd_str = $command;
					$cmd_str =~ s/\n$//go;
					ctrl( "EXPECT:SENDING:$cmd_str" . ( $return ? '\n' : '' ) );
				}
				
				send_slow( $EXP, $command );
				$return and send_slow( $EXP, "\n" );
				
				$hide and $EXP -> log_stdout( 1 );
			}
			
			$EXP -> restart_timeout_upon_receive( 0 );
			
		}],

	);
	
	if ( $end || ! $error ) {
		if ( $CONNECTED ) {
			ctrl( "TITLE:$TITLE" );
			ctrl( "CONNECTED" );
		}
	} else {
		$CONNECTED = 0;
		ctrl( "DISCONNECTED:$error" );
	}
}
else {
	if ( ! defined $EXP -> error ) {
		$CONNECTED = 0;
		ctrl( "DISCONNECTING:" . ( $EXP -> error ) );
	} else {
		$CONNECTED = 1;
		ctrl( "CONNECTING:Manual authentication requested" );
		ctrl( "TITLE:$TITLE" );
		ctrl( "CONNECTED" );
	}
}

$SIG{'WINCH'} = sub {
	return 1 unless $CONNECTED;
	while ( ! $EXP -> slave ) { select( undef, undef, undef, 0.25 ); };
	$EXP -> slave -> clone_winsize_from( \*STDIN );
	kill WINCH => $EXP -> pid if $EXP -> pid;
};

$SIG{'INT'} = undef;
$SIG{'HUP'} = sub {
	# Avoid more interruptions
	local $SIG{'WINCH'} = undef;
	
	my $chain_uuid = '';
	my $CHAIN_CFG;
	
	return 1 if $INT;
	$INT = 1;
	
	# First, read the file with the configuration to use
	my $rin = '';
	vec( $rin, fileno( $SOCKET ), 1 ) = 1;
	select( $rin, undef, undef, 2 ) or return 1;
	sysread( $SOCKET, $chain_uuid, 1024 );
	$chain_uuid =~ s/^!!_PAC_CHAIN_\[(.+)\]!!$/$1/g;
	if ( ! $chain_uuid ) {
		$INT = 0;
		return 1;
	}
	
	# Second, retrieve the 'serialized' configuration to be used
	$rin = '';
	vec( $rin, fileno( $SOCKET ), 1 ) = 1;
	select( $rin, undef, undef, 2 ) or return 1;
	eval { $CHAIN_CFG = fd_retrieve( $SOCKET ); };
	if ( $@ ) {
		$INT = 0;
		return 1;
	}
	
	# Prepare some progressbar data
	my $chain_name	= $$CHAIN_CFG{'environments'}{$chain_uuid}{'name'};
	my $exp_partial	= 0;
	my $exp_total	= 0;
	foreach my $exp ( @{ $$CHAIN_CFG{'environments'}{ $chain_uuid }{'expect'} } ) { next unless ( $$exp{'active'} // 0 ); ++$exp_total; }
	if ( ! $exp_total ) {
		$INT = 0;
		return 1;
	}
	ctrl( "CHAIN:$chain_name:$chain_uuid:$exp_partial:$exp_total" );
	
	my $TIMEOUT_CMD = $$CHAIN_CFG{'defaults'}{'timeout command'} || undef;
	if ( $$CHAIN_CFG{'environments'}{$chain_uuid}{'terminal options'}{'use personal settings'} ) {
		$TIMEOUT_CMD = $$CHAIN_CFG{'environments'}{$chain_uuid}{'terminal options'}{'timeout command'} || undef;
	}
	
	my $end = 0;
	my $avoid_first_expectation = 1;
	$EXP -> restart_timeout_upon_receive( 1 );
	for( my $i = 0; $i < scalar( @{ $$CHAIN_CFG{'environments'}{$chain_uuid}{'expect'} } ); $i++ ) {
		my $hash		= $$CHAIN_CFG{'environments'}{$chain_uuid}{'expect'}[$i];
		my $pattern		= $$hash{'expect'}		// '';
		my $command		= $$hash{'send'}		// '';
		my $hide		= $$hash{'hidden'}		// 0;
		my $active		= $$hash{'active'}		// 0;
		my $return		= $$hash{'return'}		// 1;
		my $on_match	= $$hash{'on_match'}	// -1;
		my $on_fail		= $$hash{'on_fail'}		// -1;
		my $time_out	= $$hash{'time_out'}	// -1;
		
		next unless $active;
		my $jump = 0;
		
		$time_out >= 0 and $TIMEOUT_CMD = $time_out;
		
		$pattern = subst( $pattern );
		
		ctrl( "CHAIN:$chain_name:WAITING($pattern):" . ( $exp_partial++ ) . ":$exp_total" );
		
		# Wait for pattern prompt before continue...
		$EXP -> expect( $TIMEOUT_CMD,
			
			[ timeout => sub {
				if ( $on_fail == -1 ) {
					ctrl( "CLOSE:TIMEOUT:$TIMEOUT_CMD seconds expecting pattern '$pattern'!!" );
					$CONNECTED = 0;
					$EXP -> hard_close;
				} elsif ( $on_fail == -2 ) {
					ctrl( "CHAIN:EXPECT:ON_FAIL:timeout expecting '$pattern'. Finishing Expect" );
					$CONNECTED = 1;
					$end = 1;
				} else {
					ctrl( "CHAIN:EXPECT:ON_FAIL:timeout expecting '$pattern'. Jumping to '$on_fail'" );
					$i = --$on_fail;
					$jump = 1;
				}
			}],
			
			[ eof => sub {
				
				$CONNECTED = 0;
				ctrl( "CLOSE:Connection ended by remote peer!! " . $EXP -> set_accum() );
				$EXP -> hard_close();
			}],
			
			# Found Host-Key verification string
			[ '^.+continue connecting \((.+)\/(.+)\)\?\s*$', sub {
				
				my $match = $EXP -> match;
				$match =~ /^.+continue connecting \((.+)\/(.+)\)\?\s*$/go;
				my ( $yes, $no ) = ( $1, $2 );
				if ( $ACCEPT_KEY ) {
					send_slow( $EXP, $yes . ( ( $METHOD =~ /^.*3270.*$/ ) || ( $METHOD eq 'IBM 3270/5250' ) ) ? "\r\f" : "\n" );
					ctrl( "EXPECT:HOSTKEY:accepted by configuration (sent '$yes')" );
					exp_continue;
				} else {
					send_slow( $EXP, $no . ( ( $METHOD =~ /^.*3270.*$/ ) || ( $METHOD eq 'IBM 3270/5250' ) ) ? "\r\f" : "\n" );
					$CONNECTED = 0;
					$EXP -> hard_close();
					ctrl( "CLOSE:EXPECT:HOSTKEY:rejected by configuration (sent '$no')" );
				}
			}],
			
			[ ( $avoid_first_expectation ) ? '' : $pattern, sub {
				if ( $on_match == -1 ) {
					$jump = 0;
				} elsif ( $on_match == -2 ) {
					$end = 1;
					$CONNECTED = 1;
					ctrl( "CHAIN:EXPECT:ON_MATCH:found pattern '$pattern'. Stopping by config" );
				} else {
					ctrl( "CHAIN:EXPECT:ON_MATCH:found pattern '$pattern'. Jumping to '$on_match'" );
					$avoid_first_expectation = 1;
					$i = --$on_match;
				}
			}]
		);
		last if $end;
		next if $jump;
		$avoid_first_expectation = 0;
		last unless $CONNECTED;
		
		$command = subst( $command );
		
		# ... and launch command
		if ( $hide ) {
			ctrl( "CHAIN:$chain_name:SENDING(<<HIDDEN STRING>>):$exp_partial:$exp_total" );
			$EXP -> log_stdout( 0 );
		} else {
			my $cmd_str = $command;
			$cmd_str =~ s/\n$//go;
			ctrl( "CHAIN:$chain_name:SENDING:$cmd_str" . ( $return ? '\n' : '' ) . ":$exp_partial:$exp_total" );
		}
		
		send_slow( $EXP, $command . ( $return ? ( ( $METHOD =~ /^.*3270.*$/ ) || ( $METHOD eq 'IBM 3270/5250' ) ? "\r\f" : "\n" ) : '' ) );
	}

	$EXP -> log_stdout( 0 );
	$EXP -> restart_timeout_upon_receive( 0 );
	
	$$CHAIN_CFG{'tmp'}{'set title'} and ctrl( "TITLE:$$CHAIN_CFG{'tmp'}{'title'}" );
	ctrl( "CONNECTED" );
	$INT = 0;
	return 1;
};

$SIG{'USR1'} = sub {
	# Avoid more interruptions
	local $SIG{'WINCH'} = undef;
	
	return 1 if $INT;
	$INT = 1;
	
	# Now, read the command to execute
	my $rin = '';
	vec( $rin, fileno( $SOCKET ), 1 ) = 1;
	select( $rin, undef, undef, 2 ) or return 1;
	my $tmp;
	eval { $tmp = fd_retrieve( $SOCKET ); }; $@ and return 1;
	if ( ! defined $$tmp{cmd} ) {
		$INT = 0;
		return 1;
	}
	
	_execAndCapture( $tmp );
	
	ctrl( $CONNECTED ? 'CONNECTED' : 'DISCONNECTED' );
	$INT = 0;
	return 1;
};

$SIG{'USR2'} = sub {
	# Avoid more interruptions
	local $SIG{'WINCH'} = undef;
	
	return 1 if $INT;
	$INT = 1;
	
	my $rin = '';
	vec( $rin, fileno( $SOCKET ), 1 ) = 1;
	select( $rin, undef, undef, 2 ) or return 1;
	my $tmp;
	$tmp = fd_retrieve( $SOCKET ) or return 0;
	if ( ! defined $tmp ) {
		$INT = 0;
		return 0;
	}
	
	_execScript( $tmp );
	ctrl( $CONNECTED ? 'CONNECTED' : 'DISCONNECTED' );
	
	$INT = 0;
	return 1;
};

$CONNECTED and $EXP -> interact( \*STDIN, "__PAC__STOP__${UUID}__${$}__" );
my $why = $?;
ctrl( 'RESTART' ) if ( ( $why ne 0 ) && $RESTART );

# Finish this expect session
kill( 15, $EXP -> pid ) if defined $EXP -> pid;
$EXP -> hard_close;

defined $PROXYPID && $PROXYPID and kill( 15, $PROXYPID );

ctrl( "DISCONNECTED" );

exit 0;

END {
	return 1 unless $LOG_FILE && $REMOVE_CTRL_CHARS;
	
	ctrl( "LOGFILE:Removing CONTROL characters" );
	
	if ( ! open( F, "$LOG_FILE" ) ) {
		ctrl( "ERROR: Could not open file '$LOG_FILE' for reading!! ($!)" );
		return 1;
	}
	my @lines = <F>;
	close F;
	
	if ( ! open( F, ">$LOG_FILE.$$" ) ) {
		ctrl( "ERROR: Could not open file '$LOG_FILE.$$' for writting!! ($!)" );
		return 1;
	}
	print F _removeEscapeSeqs( join( '', @lines ) );
	close F;
	
	rename( "$LOG_FILE.$$", $LOG_FILE ) or ctrl( "ERROR: $!" );
}

# END : Main program
########################################################
