Newer
Older
#!/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;
# 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 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;
}
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;
}
# Check for config and read
my $configFile = $ENV{"HOME"}.'/.awit-ssh.conf';
if (! -f $configFile) {
print STDERR sprintf("Your configuration file '%s' is missing!\n",$configFile);
exit 1;
}
my $config = Config::IniFiles->new(-file => $configFile);
my $ldapURL = $config->val("ldap","url");
my $ldapDC = $config->val("ldap","dc");
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# 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";
}
# 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";
print STDERR "Your LDAP CN : ";
$username = <STDIN>;
} else {
print STDERR "Your LDAP CN : $username (passwd->gecos)\n";
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($ldapURL,
# 'debug' => 15
);
if (!defined($ldap)) {
logger('ERROR',"Failed to setup LDAP object '%s'",$@);
exit 2;
}
# Bind
my $mesg = $ldap->bind("cn=$username,ou=Users,$ldapDC",password => $password);
filter => "(|(cn=$host)(awitLoginHost=$host)(awitLoginHostAlias=$host))",
);
# Check for error
if (my $mesgCode = $mesg->code()) {
if ($mesgCode eq "No such object") {
logger('ERROR',"LDAP returned '%s', this more than likely means a Username/Password error or your BaseDN is wrong.",$mesgCode);
} else {
logger('ERROR',"LDAP returned '%s'",$mesgCode);
}

Nigel Kukard
committed
# Some flags we may need
my $needDSS;
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
# 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'," - ".color('green')."Host %s".color('reset')." (awitLoginHost)",$ldapLoginHost);
$host = $ldapLoginHost;
}
# Check if we need to set the port
if (my $ldapLoginPort = $ldapEntry->get_value('awitLoginPort')) {
logger('INFO'," - ".color('green')."Port %s".color('reset')." (awitLoginPort)",$ldapLoginPort);
# Check if we need to set the username
if (my $ldapLoginUsername = $ldapEntry->get_value('awitLoginUsername')) {
logger('INFO'," - ".color('green')."Username %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('bold green')."%s".color('reset'),$line);

Nigel Kukard
committed
# 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('bold green')."%s".color('reset'),$ldapLoginWikiPage);
my @sshArgs = ();
# Check if we have a port defined, if so specify it
if (defined($port)) {
push(@sshArgs,'-p',$port);
}
# Check if we have a different username defined to login as
if (defined($loginUsername)) {
push(@sshArgs,'-l',$loginUsername);
}

Nigel Kukard
committed
# If the server is ancient, we need to enable DSS
if (defined($needDSS)) {
logger('WARNING',color('red')."Host needs ssh-dss".color('reset'));

Nigel Kukard
committed
push(@sshArgs,'-o','PubkeyAcceptedKeyTypes=+ssh-dss');
push(@sshArgs,'-o','HostbasedKeyTypes=+ssh-dss');
push(@sshArgs,'-o','HostKeyAlgorithms=+ssh-dss');
}
logger('NOTICE',color('green')."Connecting to host '".color('bold green')."$host".color('reset green')."'" .
(defined($port) ? " on port '".color('bold green')."$port".color('reset green')."'" : "") . "...\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=ask',
'-o','ServerAliveCountMax=24', # 120s
# Timeout for our connect
'-o','ConnectTimeout=30',
# Use basic compression
'-o','ControlMaster=autoask',
exit 0;
#
# Internal Functions
#
# Log something
sub logger
{
my ($level,$arg1,@args) = @_;
printf(STDERR '%-7s: '.$arg1.color('reset')."\n",$level,@args);
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
}
# 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
}