#!/usr/bin/perl # awit-ssh - SSH initiator which searches LDAP for host details # Copyright (c) 2016, AllWorldIT # # 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/>. use strict; use warnings; use Term::ANSIColor; use Getopt::Long; use Net::DBus qw(:typing); # Check Config::IniFiles if (!eval {require Config::IniFiles; 1;}) { print STDERR "You're missing Config::IniFiles, try 'apt-get install libconfig-inifiles-perl'\n"; exit 1; } # Check IO::Socket::INET6 if (!eval {require IO::Socket::INET6; 1;}) { print STDERR "You're missing IO::Socket::INET6, try 'apt-get install libio-socket-inet6-perl'\n"; exit 1; } # Check Net::LDAP if (!eval {require Net::LDAP; 1;}) { print STDERR "You're missing Net::LDAP, try 'apt-get install libnet-ldap-perl'\n"; exit 1; } # Check IO::Prompt if (!eval {require IO::Prompt; 1;}) { print STDERR "You're missing IO::Prompt, try 'apt-get install libio-prompt-perl'\n"; exit 1; } ## no critic (BuiltinFunctions::ProhibitStringyEval) eval qq( use IO::Prompt qw(prompt); ); ## use critic use Digest::SHA qw( sha1_hex ); use User::pwent; my $NAME = "AWIT-SSH-Client"; our $VERSION = "0.8.2"; print(STDERR "$NAME v$VERSION - Copyright (c) 2016, AllWorldIT\n\n"); # Grab options my %optctl = (); GetOptions(\%optctl, "help|?", "version", # TODO: debug is not implemented, make sure displayHelp is updated "debug", # TODO: Improve globbing before adding it to displayHelp "globbing", "knock=s", "rsync", "libvirt-vnc=s", ) or exit 1; # Check for help if (defined($optctl{'help'})) { displayHelp(); exit 0; } # Check for version if (defined($optctl{'version'})) { displayVersion(); exit 0; } # Check if we using rsync instead of SSH my $useRsync; my @rsyncParams; if (defined(my $rsyncHost = $optctl{'rsync'})) { $useRsync = $rsyncHost; } # Check if we using libvirt vnc instead of SSH my $libvirtVNC; if (defined(my $vmName = $optctl{'libvirt-vnc'})) { if (! -x '/usr/bin/ssvncviewer') { logger('ERROR',color('magenta')."To use --libvirt-vnc you need to install ssvncviewer. Hint: apt-get install ssvnc". color('reset')); exit 1; } $libvirtVNC = $vmName; } # Check if we should be doing port knocking my ($knockHost,$knockPort); if (defined(my $knock = $optctl{'knock'})) { # If so, split off the host and the port ($knockHost,$knockPort) = split(':',$knock); if (!defined($knockPort)) { logger('ERROR',color('magenta')."Port knock specifications should be in the format of HOST:PORT".color('reset')); exit 1; } } # Check for option combinations if (defined($useRsync) && defined($libvirtVNC)) { logger('ERROR',color('magenta')."Options --rsync and --libvirt-monitor cannot be used together".color('reset')); exit 1; } # Variables we may set below my $loginUsername; # Pull in hostname my $hostSpec; if (defined($useRsync)) { foreach my $param (@ARGV) { # Look for the remote:// param if ($param =~ /remote:\/\//) { # Remove it and set the hostSpec my $removedTag = substr($param,9); # Assing hostSpec to the first part of the tag ($hostSpec) = split(/[\/:]/,$removedTag); # Change first / to a :/ $removedTag =~ s,/,:/,; push(@rsyncParams,$removedTag); # Else just add it } else { push(@rsyncParams,$param); } } # Make sure we got a hostSpec if (!defined($hostSpec)) { logger('ERROR',color('magenta')."awit-ssh --rsync needs a remote://SERVER.... to be specified on the command line". color('reset')); exit 1; } } else { $hostSpec = shift(@ARGV) // ""; } my ($loginHost,$loginPort) = split(':',$hostSpec); if (defined($loginHost)) { # Suck in username if specified my ($userBit,$hostBit) = split('@',$loginHost); if (defined($hostBit)) { $loginUsername = $userBit; $loginHost = $hostBit; } } else { logger('ERROR',color('magenta')."No hostname provided".color('reset')); exit 1; } # Make sure we save the hostname my $realLoginHost = $loginHost; # Port forwarding/bouncing my ($forwardHost,$forwardPort,$forwardUsername,@forwardPortExtra); my %iniSetup; # Check for config and read my $configFile = $ENV{"HOME"}.'/.awit-ssh.conf'; if (! -f $configFile) { print STDERR "No configuration file found. Please answer the questions below to generate it.\n\n"; tie %iniSetup, 'Config::IniFiles'; $iniSetup{server} = {}; $iniSetup{server}{uri} = prompt("Your LDAP URI : "); $iniSetup{server}{uri} =~ s/^uri=//; $iniSetup{server}{base} = prompt("Your LDAP Base : "); $iniSetup{server}{base} =~ s/^base=//; tied(%iniSetup)->WriteConfig($configFile) or die "Could not write settings to new configuration file."; untie %iniSetup; } else { # Flag, so we don't echo the same output lower down immediately after a setup. $iniSetup{server} = 'installed'; } my $config = Config::IniFiles->new(-file => $configFile); my $ldapURI = $config->val("server","uri"); if (!defined($ldapURI) || $ldapURI eq "") { logger('ERROR',color('magenta')."Server URI not defined in config file".color('reset')); exit 1; } my $ldapBase = $config->val("server","base"); if (!defined($ldapBase) || $ldapBase eq "") { logger('ERROR',color('magenta')."Server base DN not defined in config file".color('reset')); exit 1; } my $pkcsProvider = $config->val("pkcs11","provider"); if (defined($pkcsProvider) && $pkcsProvider ne "") { if (! -f $pkcsProvider) { logger('ERROR',color('magenta')."PKCS11 provider '$pkcsProvider' does not exist".color('reset')); exit 1; } } if (%iniSetup) { print STDERR "LDAP server URI : $ldapURI\n"; print STDERR "LDAP server base : $ldapBase\n"; } # Try get name automatically my $pwent = getpwnam($ENV{'USER'}); (my $username) = split(/,/,$pwent->gecos); if (!defined($username) || $username eq "") { print STDERR "WARNING: Cannot determine your name, set your gecos field.\n\n"; $username = prompt("Your LDAP CN : "); } else { print STDERR "Your LDAP CN : $username (passwd->gecos)\n"; } # Sort out LDAP password my $password; # Lets try kwallet my ($kwalletObject,$kwalletHandle); # IF removed, lets rather run this in its own scope... { my $dbus = Net::DBus->find(); # Grab the kwallet service off DBus my $kwalletService = $dbus->get_service('org.kde.kwalletd'); if (!defined($kwalletService)) { logger('WARNING',color('magenta')."Kwallet not found on DBus".color('reset')); goto KWALLET_END; } $kwalletObject = $kwalletService->get_object('/modules/kwalletd','org.kde.KWallet'); # Grab a handle to the network wallet my $networkWalletName = $kwalletObject->networkWallet(); $kwalletHandle = $kwalletObject->open($networkWalletName,0,$NAME); $password = $kwalletObject->readPassword($kwalletHandle,"ldap","password",$NAME); KWALLET_END: } # If kwallet returned nothing, try read from terminal if (!defined($password) || $password eq "") { $password = prompt("Your LDAP Password: ", '-echo' => "*"); } print STDERR "\n"; my $ldap = Net::LDAP->new($ldapURI, # 'debug' => 15 ); if (!defined($ldap)) { logger('ERROR',color('magenta')."Failed to setup LDAP object '%s'".color('reset'),$@); exit 2; } # Bind my $mesg = $ldap->bind("cn=$username,ou=Users,$ldapBase",password => $password); # Search $mesg = $ldap->search( base => "ou=Servers,$ldapBase", filter => "(|(cn=$loginHost)(awitLoginHostAlias=$loginHost))", ); # Check for error if (my $mesgCode = $mesg->code()) { if ($mesgCode eq "No such object") { logger('ERROR',color('magenta')."LDAP returned '%s', this more than likely means a Username/Password error or your BaseDN is wrong.".color('reset'),$mesgCode); } else { logger('ERROR',color('magenta')."LDAP returned '%s'".color('reset'),$mesgCode); } exit 2; } # Some flags we may need my $needDSS; # If no matches my @ldapResults = $mesg->entries(); my $ldapNumResults = @ldapResults; my $ldapEntry; if ($ldapNumResults < 1) { logger('NOTICE',color('bold red')."No LDAP results, using the hostname provided on the commandline".color('reset')); } elsif ($ldapNumResults == 1) { $ldapEntry = $ldapResults[0]; } elsif ($ldapNumResults > 1) { my $counter = 1; logger('WARNING',color('red')."Found multiple entries!".color('reset')); print STDERR "\n"; foreach my $key (sort(keys %{$mesg->as_struct()})) { logger('MENU '.$counter," ".color('red')."%s".color('reset'),$key); $counter++; } my $menuSelection = prompt("Your selection [1-$ldapNumResults,q]: ", '-onechar', '-require' => { "Invalid Value - Your selection [1-$ldapNumResults,q]: " => sub { my $val = $_; return ( # Check if is numeric and its within range $val =~ /^\d$/ && $val > 0 && $val <= $ldapNumResults ) || ( # Else our only other option we accept is 'q' $val eq "q" ); } } ); if ($menuSelection eq "q") { print STDERR "\nExiting...\n"; exit 3; } print STDERR "\n"; $menuSelection = $menuSelection->{'value'} - 1; $ldapEntry = $ldapResults[$menuSelection]; } print STDERR "\n"; # Check if we got a result and modify our connection details if ($ldapEntry) { my $ldapEntryName = $ldapEntry->get_value('cn'); logger('INFO',"Found server entry '".color('green')."$ldapEntryName".color('reset')."'"); # TODO: Ability to select between mulitple awitLoginHost's separated by ,'s # Check if we need to set the port knocking host & port if (my $ldapLoginKnockHost = $ldapEntry->get_value('awitLoginKnockHost')) { logger('INFO'," - Knock host ".color('green')."%s".color('reset')." (awitLoginKnockHost)",$ldapLoginKnockHost); $knockHost //= $ldapLoginKnockHost; } if (my $ldapLoginKnockPort = $ldapEntry->get_value('awitLoginKnockPort')) { logger('INFO'," - Knock port ".color('green')."%s".color('reset')." (awitLoginKnockPort)",$ldapLoginKnockPort); $knockPort //= $ldapLoginKnockPort; } # Check if we need to set the port forwarding info if (my $ldapLoginForwardHost = $ldapEntry->get_value('awitLoginForwardHost')) { logger('INFO'," - Forward host ".color('green')."%s".color('reset')." (awitLoginForwardHost)",$ldapLoginForwardHost); $forwardHost //= $ldapLoginForwardHost; $forwardPort = 22; } if (my $ldapLoginForwardPort = $ldapEntry->get_value('awitLoginForwardPort')) { logger('INFO'," - Forward port ".color('green')."%s".color('reset')." (awitLoginForwardPort)",$ldapLoginForwardPort); $forwardPort = $ldapLoginForwardPort; } if (my $ldapLoginForwardUsername = $ldapEntry->get_value('awitLoginForwardUsername')) { logger('INFO'," - Forward user ".color('green')."%s".color('reset')." (awitLoginForwardUsername)",$ldapLoginForwardUsername); $forwardUsername //= $ldapLoginForwardUsername; } if (my $ldapLoginForwardPortExtra = $ldapEntry->get_value('awitLoginForwardPortExtra')) { my @tmpList = (); # Check if this is an array ref or not if (ref($ldapLoginForwardPortExtra) eq "ARRAY") { @tmpList = @{$ldapLoginForwardPortExtra}; } else { @tmpList = ($ldapLoginForwardPortExtra); } # Output all the port forwards foreach my $item (@tmpList) { my ($localPort,$destHost,$destPort) = split(/:/,$item); # Check localPort if (!defined($localPort) || $localPort < 8000) { logger('WARNING'," - Forward port extra ".color('red')."%s".color('reset'). " (awitLoginForwardPortExtra) is INVALID, localPort check failed",$item); goto PFEC1; } # Check destHost if (!defined($destHost)) { logger('WARNING'," - Forward port extra ".color('red')."%s".color('reset'). " (awitLoginForwardPortExtra) is INVALID, destHost check failed",$item); goto PFEC1; } # Check destPort if (!defined($destPort) || $destPort < 1) { logger('WARNING'," - Forward port extra ".color('red')."%s".color('reset'). " (awitLoginForwardPortExtra) is INVALID, destPort check failed",$item); goto PFEC1; } # Add port forwarding to our list push(@forwardPortExtra,{'localPort' => $localPort, 'destHost' => $destHost, 'destPort' => $destPort}); logger('INFO'," - Forward port extra ".color('green')."%s".color('reset')." (awitLoginForwardPortExtra)",$item); PFEC1: } } # Check if we need to set the host if (defined($optctl{'globbing'})) { $loginHost = $ldapEntryName; } if (my $ldapLoginHost = $ldapEntry->get_value('awitLoginHost')) { logger('INFO'," - Host ".color('green')."%s".color('reset')." (awitLoginHost)",$ldapLoginHost); $loginHost = $ldapLoginHost; } # Check if we need to set the port if (my $ldapLoginPort = $ldapEntry->get_value('awitLoginPort')) { logger('INFO'," - Port ".color('green')."%s".color('reset')." (awitLoginPort)",$ldapLoginPort); $loginPort = $ldapLoginPort; } # Check if we need to set the username if (my $ldapLoginUsername = $ldapEntry->get_value('awitLoginUsername')) { logger('INFO'," - User ".color('green')."%s".color('reset')." (awitLoginUsername)",$ldapLoginUsername); $loginUsername //= $ldapLoginUsername; } # Check if we have a description if (my $ldapDescription = $ldapEntry->get_value('description')) { logger('INFO',"Description"); foreach my $line (split(/\n/,$ldapDescription)) { logger('INFO'," ".color('green')."%s".color('reset'),$line); } # Hack'ish ... look if the description mentions dss is required... if ($ldapDescription =~ /needs ssh-dss/i) { $needDSS = 1; } } # Check if we have a wiki page if (my $ldapLoginWikiPage = $ldapEntry->get_value('awitLoginWikiPage')) { logger('INFO',"Wiki Page"); logger('INFO'," ".color('green')."%s".color('reset'),$ldapLoginWikiPage); } print STDERR "\n"; } # If we have kwalletObject and kwalletHandle defined, store the password as we've given awit-ssh-client permission to access # kwallet if (defined($kwalletObject) && defined($kwalletHandle)) { $kwalletObject->writePassword($kwalletHandle,"ldap","password",$password,$NAME); } # Check if we need to do port knocking if (defined($knockHost)) { # Make sure we have a port knocking port if (!defined($knockPort)) { logger('ERROR',color('bold red')."No port knocking port defined!".color('reset')); exit 1; } logger('NOTICE',"Port knocking '".color('green')."%s".color('reset')."' on port '".color('green')."%s".color('reset')."'...", $knockHost,$knockPort); # Do the port knock my $sock = IO::Socket::INET6->new( PeerAddr => $knockHost, PeerPort => $knockPort, Proto => 'tcp', Timeout => 3 ); # We should get a failure of "Connection refused", if not ERR if (defined($sock) || $! ne "Connection refused") { logger('ERROR',color('bold red')."Port knocking failed!".color('reset')); exit 1; } logger('INFO',"Port knocking success!"); print STDERR "\n"; } my @sshArgs = (); my @sshArgsPortForwards = (); if (defined($pkcsProvider) && $pkcsProvider ne "") { push(@sshArgs,'-I',$pkcsProvider); logger('NOTICE',color('blue')."Enabling smartcard/token authentication.".color('reset')); print STDERR "\n"; } # Only push the config file override to SSH if the config file exists in the users homedir\ if (-f (my $sshConfigFile = $ENV{"HOME"}.'/.ssh/config')) { push(@sshArgs,'-F',$sshConfigFile); } # If the server is ancient, we need to enable DSS if (defined($needDSS)) { logger('WARNING',color('red')."Host needs ssh-dss".color('reset')); push(@sshArgs,'-o','PubkeyAcceptedKeyTypes=+ssh-dss'); push(@sshArgs,'-o','HostbasedKeyTypes=+ssh-dss'); push(@sshArgs,'-o','HostKeyAlgorithms=+ssh-dss'); } # Try our key only, we should never need to fall back to password push(@sshArgs,'-o','PreferredAuthentications=publickey'); push(@sshArgs,'-o','StrictHostKeyChecking=ask'); # Use TCP keepalive push(@sshArgs,'-o','TCPKeepAlive=yes'); push(@sshArgs,'-o','ServerAliveInterval=5'); push(@sshArgs,'-o','ServerAliveCountMax=24'); # 120s # Timeout for our connect push(@sshArgs,'-o','ConnectTimeout=30'); # Fail if we cannot forward ports push(@sshArgs,'-o','ExitOnForwardFailure=yes'); # Check if we're doing port forwarding... foreach my $item (@forwardPortExtra) { push(@sshArgsPortForwards,'-L',sprintf('%s:%s:%s',$item->{'localPort'},$item->{'destHost'},$item->{'destPort'})); logger('NOTICE',color('magenta')."Forwarding port '".color('reset').$item->{'localPort'}.color('magenta'). "' on localhost to '".color('reset').$item->{'destHost'}.color('magenta')."' port '" .color('reset'). $item->{'destPort'}.color('magenta')."'\n"); } # Fixup environment before we start to run SSH local $ENV{'LANG'} = "en_US.UTF-8"; delete($ENV{'LC_ALL'}); delete($ENV{'LC_TIME'}); delete($ENV{'LC_CTYPE'}); # Setup TMPDIR, we prefer XDG_RUNTIME_DIR as its protected in /run/user/$UID/ my $TMPDIR = $ENV{'XDG_RUNTIME_DIR'} // $ENV{'TMPDIR'} // '/tmp'; # Sockets we may use our $forwardSocket; our $libvirtSocket; # Children PID's we may create our $forwardChild; our $libvirtChild; # Check if we're forwarding, we need to work a few things out... if (defined($forwardHost)) { logger('NOTICE',"Forwarding '".color('green').$realLoginHost.color('reset')."' via host '".color('green').$loginHost. color('reset')."'" .(defined($loginPort) ? " on port '".color('green')."$loginPort".color('reset')."'" : "") . "...\n\n\n"); # Default to port 22 if the login port is not defined my $destPort = $forwardPort // 22; # Add forward socket name $forwardSocket = "$TMPDIR/awit-ssh-forward-".sha1_hex("$forwardHost:$destPort $$").".sock"; # Build up our forwarding process args into this... my @forwardArgs = (); # TODO: Allow the use of multiple forwarded ports by separating them with ,'s # The first port will be assumed as the SSH port, all other ports will be forwarded via TCP/IP and reported in terminal # Add on port we're forwarding push(@forwardArgs,'-L',"$forwardSocket:$forwardHost:$forwardPort"); # Check if we have a port defined, if so specify it if (defined($loginPort)) { push(@forwardArgs,'-p',$loginPort); } # Check if we have a different username defined to login as if (defined($loginUsername)) { push(@forwardArgs,'-l',$loginUsername); } # Explicitly disable control master for the main forwarding process push(@forwardArgs,'-o','ControlMaster=no'); # Fork off child to establish the main connection $forwardChild = fork(); if (!$forwardChild) { # Exec ssh if (!exec('/usr/bin/ssh', @sshArgs, @forwardArgs, # Use basic compression '-o','Compression=yes', '-o','CompressionLevel=1', # All we're doing here is forwarding the port... '-N', $loginHost )) { logger('ERROR',color('magenta')."Forwarding SSH process failed to start".color('reset')); exit 1; } } }; # Install signal handlers to cleanup if we get a TERM or INT local $SIG{TERM} = local $SIG{INT} = \&cleanup; # Check if we're forwarding to a socket... if (defined($forwardSocket)) { # Loop waiting for the socket to be created my $delay = 30; while (! -e $forwardSocket && $delay > 0) { $delay--; sleep 1; } if ($delay) { # Check if we need to specify the username push(@sshArgs,'-l',$forwardUsername) if (defined($forwardUsername)); logger('NOTICE',"Connecting to host '".color('green')."$forwardHost".color('reset')."'" . (defined($forwardPort) ? " on port '".color('green')."$forwardPort".color('reset')."'" : "") . "...\n\n\n"); # Check what operation we're doing if (defined($useRsync)) { # Build SSH command my $sshCmd = join(' ','/usr/bin/ssh', @sshArgs, # Override where we connecting to '-o',"ProxyCommand='nc -U $forwardSocket'", # Explicitly disable control master '-o','ControlMaster=no', ); # Run rsync system('/usr/bin/rsync', '-vP', '-e',$sshCmd, @rsyncParams ); } else { # Fire up SSH system('/usr/bin/ssh', @sshArgs, @sshArgsPortForwards, # Override where we connecting to '-o',"ProxyCommand=nc -U $forwardSocket", # Explicitly disable control master '-o','ControlMaster=no', $realLoginHost ); } # Unlink socket and unset it to designate we exited normally unlink($forwardSocket); undef($forwardSocket); } else { logger('ERROR',color('magenta')."Forward socket not connected, aborting!".color('reset')); } # Normal SSH connection } else { logger('NOTICE',"Connecting to host '".color('green')."$loginHost".color('reset')."'" . (defined($loginPort) ? " on port '".color('green')."$loginPort".color('reset')."'" : "") . "...\n\n\n"); # Make sure we get asked for control master connections... push(@sshArgs,'-o','ControlMaster=autoask'); push(@sshArgs,'-o',"ControlPath=$TMPDIR/awit-ssh-master-%C"); # Check if we have a different username defined to login as if (defined($loginUsername)) { push(@sshArgs,'-l',$loginUsername); } # Check if we have a port defined, if so specify it if (defined($loginPort)) { push(@sshArgs,'-p',$loginPort); } # Check if we're doing rsync... if (defined($useRsync)) { # Build SSH command my $sshCmd = join(' ','/usr/bin/ssh', @sshArgs, # Use basic compression '-o','Compression=yes', '-o','CompressionLevel=7' ); # Run rsync system('/usr/bin/rsync', '-vP', '-e',$sshCmd, @rsyncParams ); # Check if we're doing libvirt monitor port forwarding... } elsif (defined($libvirtVNC)) { # Split off host and port my ($vncHost,$vncDisplay) = split(':',$libvirtVNC); if (!defined($vncHost) || $vncHost ne "127.0.0.1" || !defined($vncDisplay)) { logger('ERROR',color('magenta')."Libvirt VNC socket looks invalid '%s'".color('reset'),$libvirtVNC); exit 1; } # VNC port is the display plus 5900 my $vncPort = 5900 + $vncDisplay; # Add forward socket name $libvirtSocket = "$TMPDIR/awit-ssh-vnc-".sha1_hex("$loginHost:$loginPort:$libvirtVNC $$").".sock"; my @libvirtArgs = (); # Add on port we're forwarding push(@libvirtArgs,'-L',"$libvirtSocket:$vncHost:$vncPort"); # Fork off child to establish the main connection $libvirtChild = fork(); if (!$libvirtChild) { # Don't use signals for this child undef($SIG{'TERM'}); undef($SIG{'INT'}); # Exec ssh if (!exec('/usr/bin/ssh', @sshArgs, '-o','ControlMaster=no', @libvirtArgs, # Use basic compression '-o','Compression=yes', '-o','CompressionLevel=7', # All we're doing here is forwarding the port... '-N', $loginHost )) { logger('ERROR',color('magenta')."Libvirt VNC unix forwarding SSH process failed to start".color('reset')); exit 1; } } # Loop waiting for the socket to be created my $delay = 30; while (! -e $libvirtSocket && $delay > 0) { $delay--; sleep 1; } # If we still have timeout ticks left, then we connected, hopefully successfully if ($delay) { system('/usr/bin/ssvncviewer', # '-encodings','copyrect tight hextile zlib corre rre raw', # '-encodings','tight hextile zlib corre rre raw', # '-quality','7', # This is handled by ssh # '-compresslevel','0', # '-16bpp', $libvirtSocket); } else { logger('ERROR',color('magenta')."Libvirt socket not connected, aborting!".color('reset')); } # Normal SSH } else { system('/usr/bin/ssh', @sshArgs, @sshArgsPortForwards, # Use basic compression '-o','Compression=yes', '-o','CompressionLevel=1', $loginHost ); } } cleanup(); exit 0; # # Internal Functions # # Cleanup function sub cleanup { # Kill the children if ($forwardChild && kill(0,$forwardChild)) { kill('TERM',$forwardChild); # Wait for it to die waitpid($forwardChild,-1); } if ($libvirtChild && kill(0,$libvirtChild)) { kill('TERM',$libvirtChild); # Wait for it to die waitpid($libvirtChild,-1); } # Unlink sockets if ($forwardSocket) { unlink($forwardSocket); } if ($libvirtSocket) { unlink($libvirtSocket); } # Check if we exiting abnormally... if ($forwardSocket || $libvirtSocket) { print STDERR "\nExiting...\n"; exit 1; } exit 0; } # Log something sub logger { my ($level,$arg1,@args) = @_; printf(STDERR '%-7s: '.$arg1.color('reset')."\n",$level,@args); return; } # Display version sub displayVersion { print("Version: $VERSION\n"); return; } # Display usage sub displayHelp { print(STDERR<<EOF); Usage: $0 <options> [USER@]HOST $0 <options> --rsync -- <rsync options> remote://[USER@]HOST/file.name /tmp $0 <options> --libvirt-vnc HOST:PORT General Options: --help What you're seeing now. --version Display version. Port Knocking: --knock HOST:PORT Port knock a host to get access. Secure Copy: (using rsync) --rsync ... Run rsync instead of ssh, passing all command line parameters after the host to it. HOST is used for searching LDAP. Libvirt: --libvirt-vnc ... Connect to the qemu machines VNC. EOF return; }