diff --git a/extras/fwknop-launcher/fwknop-launcher-lsof.pl b/extras/fwknop-launcher/fwknop-launcher-lsof.pl new file mode 100755 index 00000000..5c58fe1c --- /dev/null +++ b/extras/fwknop-launcher/fwknop-launcher-lsof.pl @@ -0,0 +1,329 @@ +#!/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.0rc5 +# +# Copyright (C) 2011 Michael Rash (mbr@cipherdyne.org) +# +# License - GNU Public License version 2 (GPLv2): +# +# 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 Data::Dumper; +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 () { + 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 () { + 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 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 option." + unless $user; + + return; +} + +sub usage() { +} diff --git a/extras/fwknop-launcher/fwknop-launcher.conf b/extras/fwknop-launcher/fwknop-launcher.conf new file mode 100644 index 00000000..d295dbac --- /dev/null +++ b/extras/fwknop-launcher/fwknop-launcher.conf @@ -0,0 +1,30 @@ +# +# This file defines various settings for the fwknop-launcher.pl script. By +# using the fwknop-launcher, the user does not have to manually run the fwknop +# client before trying to access a remote service - the launcher will execute +# the client on behalf of the user when a connection is attempted (as seen +# currently through lsof). +# + +### The main variable in this file is the SPA_ACCESS variable below (at least +### one definition of SPA_ACCESS is required). It can be +### specified multiple times - one on each line. The format for this variable +### is as follows: +# +# SPA_ACCESS :::; +# +# Example: SPA_ACCESS ssh:tcp:1.2.3.4:22; +# +# The string "any" can be used as a wildcard (see below). Note that it is +# usually a good idea to restrict the launcher to sending SPA access requests +# that the remote fwknopd daemon is allowing - such as port 22 for SSH access. +# +# SPA_ACCESS any:any:any:any; + +SPA_ACCESS ssh:tcp:any:22; + +KEY_FILE ~/.fwknop.key; + +#FWKNOP_ARGS_APPEND "--server-port 50003"; + +#FWKNOP_ARGS_OVERRIDE "-A tcp/22 -s -D 127.0.0.1 --get-key /some/file";