3 #############################################################################
5 # File: fwknop-launcher-lsof.pl
7 # URL: http://www.cipherdyne.org/fwknop/
9 # Purpose: This script provides a lightweight mechanism to launch the fwknop
10 # client shortly after a local user tries to initiate a connection
11 # to a remote service that is protected by fwknopd. The advantage
12 # of using this script is that local users don't have to run the
13 # fwknop client themselves before initiating an outbound
14 # connection. The idea for this script came from Sebastien
15 # Jeanquier. It is not required to have the fwknopd daemon
16 # installed in order for this script to be effective - only the
17 # fwknop client needs to be available.
19 # Author: Michael Rash (mbr@cipherdyne.org)
23 # Copyright (C) 2011 Michael Rash (mbr@cipherdyne.org)
25 # License - GNU Public License version 2 (GPLv2):
27 # This program is distributed in the hope that it will be useful,
28 # but WITHOUT ANY WARRANTY; without even the implied warranty of
29 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
30 # GNU General Public License for more details.
32 # You should have received a copy of the GNU General Public License
33 # along with this program; if not, write to the Free Software
34 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
37 #############################################################################
44 #================== config ==================
45 my $launcher_config = '~/.fwknop-launcher.conf';
47 my $sleep_interval = 1;
49 my $lsof_cmd = '/usr/bin/lsof';
50 my $fwknop_cmd = '/usr/bin/fwknop';
51 #================ end config ================
53 my $ip_re = qr|(?:[0-2]?\d{1,2}\.){3}[0-2]?\d{1,2}|; ### IPv4
55 my %spa_dispatch_cache = ();
56 my %spa_config_dsts = ();
57 my $fwknop_args_append = '';
58 my $fwknop_args_override = '';
66 die "[*] Use --help for usage information.\n" unless(GetOptions (
67 'config=s' => \$launcher_config, # launcher config
68 'lsof-cmd=s' => \$lsof_cmd, # lsof path
69 'fwknop-cmd=s' => \$fwknop_cmd, # fwknop path
70 'sleep-interval=i' => \$sleep_interval, # seconds
71 'user=s' => \$user, # set the user
72 'home-dir=s' => \$home_dir, # set the home dir
73 'no-daemon' => \$no_daemon, # run in the foreground
74 'verbose' => \$verbose, # verbose mode
75 'help' => \$help # Print help
82 &daemonize() unless $no_daemon;
88 #============== end main =============
92 my $lsof_exec_ctr = 0;
96 my $cmd = "$lsof_cmd -u $user -a -n -P -i 4"; ### IPv4
98 print "[+] Executing: $cmd\n" if $no_daemon and $verbose;
100 open LSOF, "$cmd |" or die "[*] Could not execute $cmd: $!";
102 ### COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
103 ### telnet 31483 mbr 3u IPv4 54543 0t0 TCP 127.0.0.1:41619->127.0.0.1:1234 (SYN_SENT)
106 if (/^(\S+)\s+(\d+)\s+\S+\s+.*\s(\S+)\s+($ip_re):
107 (\d+)\-\>($ip_re):(\d+)\s+\(SYN_SENT\)/x) {
116 print "[+] Matched line: $_" if $no_daemon and $verbose;
118 my $connection_string = "$command:$proto:$src_ip:$s_port:$dst_ip:$d_port";
120 next if defined $spa_dispatch_cache{$connection_string};
122 if (&is_valid_fwknop_dst($command, $proto, $dst_ip, $d_port)) {
124 if ($no_daemon and $verbose) {
125 print "[+] Matched connection, launching fwknop.\n";
128 &dispatch_fwknop($proto, $dst_ip, $d_port);
130 ### add this line to the dispatch cache so that we don't
131 ### send multiple SPA packets for the same connection
132 $spa_dispatch_cache{$connection_string} = '';
135 if ($no_daemon and $verbose) {
136 print "[-] Attempted connection not matched by ",
137 "any SPA_ACCESS variable in $launcher_config\n";
146 if ($lsof_exec_ctr == 3600) {
147 %spa_dispatch_cache = ();
151 sleep $sleep_interval;
156 sub is_valid_fwknop_dst() {
157 my ($cmd, $proto, $dst_ip, $d_port) = @_;
159 CMD: for my $access_cmd (keys %spa_config_dsts) {
160 next CMD unless $access_cmd eq 'any' or $cmd eq $access_cmd;
161 PROTO: for my $access_proto (keys %{$spa_config_dsts{$access_cmd}}) {
162 next PROTO unless $access_proto eq 'any' or $proto eq $access_proto;
163 IP: for my $access_ip (keys %{$spa_config_dsts{$access_cmd}{$access_proto}}) {
164 next IP unless $access_ip eq 'any' or $access_ip eq $dst_ip;
165 PORT: for my $access_port
166 (keys %{$spa_config_dsts{$access_cmd}{$access_proto}{$access_ip}}) {
167 return 1 if $access_port eq 'any' or $d_port == $access_port;
176 sub dispatch_fwknop() {
177 my ($proto, $dst_ip, $d_port) = @_;
179 my $fwknop_cmd_str = "$fwknop_cmd -A $proto/$d_port -s -D " .
180 "$dst_ip --get-key $key_file";
182 if ($fwknop_args_append) {
183 $fwknop_cmd_str .= " $fwknop_args_append";
184 } elsif ($fwknop_args_override) {
185 $fwknop_cmd_str = "$fwknop_cmd $fwknop_args_override";
188 print "[+] Executing: $fwknop_cmd_str\n" if $no_daemon;
190 open FWKNOP, "$fwknop_cmd_str |"
191 or die "[*] Could not execute: $fwknop_cmd_str: $!";
201 die "[*] $0: Couldn't fork: $!" unless defined $pid;
202 POSIX::setsid() or die "[*] $0: Can't start a new session: $!";
209 if ($launcher_config =~ m|^\~|) {
211 ### get the path to the user's home directory
212 &get_homedir() unless $home_dir;
214 $launcher_config =~ s|^\~|$home_dir|;
217 my $found_key_file = 0;
218 my $found_access_var = 0;
221 open F, "< $launcher_config"
222 or die "[*] Could not open $launcher_config: $!";
224 if (/^SPA_ACCESS\s+(\S+):(\S+):(\S+):(\S+);/) {
230 ### validate the protocol
231 unless ($proto eq 'any'
235 die "[*] Invalid proto '$proto' ",
236 "in $launcher_config at line: $line_ctr";
239 ### validate connection destination IP
240 unless ($dst_ip eq 'any'
241 or $dst_ip =~ /$ip_re/) {
242 die "[*] Invalid IP '$dst_ip' ",
243 "in $launcher_config at line: $line_ctr";
246 ### validate connection destination port
247 unless ($d_port eq 'any'
248 or $d_port =~ /^\d+$/) {
249 die "[*] Invalid port '$d_port' ",
250 "in $launcher_config at line: $line_ctr";
253 ### add this dst into the valid destinations cache
254 $spa_config_dsts{$cmd}{$proto}{$dst_ip}{$d_port} = '';
256 $found_access_var = 1;
258 } elsif (/^KEY_FILE\s+(\S+);/) {
261 if ($key_file =~ m|^\~|) {
262 &get_homedir() unless $home_dir;
263 $key_file =~ s|^\~|$home_dir|;
268 } elsif (/^USER\s+(\S+);/) {
269 ### might have been set via the command line
270 $user = $1 unless $user;
272 } elsif (/^FWKNOP_ARGS_APPEND\s+\"(.*)\";/) {
273 $fwknop_args_append = $1;
274 } elsif (/^FWKNOP_ARGS_OVERRIDE\s+\"(.*)\";/) {
275 $fwknop_args_override = $1;
282 unless ($found_key_file) {
283 die "[*] Must have a key file defined via KEY_FILE";
286 unless ($found_access_var) {
287 die "[*] Must have at least one SPA_ACCESS var defined";
290 &get_username() unless $user;
297 $home_dir = (getpwuid($<))[7];
300 $home_dir = $ENV{'HOME'} if defined $ENV{'HOME'};
303 die "[*] Could not determine home directory. Use the -d <homedir> option."
311 $user = (getpwuid($<))[0];
314 if (defined $ENV{'USER'}) {
315 $user = $ENV{'USER'};
316 } elsif (defined $ENV{'USERNAME'}) {
317 $user = $ENV{'USERNAME'};
321 die "[*] Could not determine username. Use the -u <user> option."
330 Usage: fwknop-launcher-lsof.pl [options]
334 -c, --config <file> - Path to fwknop-launcher.conf config file.
335 -l, --lsof-cmd <path> - Path to lsof command.
336 -f, --fwknop-cmd <path> - Path to fwknop client command.
337 -s, --sleep <seconds> - Specify sleep interval (default:
338 $sleep_interval seconds)
339 -n --no-daemon - Run in foreground mode.
340 -u, --user <username> - Specify username (usually this is not
342 --home-dir <dir> - Path to user's home directory (usually
344 -v --verbose - Print verbose information to the terminal
345 (requires --no-daemon).
346 --help - Print usage info and exit.