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;
}
38
39
40
41
42
43
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
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";
}
# 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\n";
}
116
117
118
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
147
148
149
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;
}

Nigel Kukard
committed
# Some flags we may need
my $needDSS;
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# 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 (awitLoginPort)",$ldapLoginPort);
# Check if we need to set the username
if (my $ldapLoginUsername = $ldapEntry->get_value('awitLoginUsername')) {
logger('INFO'," - Username %s (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'," %s",$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 '%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);
}
# 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)) {
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','ServerAliveCountMax=24', # 120s
# Timeout for our connect
'-o','ConnectTimeout=30',
# Use basic compression
'-o','ExitOnForwardFailure=yes',
'-o','ControlMaster=ask',
exit 0;
#
# Internal Functions
#
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
# 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
}