351 lines
10 KiB
Perl
Executable File
351 lines
10 KiB
Perl
Executable File
#!/usr/bin/perl -w
|
|
#
|
|
#############################################################################
|
|
#
|
|
# File: fwknop-launcher-lsof.pl
|
|
#
|
|
# URL: http://www.cipherdyne.org/fwknop/
|
|
#
|
|
# Purpose: This script provides a lightweight mechanism to launch the fwknop
|
|
# client shortly after a local user tries to initiate a connection
|
|
# to a remote service that is protected by fwknopd. The advantage
|
|
# of using this script is that local users don't have to run the
|
|
# fwknop client themselves before initiating an outbound
|
|
# connection. The idea for this script came from Sebastien
|
|
# Jeanquier. It is not required to have the fwknopd daemon
|
|
# installed in order for this script to be effective - only the
|
|
# fwknop client needs to be available.
|
|
#
|
|
# Author: Michael Rash (mbr@cipherdyne.org)
|
|
#
|
|
# Version: 2.0.1
|
|
#
|
|
# Copyright (C) 2011 Michael Rash (mbr@cipherdyne.org)
|
|
#
|
|
# License - GNU General Public License (GPL v2+):
|
|
#
|
|
# 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, write to the Free Software
|
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
|
# USA
|
|
#
|
|
#############################################################################
|
|
#
|
|
|
|
use POSIX;
|
|
use Getopt::Long;
|
|
use strict;
|
|
|
|
#================== config ==================
|
|
my $launcher_config = '~/.fwknop-launcher.conf';
|
|
|
|
my $sleep_interval = 1;
|
|
|
|
my $lsof_cmd = '/usr/bin/lsof';
|
|
my $fwknop_cmd = '/usr/bin/fwknop';
|
|
#================ end config ================
|
|
|
|
my $ip_re = qr|(?:[0-2]?\d{1,2}\.){3}[0-2]?\d{1,2}|; ### IPv4
|
|
|
|
my %spa_dispatch_cache = ();
|
|
my %spa_config_dsts = ();
|
|
my $fwknop_args_append = '';
|
|
my $fwknop_args_override = '';
|
|
my $key_file = '';
|
|
my $home_dir = '';
|
|
my $user = '';
|
|
my $no_daemon = 0;
|
|
my $verbose = 0;
|
|
my $help = 0;
|
|
|
|
die "[*] Use --help for usage information.\n" unless(GetOptions (
|
|
'config=s' => \$launcher_config, # launcher config
|
|
'lsof-cmd=s' => \$lsof_cmd, # lsof path
|
|
'fwknop-cmd=s' => \$fwknop_cmd, # fwknop path
|
|
'sleep-interval=i' => \$sleep_interval, # seconds
|
|
'user=s' => \$user, # set the user
|
|
'home-dir=s' => \$home_dir, # set the home dir
|
|
'no-daemon' => \$no_daemon, # run in the foreground
|
|
'verbose' => \$verbose, # verbose mode
|
|
'help' => \$help # Print help
|
|
));
|
|
|
|
&usage() if $help;
|
|
|
|
&init();
|
|
|
|
&daemonize() unless $no_daemon;
|
|
|
|
&watch_lsof();
|
|
|
|
exit 0;
|
|
|
|
#============== end main =============
|
|
|
|
sub watch_lsof() {
|
|
|
|
my $lsof_exec_ctr = 0;
|
|
|
|
for (;;) {
|
|
|
|
my $cmd = "$lsof_cmd -u $user -a -n -P -i 4"; ### IPv4
|
|
|
|
print "[+] Executing: $cmd\n" if $no_daemon and $verbose;
|
|
|
|
open LSOF, "$cmd |" or die "[*] Could not execute $cmd: $!";
|
|
|
|
### COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
|
|
### telnet 31483 mbr 3u IPv4 54543 0t0 TCP 127.0.0.1:41619->127.0.0.1:1234 (SYN_SENT)
|
|
|
|
while (<LSOF>) {
|
|
if (/^(\S+)\s+(\d+)\s+\S+\s+.*\s(\S+)\s+($ip_re):
|
|
(\d+)\-\>($ip_re):(\d+)\s+\(SYN_SENT\)/x) {
|
|
my $command = $1;
|
|
my $pid = $2;
|
|
my $proto = lc($3);
|
|
my $src_ip = $4;
|
|
my $s_port = $5;
|
|
my $dst_ip = $6;
|
|
my $d_port = $7;
|
|
|
|
print "[+] Matched line: $_" if $no_daemon and $verbose;
|
|
|
|
my $connection_string = "$command:$proto:$src_ip:$s_port:$dst_ip:$d_port";
|
|
|
|
next if defined $spa_dispatch_cache{$connection_string};
|
|
|
|
if (&is_valid_fwknop_dst($command, $proto, $dst_ip, $d_port)) {
|
|
|
|
if ($no_daemon and $verbose) {
|
|
print "[+] Matched connection, launching fwknop.\n";
|
|
}
|
|
|
|
&dispatch_fwknop($proto, $dst_ip, $d_port);
|
|
|
|
### add this line to the dispatch cache so that we don't
|
|
### send multiple SPA packets for the same connection
|
|
$spa_dispatch_cache{$connection_string} = '';
|
|
|
|
} else {
|
|
if ($no_daemon and $verbose) {
|
|
print "[-] Attempted connection not matched by ",
|
|
"any SPA_ACCESS variable in $launcher_config\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
close LSOF;
|
|
|
|
$lsof_exec_ctr++;
|
|
|
|
if ($lsof_exec_ctr == 3600) {
|
|
%spa_dispatch_cache = ();
|
|
$lsof_exec_ctr = 0;
|
|
}
|
|
|
|
sleep $sleep_interval;
|
|
}
|
|
return;
|
|
}
|
|
|
|
sub is_valid_fwknop_dst() {
|
|
my ($cmd, $proto, $dst_ip, $d_port) = @_;
|
|
|
|
CMD: for my $access_cmd (keys %spa_config_dsts) {
|
|
next CMD unless $access_cmd eq 'any' or $cmd eq $access_cmd;
|
|
PROTO: for my $access_proto (keys %{$spa_config_dsts{$access_cmd}}) {
|
|
next PROTO unless $access_proto eq 'any' or $proto eq $access_proto;
|
|
IP: for my $access_ip (keys %{$spa_config_dsts{$access_cmd}{$access_proto}}) {
|
|
next IP unless $access_ip eq 'any' or $access_ip eq $dst_ip;
|
|
PORT: for my $access_port
|
|
(keys %{$spa_config_dsts{$access_cmd}{$access_proto}{$access_ip}}) {
|
|
return 1 if $access_port eq 'any' or $d_port == $access_port;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
sub dispatch_fwknop() {
|
|
my ($proto, $dst_ip, $d_port) = @_;
|
|
|
|
my $fwknop_cmd_str = "$fwknop_cmd -A $proto/$d_port -s -D " .
|
|
"$dst_ip --get-key $key_file";
|
|
|
|
if ($fwknop_args_append) {
|
|
$fwknop_cmd_str .= " $fwknop_args_append";
|
|
} elsif ($fwknop_args_override) {
|
|
$fwknop_cmd_str = "$fwknop_cmd $fwknop_args_override";
|
|
}
|
|
|
|
print "[+] Executing: $fwknop_cmd_str\n" if $no_daemon;
|
|
|
|
open FWKNOP, "$fwknop_cmd_str |"
|
|
or die "[*] Could not execute: $fwknop_cmd_str: $!";
|
|
close FWKNOP;
|
|
|
|
return 0;
|
|
}
|
|
|
|
sub daemonize() {
|
|
|
|
my $pid = fork();
|
|
exit 0 if $pid;
|
|
die "[*] $0: Couldn't fork: $!" unless defined $pid;
|
|
POSIX::setsid() or die "[*] $0: Can't start a new session: $!";
|
|
|
|
return;
|
|
}
|
|
|
|
sub init() {
|
|
|
|
if ($launcher_config =~ m|^\~|) {
|
|
|
|
### get the path to the user's home directory
|
|
&get_homedir() unless $home_dir;
|
|
|
|
$launcher_config =~ s|^\~|$home_dir|;
|
|
}
|
|
|
|
my $found_key_file = 0;
|
|
my $found_access_var = 0;
|
|
|
|
my $line_ctr = 1;
|
|
open F, "< $launcher_config"
|
|
or die "[*] Could not open $launcher_config: $!";
|
|
while (<F>) {
|
|
if (/^SPA_ACCESS\s+(\S+):(\S+):(\S+):(\S+);/) {
|
|
my $cmd = $1;
|
|
my $proto = lc($2);
|
|
my $dst_ip = $3;
|
|
my $d_port = $4;
|
|
|
|
### validate the protocol
|
|
unless ($proto eq 'any'
|
|
or $proto eq 'tcp'
|
|
or $proto eq 'udp'
|
|
) {
|
|
die "[*] Invalid proto '$proto' ",
|
|
"in $launcher_config at line: $line_ctr";
|
|
}
|
|
|
|
### validate connection destination IP
|
|
unless ($dst_ip eq 'any'
|
|
or $dst_ip =~ /$ip_re/) {
|
|
die "[*] Invalid IP '$dst_ip' ",
|
|
"in $launcher_config at line: $line_ctr";
|
|
}
|
|
|
|
### validate connection destination port
|
|
unless ($d_port eq 'any'
|
|
or $d_port =~ /^\d+$/) {
|
|
die "[*] Invalid port '$d_port' ",
|
|
"in $launcher_config at line: $line_ctr";
|
|
}
|
|
|
|
### add this dst into the valid destinations cache
|
|
$spa_config_dsts{$cmd}{$proto}{$dst_ip}{$d_port} = '';
|
|
|
|
$found_access_var = 1;
|
|
|
|
} elsif (/^KEY_FILE\s+(\S+);/) {
|
|
$key_file = $1;
|
|
|
|
if ($key_file =~ m|^\~|) {
|
|
&get_homedir() unless $home_dir;
|
|
$key_file =~ s|^\~|$home_dir|;
|
|
}
|
|
|
|
$found_key_file = 1;
|
|
|
|
} elsif (/^USER\s+(\S+);/) {
|
|
### might have been set via the command line
|
|
$user = $1 unless $user;
|
|
|
|
} elsif (/^FWKNOP_ARGS_APPEND\s+\"(.*)\";/) {
|
|
$fwknop_args_append = $1;
|
|
} elsif (/^FWKNOP_ARGS_OVERRIDE\s+\"(.*)\";/) {
|
|
$fwknop_args_override = $1;
|
|
}
|
|
|
|
$line_ctr++;
|
|
}
|
|
close F;
|
|
|
|
unless ($found_key_file) {
|
|
die "[*] Must have a key file defined via KEY_FILE";
|
|
}
|
|
|
|
unless ($found_access_var) {
|
|
die "[*] Must have at least one SPA_ACCESS var defined";
|
|
}
|
|
|
|
&get_username() unless $user;
|
|
|
|
return;
|
|
}
|
|
|
|
sub get_homedir() {
|
|
|
|
$home_dir = (getpwuid($<))[7];
|
|
|
|
unless ($home_dir) {
|
|
$home_dir = $ENV{'HOME'} if defined $ENV{'HOME'};
|
|
}
|
|
|
|
die "[*] Could not determine home directory. Use the -d <homedir> option."
|
|
unless $home_dir;
|
|
|
|
return;
|
|
}
|
|
|
|
sub get_username() {
|
|
|
|
$user = (getpwuid($<))[0];
|
|
|
|
unless ($user) {
|
|
if (defined $ENV{'USER'}) {
|
|
$user = $ENV{'USER'};
|
|
} elsif (defined $ENV{'USERNAME'}) {
|
|
$user = $ENV{'USERNAME'};
|
|
}
|
|
}
|
|
|
|
die "[*] Could not determine username. Use the -u <user> option."
|
|
unless $user;
|
|
|
|
return;
|
|
}
|
|
|
|
sub usage() {
|
|
print <<_HELP_;
|
|
|
|
Usage: fwknop-launcher-lsof.pl [options]
|
|
|
|
Options:
|
|
|
|
-c, --config <file> - Path to fwknop-launcher.conf config file.
|
|
-l, --lsof-cmd <path> - Path to lsof command.
|
|
-f, --fwknop-cmd <path> - Path to fwknop client command.
|
|
-s, --sleep <seconds> - Specify sleep interval (default:
|
|
$sleep_interval seconds)
|
|
-n --no-daemon - Run in foreground mode.
|
|
-u, --user <username> - Specify username (usually this is not
|
|
needed).
|
|
--home-dir <dir> - Path to user's home directory (usually
|
|
this is not needed).
|
|
-v --verbose - Print verbose information to the terminal
|
|
(requires --no-daemon).
|
|
--help - Print usage info and exit.
|
|
|
|
_HELP_
|
|
exit 0;
|
|
}
|