#!/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 Getopt::Long; # 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 Term::ReadKey if (!eval {require Term::ReadKey; 1;}) { print STDERR "You're missing Term::ReadKey, try 'apt-get install libterm-readkey-perl'\n"; exit 1; } my $VERSION = "0.0.1"; print(STDERR "AWIT-SSH-Client v$VERSION - Copyright (c) 2016, AllWorldIT\n\n"); # Grab options my %optctl = (); GetOptions(\%optctl, "help|?", "version", "debug", "knock=s", ) or exit 1; # Check for help if (defined($optctl{'help'})) { displayHelp(); exit 0; } # Check for version if (defined($optctl{'version'})) { displayVersion(); exit 0; } # Pull in hostname my $hostSpec = shift(@ARGV) // ""; my ($host,$port) = split(':',$hostSpec); if (!defined($host)) { logger('ERROR',"No hostname provided\n"); displayHelp(); exit 1; } # Check if we should be doing port knocking if (defined(my $knock = $optctl{'knock'})) { # If so, split off the host and the port my ($host,$port) = split(':',$knock); if (!defined($port)) { logger('ERROR',"Port knock specifications should be in the format of HOST:PORT"); exit 1; } print STDERR "Port knocking '$host' on port '$port'..."; # Do the port knock my $sock = IO::Socket::INET6->new( PeerAddr => $host, PeerPort => $port, Proto => 'tcp', Timeout => 3 ); # We should get a failure of "Connection refused", if not ERR if (defined($sock) || $! ne "Connection refused") { print STDERR "FAILED\n"; exit 1; } print STDERR "success\n"; } print STDERR "Your LDAP CN : "; my $username = <STDIN>; print STDERR "Your LDAP Password: "; # Don't echo password Term::ReadKey::ReadMode('noecho'); chomp(my $password = <STDIN>); # Turn echo back on Term::ReadKey::ReadMode(0); print STDERR "\n"; my $ldap = Net::LDAP->new('ldaps://XXX', # 'debug' => 15 ); if (!defined($ldap)) { logger('ERROR',"Failed to setup LDAP object '%s'",$@); exit 2; } # Bind my $mesg = $ldap->bind("cn=$username,ou=Users,YYY",password => $password); # Search $mesg = $ldap->search( base => "ou=Servers,ZZZ", filter => "(|(cn=$host)(awitLoginHost=$host)(awitLoginHostAlias=$host))", ); # Check for error if ($mesg->code()) { logger('ERROR',"LDAP returned '%s'",$mesg->error()); 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',"No LDAP results, using defaults"); } elsif ($ldapNumResults == 1) { $ldapEntry = $ldapResults[0]; } elsif ($ldapNumResults > 1) { logger('WARNING',"Found multiple entries!"); while (my ($cn,$item) = %{$mesg->as_struct()}) { logger('WARNING'," %s",$item->{'cn'}); } exit 3; } 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 '$ldapEntryName'"); # Check if we need to set the host if (my $ldapLoginHost = $ldapEntry->get_value('awitLoginHost')) { logger('INFO'," - Host %s (awitLoginHost)",$ldapLoginHost); $host = $ldapLoginHost; } # Check if we need to set the port if (my $ldapLoginPort = $ldapEntry->get_value('awitLoginPort')) { logger('INFO'," - Port %s",$ldapLoginPort); $port = $ldapLoginPort; } # Check if we have a description if (my $ldapDescription = $ldapEntry->get_value('description')) { logger('INFO',"Description"); foreach my $line (split(/\n/,$ldapDescription)) { logger('INFO'," %s",$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 '%s'",$ldapLoginWikiPage); } print STDERR "\n"; } # TODO for forawarding my @sshArgs = (); # Check if we have a port defined, if so specify it if (defined($port)) { push(@sshArgs,'-p',$port); } # If the server is ancient, we need to enable DSS if (defined($needDSS)) { push(@sshArgs,'-o','PubkeyAcceptedKeyTypes=+ssh-dss'); push(@sshArgs,'-o','HostbasedKeyTypes=+ssh-dss'); push(@sshArgs,'-o','HostKeyAlgorithms=+ssh-dss'); } logger('NOTICE',"Connecting to host '$host'" . (defined($port) ? " on port '$port'" : "") . "...\n\n\n"); exec('/usr/bin/ssh', '-F',$ENV{"HOME"}.'/.ssh/config', # Try our key only, we should never need to fall back to password '-o','PreferredAuthentications=publickey', '-o','StrictHostKeyChecking=yes', # Use TCP keepalive '-o','TCPKeepAlive=5', '-o','ServerAliveInterval=5', '-o','ConnectTimeout=30', # Use basic compression '-o','Compression=yes', '-o','CompressionLevel=1', # Fail if we cannot forward ports '-o','ExitOnForwardFailure=yes', '-o','ControlMaster=ask', '-o','ControlPath=~/.ssh/awit-ssh-master-%C', @sshArgs, $host ); exit 0; # # Internal Functions # # Log something sub logger { my ($level,$arg1,@args) = @_; printf(STDERR '%-7s: '.$arg1."\n",$level,@args); } # Display version sub displayVersion { print("Version: $VERSION\n"); } # Display usage sub displayHelp { print(STDERR<<EOF); Usage: $0 <options> General Options: --help What you're seeing now. --version Display version. --debug Enable debugging. Port Knocking: --knock HOST:PORT Port knock a host to get access. EOF }