fwknop/extras/fwknop-launcher/fwknop-launcher-lsof.pl
2016-06-03 22:08:15 -04:00

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;
}