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;
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;
}
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
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";
}
# 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";
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
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 (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;
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# 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);
}
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);
}
# 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('orange')."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','ExitOnForwardFailure=yes',
'-o','ControlMaster=ask',
exit 0;
#
# Internal Functions
#
# Log something
sub logger
{
my ($level,$arg1,@args) = @_;
printf(STDERR '%-7s: '.$arg1.color('reset')."\n",$level,@args);
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
}
# 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
}