firewalld support from Gerry Reno
[fwknop.git] / perl / legacy / fwknop / fwknop
1 #!/usr/bin/perl -w
2 #
3 #############################################################################
4 #
5 # File: fwknop
6 #
7 # URL: http://www.cipherdyne.org/fwknop/
8 #
9 # Purpose: fwknop implements an authorization scheme known as Single Packet
10 #          Authorization (SPA) that requires only a single encrypted packet to
11 #          communicate various pieces of information including desired access
12 #          through an iptables/ipfw policy and/or specific commands to execute
13 #          on the target system.  The main application of this program is to
14 #          protect services such as SSH with an additional layer of security
15 #          in order to make the exploitation of vulnerabilities (both 0-day
16 #          and unpatched code) much more difficult.  fwknop also supports
17 #          encrypted port knocking, but this is a legacy authentication mode
18 #          when compared to SPA.
19 #
20 #          More information can be found in the fwknop(8) and fwknopd(8) man
21 #          pages, and also online here:
22 #
23 #          http://www.cipherdyne.org/fwknop/docs/
24 #
25 # Author: Michael Rash (mbr@cipherdyne.org)
26 #
27 # Version: 1.9.12
28 #
29 # Copyright (C) 2004-2009 Michael Rash (mbr@cipherdyne.org)
30 #
31 # License - GNU Public License version 2 (GPLv2):
32 #
33 #    This program is distributed in the hope that it will be useful,
34 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
35 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
36 #    GNU General Public License for more details.
37 #
38 #    You should have received a copy of the GNU General Public License
39 #    along with this program; if not, write to the Free Software
40 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
41 #    USA
42 #
43 #############################################################################
44 #
45 # $Id: fwknop 1533 2009-09-08 02:44:02Z mbr $
46 #
47
48 use IO::Socket;
49 use IO::Handle;
50 use MIME::Base64;
51 use Data::Dumper;
52 use POSIX;
53 use Getopt::Long;
54 use warnings;
55 use strict;
56
57 my $version = '1.9.12';
58 my $revision_svn = '$Revision: 1533 $';
59 my $rev_num = '1';
60 ($rev_num) = $revision_svn =~ m|\$Rev.*:\s+(\S+)|;
61
62 my $lib_dir = '/usr/lib/fwknop';
63 my $print_version = 0;
64 my $print_help    = 0;
65 my $run_last_args = 0;
66 my $debug         = 0;
67 my $quiet         = 0;
68 my $verbose       = 0;
69 my $test_mode     = 0;
70 my $cmdl_homedir  = '';
71 my $knock_sleep   = 1;  ### default to 1 second difference between port knocks
72 my $knock_dst     = '';
73 my $knock_dst_pre_resolve = '';
74 my $homedir       = '';
75 my $min_port      = 10000;
76 my $max_port      = 65535;
77 my $icmp_type     = 8;  ### type/code 8/0 => echo request
78 my $icmp_code     = 0;
79 my $spoof_src     = '';
80 my $server_mode   = 'pcap';
81 my $user_rc_file  = '';
82 my $server_proto  = '';
83 my $run_last_host = '';
84 my $total_digest  = '';
85 my $show_last_host_cmd = '';
86 my $show_last_cmd = 0;
87 my $time_offset_plus = '';
88 my $time_offset_minus = '';
89 my $skip_fko_module = 0;
90 my $test_fko_exists = 0;
91 my $use_fko_module = 0;
92 my $fko_obj = '';
93 my $http_proxy_host = '';
94 my $http_proxy;
95 ### the variable is declared, but not defined. This is necessary for the
96 ###--HTTP_proxy cli option to work as expected.
97
98 my $http_proxy_user = '';
99 my $http_proxy_pass = '';
100 my $gpg_home_dir  = '';
101 my $gpg_recipient = '';
102 my $use_gpg_agent = 0;
103 my $max_msg_len   = 1500;
104 my $max_resolve_http_recv = 1500;
105 my $gpg_verbose   = 0;
106 my $gpg_no_options = 0;
107 my $gpg_agent_info = '';
108 my $include_salted = 0;
109 my $client_src_port = 0;
110 my $gpg_default_key = 0;
111 my $gpg_use_options = 0;
112 my $err_wait_timer  = 30;  ### seconds
113 my $resolve_ip_url  = 'http://www.whatismyip.com/automation/n09230945.asp';
114 my $gpg_signing_key = '';
115 my $save_packet_mode = 0;
116 my $save_packet_file = '';
117 my $save_packet_append = 0;
118 my $cmdline_pcap_cmd   = '';
119 my $no_save_last_args  = 0;
120 my $save_destination   = 0;
121 my $server_auth_method = '';
122 my $spa_established_tcp  = 0;
123 my $spa_over_http = 0;
124 my $resolve_external_ip  = 0;
125 my $server_auth_crypt_pw = '';
126 my $pcap_sleep_interval  = 1;  ### seconds
127 my $selected_random_nat_port = 0;
128 my $include_base64_trailing_equals = 0;
129 my $include_base64_gnupg_prefix = 0;
130 my $rand_port     = 0;  ### for SPA packet destination port
131 my $NAT_rand_port = 0;  ### for randomized access based on
132                         ### NAT rules (e.g. ssh -p <randport>).
133 my $NAT_local = 0; ### Flag for forwarding a port to local socket.
134
135 my $locale = 'C';  ### default LC_ALL env variable
136 my $no_locale = 0;
137 my $gpg_prefix = 'hQ';  ### base64 encoded version of 0x8502
138 my $gpg_path = '';
139
140 ### User agent for contacting http://www.whatismyip.org/, (can
141 ### override with --User-agent)
142 my $ext_resolve_user_agent = "Fwknop/$version";
143 $ext_resolve_user_agent =~ s|-pre\d+||;
144
145 ### ACCESS message:
146 ###     random data : user : client_timestamp : client_version : \
147 ###     type (1) : access_request : message digest
148 my $SPA_ACCESS_MODE  = 1;  ### default
149
150 ### COMMAND message:
151 ###     random data : user : client_timestamp : client_version : \
152 ###     type (0) : command : message digest
153 my $SPA_COMMAND_MODE = 0;
154
155 ### NAT ACCESS message:
156 ###     random data : user : client_timestamp : client_version : \
157 ###     type (2) : access_request : NAT_info : message digest
158 my $SPA_NAT_ACCESS_MODE = 2;
159
160 ### ACCESS message with client-defined firewall timeout:
161 ###     random data : user : client_timestamp : client_version : \
162 ###     type (3) : access_request : timeout : message digest
163 my $SPA_CLIENT_TIMEOUT_ACCESS_MODE = 3;
164
165 ### NAT ACCESS message with client-defined firewall timeout:
166 ###     random data : user : client_timestamp : client_version : \
167 ###     type (4) : access_request : NAT_info : timeout : message digest
168 my $SPA_CLIENT_TIMEOUT_NAT_ACCESS_MODE = 4;
169
170 ### local NAT ACCESS message:
171 ###     random data : user : client_timestamp : client_version : \
172 ###     type (5) : access_request : NAT_info : message digest
173 my $SPA_LOCAL_NAT_ACCESS_MODE = 5;
174
175 ### local NAT ACCESS message with client-defined firewall timeout:
176 ###     random data : user : client_timestamp : client_version : \
177 ###     type (6) : access_request : NAT_info : timeout : message digest
178 my $SPA_CLIENT_TIMEOUT_LOCAL_NAT_ACCESS_MODE = 6;
179
180 ### default time values
181 my $knock_interval  = 60;
182 my $cmdl_fw_timeout = -1;
183
184 ### Digest types and command argument flags
185 my $MD5_DIGEST    = 1;
186 my $SHA1_DIGEST   = 2;
187 my $SHA256_DIGEST = 3;
188 my $digest_type = $SHA256_DIGEST; ### default
189 my $cmdl_digest_alg = '';
190
191 ### default destination port; you can change with --Server-port,
192 ### --rand-port, or by appending the ":<port>" syntax to the
193 ## destination host
194 my $DEFAULT_PORT = 62201;
195
196 ### default to root (client must run as root in this mode)
197 my $spoof_username = '';
198 my $spoof_proto    = '';
199
200 ### encrypted port knock vars (these are only used in the legacy
201 ### port knocking mode).
202 my $cmdline_offset  = 0;
203 my $enc_port_offset = 61000;  ### default offset
204 my $enc_key         = '';
205 my $enc_alg         = 'Rijndael';
206 my $enc_blocksize   = 32;
207
208 ### there is a constant "RIJNDAEL_KEYSIZE" in the Crypt::Rijndael sources, but
209 ### it is not used; a 16 byte key size is fine.
210 my $enc_keysize = 16;
211
212 my $enc_shared_secret = '';
213 my $enc_allow_ip      = '';
214 my $enc_source_ip     = '';
215 my $enc_rotate_proto  = 0;
216 my $get_key_file      = '';  ### get key from file
217 my $enc_pcap_port     = $DEFAULT_PORT;
218 my $cmdl_spa_port     = 0;
219 my $access_str        = '';
220 my $NAT_access_str = '';  ### for access through the iptables FORWARD chain
221
222 ### packet counters
223 my $tcp_ctr  = 0;
224 my $udp_ctr  = 0;
225 my $icmp_ctr = 0;
226
227 ### tcp option types
228 my $tcp_nop_type       = 1;
229 my $tcp_mss_type       = 2;
230 my $tcp_win_scale_type = 3;
231 my $tcp_sack_type      = 4;
232 my $tcp_timestamp_type = 8;
233
234 my %tcp_p0f_opt_types = (
235     'N' => $tcp_nop_type,
236     'M' => $tcp_mss_type,
237     'W' => $tcp_win_scale_type,
238     'S' => $tcp_sack_type,
239     'T' => $tcp_timestamp_type
240 );
241
242 my $ip_re = qr|(?:[0-2]?\d{1,2}\.){3}[0-2]?\d{1,2}|;
243
244 my @args_cp = @ARGV;
245
246 ### run GetOpt() to get command line args
247 &handle_command_line();
248
249 &usage(0) if $print_help;
250
251 if ($print_version) {
252     print "[+] fwknop v$version (file revision: $rev_num)\n",
253         "      by Michael Rash <mbr\@cipherdyne.org>\n";
254     exit 0;
255 }
256
257 ### set LC_ALL env variable
258 $ENV{'LC_ALL'} = $locale unless $no_locale;
259
260 &set_digest_type() if $cmdl_digest_alg;
261
262 ### import fwknop perl modules
263 &import_perl_modules();
264
265 ### this is only necessary for older versions of perl (newer versions
266 ### call srand() automatically at the first usage of rand() if srand()
267 ### was not already called).
268 srand();
269
270 &get_homedir();
271
272 ### save a copy of the SPA destination
273 $knock_dst_pre_resolve = $knock_dst;
274
275 &assign_spa_port();
276
277 if ($run_last_args or $show_last_cmd) {
278
279     ### run fwknop with same command line args as the previous
280     ### execution
281     &run_last_cmdline();
282
283 } elsif ($run_last_host or $show_last_host_cmd) {
284
285     $run_last_host = $show_last_host_cmd if $show_last_host_cmd;
286
287     ### run fwknop with the last args for this particular knock destination
288     &run_last_host_cmdline();
289 }
290
291 die "[*] Must specify a destination server with -D <IP|Host>"
292     unless $knock_dst;
293
294 if ($cmdl_fw_timeout != -1) {
295     die "[*] Must specify a firewall timeout >= 0"
296         unless $cmdl_fw_timeout >= 0;
297 }
298
299 my $print_mode = '';
300 if (lc($server_mode) eq 'pcap') {
301     $print_mode = 'SPA';
302 } elsif (lc($server_mode) eq 'knock') {
303     $print_mode = 'encrypted port knocking';
304 } elsif (lc($server_mode) eq 'shared') {
305     $print_mode = 'shared sequence port knocking';
306 } else {
307     die "[*] Unknown server mode: $server_mode ",
308         qq|(must be "pcap", "knock", or "shared").|;
309 }
310
311 if ($debug) {
312     print "\n[+] ***DEBUG*** Starting fwknop client ($print_mode mode)...\n";
313 } else {
314     print "\n[+] Starting fwknop client ($print_mode mode)...\n"
315         unless $quiet;
316 }
317
318 if ($verbose) {
319     print "[+] fwknop Command line: @args_cp\n";
320 }
321
322 unless ($knock_dst =~ /$ip_re/ or $http_proxy) {
323     print "[+] Resolving hostname: $knock_dst\n" unless $quiet;
324     ### resolve to an IP
325     my $iaddr = inet_aton($knock_dst)
326         or die "[*] Could not resolve $knock_dst to an IP.";
327     my $addr = inet_ntoa($iaddr)
328         or die "[*] Could not resolve $knock_dst to an IP.";
329     $knock_dst = $addr;
330 }
331
332 if ($NAT_local and not $NAT_access_str) {
333     if ($NAT_rand_port) {
334         my $rand_port = &rand_port();
335         $NAT_access_str = "$knock_dst,$rand_port";
336         print "[+] Requesting NAT access for randomized port: $rand_port\n";
337         $selected_random_nat_port = 1;
338     } else {
339         $NAT_access_str = "$knock_dst,55000";
340         print
341 "[+] Requesting NAT support for port 55,000; use --NAT-rand-port for a\n",
342 "    random port.\n";
343     }
344 }
345
346 &validate_access_str() if $access_str;
347 &validate_NAT_access_str() if $NAT_access_str;
348
349 if (lc($server_mode) eq 'pcap' or lc($server_mode) eq 'knock') {
350     die "[*] Must also specify: -D <destination>\n"
351         unless $knock_dst;
352
353     if ($spoof_src) {
354         $< == 0 && $> == 0 or
355             die '[*] You must be root (or equivalent ',
356                 "UID 0 account) to spoof the source address.\n";
357     }
358
359     unless ($enc_allow_ip
360             or $enc_source_ip
361             or $resolve_external_ip) {
362         die "[*] Must either specify: --allow-IP <IP>, ",
363             "--source-IP, or --Resolve-external-IP\n";
364     }
365
366     ### make fwknop server see "0.0.0.0" in the encrypted sequence.
367     ### This will instruct the server to open the port for whatever
368     ### source IP the sequence comes from.  This is useful for
369     ### clients that are behind a NAT device.
370     $enc_allow_ip = '0.0.0.0' if $enc_source_ip;
371
372     ### resolve the extenal IP via http://www.whatismyip.org
373     $enc_allow_ip = &resolve_external_ip() if $resolve_external_ip;
374
375     unless ($enc_allow_ip =~ /$ip_re/) {
376         ### resolve to an IP
377         my $iaddr = inet_aton($enc_allow_ip)
378             or die "[*] Could not resolve $enc_allow_ip to IP.";
379         my $addr = inet_ntoa($iaddr)
380             or die "[*] Could not resolve $enc_allow_ip to IP.";
381         $enc_allow_ip = $addr;
382     }
383
384     if ($cmdline_offset) {
385         if (lc($server_mode) eq 'pcap') {
386             die "[*] Port offset is meaningless in pcap mode ",
387                 "(only a single packet is sent).";
388         }
389         unless ($cmdline_offset < 65280 and $cmdline_offset > 0) {
390             die "[*] Port offset must be 0 < port < 65280";
391         }
392         $enc_port_offset = $cmdline_offset;
393     }
394     if (lc($server_mode) eq 'pcap') {
395         unless ($enc_pcap_port < 65535 and $enc_pcap_port > 0) {
396             die "[*] Port offset must be 0 < port < 65535";
397         }
398     }
399 } else {
400     if ($enc_rotate_proto) {
401         die '[*] Can only specify --rotate-proto with ',
402             'encrypted sequences.';
403     }
404 }
405
406 if ($save_packet_mode) {
407     ### save of copy of the packet
408     unless ($save_packet_file) {
409         $save_packet_file = "$homedir/fwknop_save_packet.$$";
410     }
411     unless ($save_packet_append) {
412         unlink $save_packet_file if -e $save_packet_file;
413     }
414 }
415
416 ### save our command line args (so -l can be used next time)
417 unless ($run_last_args or $run_last_host or $no_save_last_args
418         or $show_last_cmd or $show_last_host_cmd) {
419     &save_args();
420 }
421
422 if (lc($server_mode) eq 'pcap' or lc($server_mode) eq 'knock') {
423
424     ### get the encryption key from the --get-key file
425     ### or from STDIN if it's not in the file.
426     &get_key();
427
428     &handle_server_auth_method() if $server_auth_method;
429
430     if (lc($server_mode) eq 'pcap') {
431
432         ### construct and send the encrypted message to the server
433         ### (sends a single packet).
434         &pcap_send_encrypted_msg(&pcap_build_enc_msg());
435
436     } else {
437         ### we are running in port knocking mode, so get the
438         ### encrypted port sequence (16 ports)
439         &knock_ports(&encrypt_sequence());
440     }
441 } else {
442     ### we are running in non-encrypted port knocking mode, so get
443     ### the port sequence
444     &knock_ports(&import_shared_sequence());
445 }
446 exit 0;
447 #============================ end main ==============================
448
449 sub pcap_build_enc_msg() {
450
451     ### message format (all fields are separated by ":" characters
452     #
453     #  random number (16 bytes)
454     #  username
455     #  timestamp
456     #  software version
457     #  message type and content:
458     #    0 => command mode / command to execute
459     #    1 => access mode / IP,proto,port
460     #    2 => nat access mode / IP,proto,port / internalIP,externalNATPort
461     #  (optional) server_auth (post 0.9.2 release)
462     #  message digest (SHA256 / SHA1 / MD5 )
463
464     my $msg = '';
465
466     ### initialize the FKO object if we are using the FKO module
467     if ($use_fko_module) {
468         $fko_obj = FKO->new()
469             or die "[*] Could not acquire FKO object: ", FKO->error_str;
470         if ($debug) {
471             print "[+] Using libfko functions via the FKO module.\n";
472         }
473     }
474
475     unless ($quiet) {
476         print "\n[+] Building encrypted Single Packet Authorization (SPA) ",
477             "message...\n";
478         print "[+] Packet fields:\n\n";
479     }
480
481     ### start the SPA message with 16 bytes of random data
482     $msg = &SPA_random_number();
483
484     ### append the username
485     $msg .= &SPA_user();
486
487     ### append the timestamp
488     $msg .= &SPA_timestamp();
489
490     ### append the fwknop client version
491     $msg .= &SPA_version();
492
493     ### append the message type (integer)
494     $msg .= &SPA_message_type();
495
496     ### append the SPA message (this is usually just a request for
497     ### access to a port/protocol combination)
498     $msg .= &SPA_message();
499
500     ### append any client defined fw timeout (optional)
501     $msg .= &fko_SPA_client_timeout() if $use_fko_module;
502
503     ### append NAT access requirement (optional)
504     $msg .= &SPA_nat_access();
505
506     ### append server authentication method (optional)
507     $msg .= &SPA_server_auth();
508
509     ### append any client defined fw timeout (optional)
510     $msg .= &no_fko_SPA_client_timeout() unless $use_fko_module;
511
512     ### append Message Digest
513     $msg =~ s/\n//g;
514     $msg .= &SPA_digest($msg);
515
516     my $encrypted_msg = '';
517
518     ### encrypt the SPA packet using the FKO module
519     if ($use_fko_module) {
520         $encrypted_msg = &fko_encrypt();
521         $fko_obj->destroy();
522         return $encrypted_msg;
523     }
524
525     if ($debug) {
526         print "\n[+] Clear text message (some fields base64 encoded): $msg\n",
527             "    Digest: $total_digest\n";
528     }
529
530     if ($gpg_signing_key or $gpg_recipient) {
531         $encrypted_msg = &pcap_GPG_encrypt_msg($msg);
532     } else {
533         $encrypted_msg = &pcap_Rijndael_encrypt_msg($msg);
534     }
535
536     unless ($include_base64_trailing_equals and $encrypted_msg =~ /=$/) {
537         print "[+] Stripping trailing equals chars from base64 encoding.\n"
538             if $debug;
539         $encrypted_msg =~ s/=*$//;
540     }
541
542     return $encrypted_msg;
543 }
544
545 sub SPA_random_number() {
546
547     my $random_num = 0;
548
549     if ($use_fko_module) {
550         $random_num = $fko_obj->rand_value();
551     } else {
552         $random_num = int(rand(100000000000000));
553         $random_num .= int(rand(10)) while (length($random_num) < 16);
554     }
555     print "        Random data:    $random_num\n" unless $quiet;
556
557     return '' if $use_fko_module;
558     return $random_num;
559 }
560
561 sub SPA_user() {
562     my $user = 'root';
563     if ($spoof_src) {
564         if ($spoof_username) {
565             $user = $spoof_username;
566         }
567     } else {
568         ### getlogin() is better than using ENV{'USER'}, which is
569         ### easily manipulated, so only use as a last resort.
570         if ($spoof_username) {
571             $user = $spoof_username;
572         } else {
573             $user = getlogin() || getpwuid($<) ||
574                 die "[*] Could not determine user; try using the ",
575                     "--Spoof-user option";
576         }
577     }
578
579     if ($use_fko_module) {
580         my $err = $fko_obj->username($user);
581         if ($err) {
582             die "[*] FKO error setting username: ",
583                 $fko_obj->errstr($err), "\n";
584         }
585         $user = $fko_obj->username();
586     }
587     print  "        Username:       $user\n" unless $quiet;
588
589     return '' if $use_fko_module;
590     return ':' . encode_base64($user, '');
591 }
592
593 sub SPA_timestamp() {
594
595     my $timestamp = '';
596
597     $timestamp = time() unless $use_fko_module;
598
599     if ($time_offset_plus) {
600         my $offset = &time_offset($time_offset_plus);
601         $timestamp += $offset;
602         if ($use_fko_module) {
603             my $err = $fko_obj->timestamp($offset);
604             if ($err) {
605                 die "[*] FKO error setting timestamp offset: ",
606                     $fko_obj->errstr($err), "\n";
607             }
608         }
609     }
610
611     if ($time_offset_minus) {
612         my $offset = &time_offset($time_offset_minus);
613         $timestamp -= $offset;
614         if ($use_fko_module) {
615             my $err = $fko_obj->timestamp(-$offset);
616             if ($err) {
617                 die "[*] FKO error setting timestamp offset: ",
618                     $fko_obj->errstr($err), "\n";
619             }
620         }
621     }
622
623     $timestamp = $fko_obj->timestamp() if $use_fko_module;
624
625     print "        Timestamp:      $timestamp\n" unless $quiet;
626
627     return '' if $use_fko_module;
628     return ':' . $timestamp;
629 }
630
631 sub SPA_version() {
632
633     if ($use_fko_module) {
634         print "        Version:        ", $fko_obj->version(), "\n"
635             unless $quiet;
636     } else {
637         print "        Version:        $version\n" unless $quiet;
638     }
639
640     return '' if $use_fko_module;
641     return ':' . $version;
642 }
643
644 sub SPA_message_type() {
645
646     my $return_str = '';
647     my $print_str  = '';
648     my $spa_type   = '';
649     my $fko_err    = '';
650
651     if ($cmdline_pcap_cmd) {
652         $print_str = 'command mode';
653         $spa_type  = $SPA_COMMAND_MODE;
654         if ($use_fko_module) {
655             $fko_err  = $fko_obj->spa_message_type(FKO->FKO_COMMAND_MSG);
656             $spa_type = $fko_obj->spa_message_type();
657         }
658     } elsif ($NAT_access_str) {
659         if ($NAT_local) {
660             if ($cmdl_fw_timeout >= 0) {
661                 $print_str = 'Local NAT client-timeout access mode';
662                 $spa_type  = $SPA_CLIENT_TIMEOUT_LOCAL_NAT_ACCESS_MODE;
663                 if ($use_fko_module) {
664                     $fko_err = $fko_obj->
665                         spa_message_type(FKO->FKO_CLIENT_TIMEOUT_LOCAL_NAT_ACCESS_MSG);
666                     $spa_type = $fko_obj->spa_message_type();
667                 }
668             } else {
669                 $print_str = 'Local NAT access mode';
670                 $spa_type  = $SPA_LOCAL_NAT_ACCESS_MODE;
671                 if ($use_fko_module) {
672                     $fko_err = $fko_obj->
673                         spa_message_type(FKO->FKO_LOCAL_NAT_ACCESS_MSG);
674                     $spa_type = $fko_obj->spa_message_type();
675                 }
676             }
677         } else {
678             if ($cmdl_fw_timeout >= 0) {
679                 $print_str = 'NAT client-timeout access mode';
680                 $spa_type  = $SPA_CLIENT_TIMEOUT_NAT_ACCESS_MODE;
681                 if ($use_fko_module) {
682                     $fko_err = $fko_obj->
683                         spa_message_type(FKO->FKO_CLIENT_TIMEOUT_NAT_ACCESS_MSG);
684                     $spa_type = $fko_obj->spa_message_type();
685                 }
686             } else {
687                 $print_str = 'NAT access mode';
688                 $spa_type  = $SPA_NAT_ACCESS_MODE;
689                 if ($use_fko_module) {
690                     $fko_err = $fko_obj->
691                         spa_message_type(FKO->FKO_NAT_ACCESS_MSG);
692                     $spa_type = $fko_obj->spa_message_type();
693                 }
694             }
695         }
696     } else {
697         if ($cmdl_fw_timeout >= 0) {
698             $print_str = 'access client-timeout mode';
699             $spa_type  = $SPA_CLIENT_TIMEOUT_ACCESS_MODE;
700             if ($use_fko_module) {
701                 $fko_err  = $fko_obj->spa_message_type(FKO->FKO_CLIENT_TIMEOUT_ACCESS_MSG);
702                 $spa_type = $fko_obj->spa_message_type();
703             }
704         } else {
705             $print_str = 'access mode';
706             $spa_type  =  $SPA_ACCESS_MODE;
707             if ($use_fko_module) {
708                 $fko_err  = $fko_obj->spa_message_type(FKO->FKO_ACCESS_MSG);
709                 $spa_type = $fko_obj->spa_message_type();
710             }
711         }
712     }
713
714     if ($use_fko_module) {
715         if ($fko_err) {
716             die "[*] FKO error setting message type: ",
717                 $fko_obj->errstr($fko_err), "\n";
718         }
719     }
720
721     unless ($quiet) {
722         print "        Type:           $spa_type   ($print_str)\n";
723     }
724
725     return '' if $use_fko_module;
726     return ':' . $spa_type  ;
727 }
728
729 sub SPA_message() {
730
731     $access_str = 'none/0' unless $access_str;
732     my $print_str = "$enc_allow_ip,$access_str";
733
734     if ($cmdline_pcap_cmd) {
735         ### a specific command will be executed on the server.  Note we
736         ### prepend the command string with the $enc_allow_ip so that the
737         ### fwknopd server can apply the REQUIRE_SOURCE_ADDRESS criteria.
738
739         $print_str = $cmdline_pcap_cmd;
740
741         if ($use_fko_module) {
742             my $err = $fko_obj->spa_message("$enc_allow_ip,$cmdline_pcap_cmd");
743             if ($err) {
744                 die "[*] FKO error setting command string: ",
745                     $fko_obj->errstr($err), "\n";
746             }
747             $print_str = $fko_obj->spa_message();
748         }
749         print "        Cmd:            $print_str\n" unless $quiet;
750         return '' if $use_fko_module;
751         return ':' . encode_base64("$enc_allow_ip,$cmdline_pcap_cmd", '');
752     }
753
754     if ($use_fko_module) {
755         my $err = $fko_obj->spa_message("$enc_allow_ip,$access_str");
756         if ($err) {
757             die "[*] FKO error setting access string: ",
758                 $fko_obj->errstr($err), "\n";
759         }
760         $print_str = $fko_obj->spa_message();
761     }
762
763     ### access to port(s)/protocol(s) will be granted on the
764     ### server
765     print "        Access:         $print_str\n"
766         unless $quiet;
767
768     return '' if $use_fko_module;
769     return ':' . encode_base64("$enc_allow_ip,$access_str", '');
770 }
771
772 sub fko_SPA_client_timeout() {
773     return '' unless $cmdl_fw_timeout >= 0;
774
775     if ($use_fko_module) {
776         my $err = $fko_obj->spa_client_timeout($cmdl_fw_timeout);
777         if ($err) {
778             die "[*] FKO error setting timeout: ",
779                 $fko_obj->errstr($err), "\n";
780         }
781     }
782     return '';
783 }
784
785 sub no_fko_SPA_client_timeout() {
786     return '' unless $cmdl_fw_timeout >= 0;
787     return ':' . $cmdl_fw_timeout;
788 }
789
790 sub SPA_server_auth() {
791     if (lc($server_auth_method) eq 'crypt') {
792         unless ($quiet) {
793             print "        Server auth:   $server_auth_method,";
794             for (my $i=0; $i<length($server_auth_crypt_pw); $i++) {
795                 print '*';
796             }
797             print "\n";
798         }
799         return ':' . encode_base64("crypt,$server_auth_crypt_pw", '');
800     }
801     return '';
802 }
803
804 sub SPA_nat_access() {
805
806     return '' unless $NAT_access_str;
807
808     my $print_str = $NAT_access_str;
809
810     if ($use_fko_module) {
811         my $err = $fko_obj->spa_nat_access($NAT_access_str);
812         if ($err) {
813             die "[*] FKO error setting NAT access string: ",
814                 $fko_obj->errstr($err), "\n";
815         }
816         $print_str = $fko_obj->spa_nat_access();
817     }
818
819     print "        NAT access:     $print_str\n" unless $quiet;
820
821     return '' if $use_fko_module;
822     return ':' . encode_base64($NAT_access_str, '');
823 }
824
825 sub SPA_digest() {
826     my $msg = shift;
827
828     my $digest = '';
829
830     if ($digest_type == $MD5_DIGEST) {
831         if ($use_fko_module) {
832             my $err = $fko_obj->digest_type(FKO->FKO_DIGEST_MD5);
833             if ($err) {
834                 die "[*] FKO error setting digest: ",
835                     $fko_obj->errstr($err), "\n";
836             }
837         } else {
838             require Digest::MD5;
839             Digest::MD5->import(qw(md5_base64));
840             if ($debug) {
841                 print "[+] Digest::MD5 $Digest::MD5::VERSION\n";
842             }
843             $digest = md5_base64($msg);
844             if ($debug) {
845                 $total_digest = md5_base64("$msg:$digest");
846             }
847             print "        MD5 digest:     $digest\n" unless $quiet;
848         }
849     } elsif ($digest_type == $SHA1_DIGEST) {
850         if ($use_fko_module) {
851             my $err = $fko_obj->digest_type(FKO->FKO_DIGEST_SHA1);
852             if ($err) {
853                 die "[*] FKO error setting digest: ",
854                     $fko_obj->errstr($err), "\n";
855             }
856         } else {
857             require Digest::SHA;
858             Digest::SHA->import(qw(sha1_base64));
859             if ($debug) {
860                 print "[+] Digest::SHA::VERSION $Digest::SHA::VERSION\n";
861             }
862             $digest = sha1_base64($msg);
863             if ($debug) {
864                 $total_digest = sha1_base64("$msg:$digest");
865             }
866             print "        SHA1 digest:    $digest\n" unless $quiet;
867         }
868     } elsif ($digest_type == $SHA256_DIGEST) {
869         if ($use_fko_module) {
870             my $err = $fko_obj->digest_type(FKO->FKO_DIGEST_SHA256);
871             if ($err) {
872                 die "[*] FKO error setting digest: ",
873                     $fko_obj->errstr($err), "\n";
874             }
875         } else {
876             require Digest::SHA;
877             Digest::SHA->import(qw(sha256_base64));
878             if ($debug) {
879                 print "[+] Digest::SHA::VERSION $Digest::SHA::VERSION\n";
880             }
881             $digest = sha256_base64($msg);
882             if ($debug) {
883                 $total_digest = sha256_base64("$msg:$digest");
884             }
885             print "        SHA256 digest:  $digest\n" unless $quiet;
886         }
887     } else {
888         die "[*] Improper digest algorithm, use --help";
889     }
890     return '' if $use_fko_module;
891     return ':' . $digest;
892 }
893
894 sub fko_encrypt() {
895
896     my $fko_err = '';
897
898     if ($gpg_signing_key or $gpg_recipient) {
899         $fko_err = $fko_obj->encryption_type(FKO->FKO_ENCRYPTION_GPG);
900
901         die "[*] FKO gpg encryption error: ", $fko_obj->errstr($fko_err), "\n"
902             if $fko_err;
903
904         $fko_err = $fko_obj->gpg_home_dir($gpg_home_dir);
905         die "[*] FKO could not set gpg home dir: ",
906             $fko_obj->errstr($fko_err), "\n" if $fko_err;
907
908         if ($gpg_signing_key) {
909             $fko_err = $fko_obj->gpg_signer($gpg_signing_key);
910             die "[*] FKO could not set gpg signing key: ",
911                 $fko_obj->errstr($fko_err), "\n" if $fko_err;
912         }
913
914         if ($gpg_recipient) {
915             $fko_err = $fko_obj->gpg_recipient($gpg_recipient);
916             die "[*] FKO could not set gpg recipient key: ",
917                 $fko_obj->errstr($fko_err), "\n" if $fko_err;
918         }
919     }
920
921     $fko_err = $fko_obj->spa_data_final($enc_key);
922     if ($fko_err) {
923         die "[*] FKO encryption error: ", $fko_obj->errstr($fko_err), "\n";
924     }
925
926     unless ($quiet) {
927         if ($digest_type == $SHA256_DIGEST) {
928             print "        SHA256 digest:  ", $fko_obj->spa_digest(), "\n";
929         } elsif ($digest_type == $SHA1_DIGEST) {
930             print "        SHA1 digest:    ", $fko_obj->spa_digest(), "\n";
931         } elsif ($digest_type == $MD5_DIGEST) {
932             print "        MD5 digest:     ", $fko_obj->spa_digest(), "\n";
933         }
934     }
935     if ($debug) {
936         print "[+] FKO digest type: ", $fko_obj->digest_type(), "\n";
937     }
938
939     my $encrypted_msg = $fko_obj->spa_data();
940
941     if ($spa_over_http) {
942         ### change "+" chars to "-", and "/" to "_"
943         $encrypted_msg =~ s|\+|-|g;
944         $encrypted_msg =~ s|\/|_|g;
945     }
946
947     if ($gpg_signing_key or $gpg_recipient) {
948         if ($encrypted_msg =~ /^$gpg_prefix/) {
949             unless ($include_base64_gnupg_prefix) {
950                 print qq|[+] Stripping encoded "$gpg_prefix" prefix from |,
951                     "outgoing encoded SPA packet.\n" if $debug;
952                 ### perl -MMIME::Base64 -e 'print encode_base64("\x85\x02\n")'
953                 ### The 'magic' database (via the 'file') command identifies GnuPG
954                 ### encrypted files as starting with 0x8502
955                 $encrypted_msg =~ s/^$gpg_prefix//;
956             }
957         } else {
958             print
959 "[-] Warning: GnuPG encrypted SPA packet does not begin with: $gpg_prefix\n",
960 "    It is recommend to set GPG_NO_PREFIX_ADD in access.conf on the fwknopd\n",
961 "    server side.\n";
962         }
963     } else {
964         if ($include_salted and $encrypted_msg !~ /^U2FsdGVkX1/) {
965             ### the FKO module does not include the U2FsdGVkX1 string
966             ### so add it if necessary
967             print "[+] Added encoded 'Salted__' prefix (U2FsdGVkX1) to ",
968                 "outgoing encoded SPA packet.\n" if $debug;
969             $encrypted_msg = 'U2FsdGVkX1' . $encrypted_msg;
970         }
971     }
972
973     unless ($include_base64_trailing_equals and $encrypted_msg =~ /=$/) {
974         print "[+] Stripping trailing equals chars from base64 encoding.\n"
975             if $debug;
976         $encrypted_msg =~ s/=*$//;
977     }
978
979     return $encrypted_msg;
980 }
981
982 sub assign_spa_port() {
983
984     if ($rand_port and $cmdl_spa_port != 0) {
985         die "[*] Cannot use --Server-port and --rand-port at the same time";
986     }
987
988     if ($rand_port and $spa_over_http) {
989         die "[*] Cannot use --HTTP and --rand-port at the same time";
990     }
991
992     ### allow for ":<port>" extension to -D arg
993     if ($knock_dst =~ /(.*):(\d+)/) {
994         $knock_dst     = $1;
995         $enc_pcap_port = $2;
996
997         die "[*] Cannot use --rand-port with a manually ",
998             "specified -D <host>:<port>" if $rand_port;
999         die "[*] Cannot use --Server-port with -D <host>:<port>"
1000             if $cmdl_spa_port;
1001     }
1002
1003     if ($spa_over_http) {
1004
1005         ### default to port 80 if the port has not already been updated
1006         ### (via --Server-port or the above IP:PORT notation).
1007         $enc_pcap_port = 80 unless $knock_dst =~ /.*:\d+/;
1008
1009         ### if using an HTTP proxy, allow the http://HOST:PORT notation
1010         ### to determine the port
1011         ### parses all the potential forms of http_proxy
1012                ###FIXME: Is this the best place to parse this?
1013         if ($http_proxy) {
1014             if ($http_proxy =~ m|http://(\S+):(\S+)@(\S+):(\d+)|) {
1015                 if ($http_proxy_user eq '') {
1016                     $http_proxy_user = $1;
1017                 }
1018                 if ($http_proxy_pass eq '') {
1019                     $http_proxy_pass = $2;
1020                 }
1021                 $http_proxy_host = $3;
1022                 $enc_pcap_port   = $4;
1023             } elsif ($http_proxy =~ m|http://(\S+):(\S+)@(\S+)|) {
1024                 if ($http_proxy_user eq '') {
1025                     $http_proxy_user = $1;
1026                 }
1027                 if ($http_proxy_pass eq '') {
1028                     $http_proxy_pass = $2;
1029                 }
1030                 $http_proxy_host = $3;
1031             } elsif ($http_proxy =~ m|http://(\S+):(\d+)|) {
1032                 $http_proxy_host = $1;
1033                 $enc_pcap_port   = $2;
1034             } elsif ($http_proxy =~ m|http://(\S+)|) {
1035                 $http_proxy_host = $1;
1036             } else {
1037                 die "[*] Proxy must begin with 'http://'";
1038             }
1039             if ($http_proxy_host =~ m|/|) {
1040                 die "[*] Proxy host must be a valid hostname";
1041             }
1042         }
1043     }
1044
1045     if ($rand_port) {
1046         ### send the SPA packet over a random port between 10,000 and 65535
1047         $enc_pcap_port = &rand_port();
1048     }
1049
1050     $enc_pcap_port = $cmdl_spa_port if $cmdl_spa_port;
1051
1052     return;
1053 }
1054
1055 sub pcap_GPG_encrypt_msg() {
1056     my $msg = shift;
1057
1058     my $gnupg = GnuPG::Interface->new();
1059
1060     my %gnupg_options = (
1061         'batch' => 1,
1062         'homedir' => $gpg_home_dir,
1063         'no_options' => 1
1064     );
1065
1066     delete $gnupg_options{'batch'} if $gpg_verbose;
1067     delete $gnupg_options{'no_options'} if $gpg_use_options;
1068
1069     $gnupg->options->hash_init(%gnupg_options);
1070
1071     ### if --gpg-default-key is given, then we trust that the user has
1072     ### set the default key with the default-key variable in ~/.gnupg/options
1073     ### and we need to enable options
1074     if ($gpg_default_key) {
1075         delete $gnupg_options{'no_options'}
1076             if defined delete $gnupg_options{'no_options'};
1077     } else {
1078         $gnupg->options->default_key($gpg_signing_key);
1079     }
1080
1081     $gnupg->options->push_recipients($gpg_recipient);
1082
1083     if ($gpg_path) {
1084         ### normally gpg is in the local path, but if not --gpg-path can
1085         ### provide a custom path
1086         $gnupg->call($gpg_path);
1087     }
1088
1089     my ($input, $output, $error, $pw, $status) =
1090         (IO::Handle->new(),
1091         IO::Handle->new(),
1092         IO::Handle->new(),
1093         IO::Handle->new(),
1094         IO::Handle->new());
1095
1096     my $handles = GnuPG::Handles->new(
1097         stdin  => $input,
1098         stdout => $output,
1099         stderr => $error,
1100         passphrase => $pw,
1101         status => $status
1102     );
1103
1104     my $pid;
1105
1106     if ($use_gpg_agent or $gpg_agent_info) {
1107         if ($gpg_agent_info) {
1108             $ENV{'GPG_AGENT_INFO'} = $gpg_agent_info;
1109         }
1110         $pid = $gnupg->sign_and_encrypt('handles' => $handles,
1111             'command_args' => [ qw( --use-agent ) ]);
1112     } else {
1113         $pid = $gnupg->sign_and_encrypt('handles' => $handles);
1114     }
1115
1116     print $pw $enc_key;
1117     close $pw;
1118
1119     print $input $msg;
1120     close $input;
1121
1122     my @ciphertext = <$output>;
1123     close $output;
1124
1125     my @errors = <$error>;
1126     close $error;
1127
1128     waitpid $pid, 0;
1129
1130     my $ctext = '';
1131     if (@ciphertext) {
1132         $ctext = join '', @ciphertext;
1133     }
1134
1135     unless ($ctext) {
1136         print "[*] GnuPG encrypt failed.\n";
1137         unless ($gpg_verbose) {
1138             print "    GnuPG errors:\n";
1139             print for @errors;
1140         }
1141         exit 1;
1142     }
1143
1144     if ($verbose) {
1145         print "[+] Encrypted msg hex dump (" .
1146             length($ctext) . " bytes):\n";
1147         &hex_dump($ctext);
1148     }
1149
1150     my $encoded_msg = encode_base64($ctext, '');
1151
1152     if ($verbose and $debug) {
1153         print "[+] base64-encoded message before stripping identifying chars:\n",
1154             $encoded_msg, "\n";
1155     }
1156
1157     if ($spa_over_http) {
1158         ### change "+" chars to "-", and "/" to "_"
1159         $encoded_msg =~ s|\+|-|g;
1160         $encoded_msg =~ s|\/|_|g;
1161     }
1162
1163     if ($encoded_msg =~ /^$gpg_prefix/) {
1164         unless ($include_base64_gnupg_prefix) {
1165             print qq|[+] Stripping encoded "$gpg_prefix" prefix from |,
1166                 "outgoing encoded SPA packet.\n" if $debug;
1167             ### perl -MMIME::Base64 -e 'print encode_base64("\x85\x02\n")'
1168             ### The 'magic' database (via the 'file') command identifies GnuPG
1169             ### encrypted files as starting with 0x8502
1170             $encoded_msg =~ s/^$gpg_prefix//;
1171         }
1172     } else {
1173         print
1174 "[-] Warning: GnuPG encrypted SPA packet does not begin with: $gpg_prefix\n",
1175 "    It is recommend to set GPG_NO_PREFIX_ADD in access.conf on the fwknopd\n",
1176 "    server side.\n";
1177     }
1178
1179     print "[+] Encrypted message: $encoded_msg\n" if $debug;
1180     return $encoded_msg;
1181 }
1182
1183 sub pcap_Rijndael_encrypt_msg() {
1184     my $msg = shift;
1185
1186     require Crypt::CBC;
1187
1188     if ($debug) {
1189         print "[+] Crypt::CBC::VERSION $Crypt::CBC::VERSION\n";
1190     }
1191
1192     my $cipher = Crypt::CBC->new({
1193         'key'    => $enc_key,
1194         'cipher' => $enc_alg,
1195     });
1196
1197     my $encrypted_msg = $cipher->encrypt($msg);
1198
1199     if ($verbose) {
1200         print "\n[+] Encrypted msg hex dump before base64 encoding (" .
1201             length($encrypted_msg) . " bytes):\n";
1202         &hex_dump($encrypted_msg);
1203     }
1204
1205     my $encoded_msg = encode_base64($encrypted_msg, '');
1206
1207     if ($verbose and $debug) {
1208         print "[+] base64-encoded message before stripping identifying chars:\n",
1209             $encoded_msg, "\n";
1210     }
1211
1212     if ($spa_over_http) {
1213         ### change "+" chars to "-", and "/" to "_"
1214         $encoded_msg =~ s|\+|-|g;
1215         $encoded_msg =~ s|\/|_|g;
1216     }
1217
1218     ### Crypt::CBC adds the string "Salted__" to the beginning of the
1219     ### encrypted text (at least for how we create the cipher object
1220     ### above), so delete the encoded version of this string ("U2FsdGVkX1")
1221     ### before sending on the wire.  The fwknopd server will add this
1222     ### string back in before decrypting.  This makes it harder to write
1223     ### an IDS signature that looks for fwknop traffic (e.g. look for the
1224     ### string "U2FsdGVkX1" over UDP port 62201).
1225     unless ($include_salted) {
1226         print "[+] Stripping encoded 'Salted__' prefix (U2FsdGVkX1) from ",
1227             "outgoing encoded SPA packet.\n" if $debug;
1228         $encoded_msg =~ s/^U2FsdGVkX1//;  ### encoded "Salted__" string
1229     }
1230
1231     print "[+] Encrypted message: $encoded_msg\n" if $debug;
1232     return $encoded_msg;
1233 }
1234
1235 sub pcap_send_encrypted_msg() {
1236     my $msg = shift;
1237
1238     if ($spa_over_http) {
1239         ### make sure that the request begins with "/"
1240         $msg = '/' . $msg unless $msg =~ m|^/|;
1241     }
1242
1243     my $msg_len = length($msg);
1244
1245     if ($msg_len > $max_msg_len) {
1246         die "[*] Message length is too long ($msg_len bytes), ",
1247             "must be less than $max_msg_len bytes";
1248     }
1249
1250     if ($verbose) {
1251         print "\n[+] Packet data:\n\n", $msg, "\n\n" unless $quiet;
1252     }
1253
1254     if ($save_packet_mode) {
1255         print "    Saving packet data to: $save_packet_file\n" unless $quiet;
1256         if ($save_packet_append) {
1257             open F, ">> $save_packet_file" or die "[*] Could not open ",
1258                 "$save_packet_file: $!";
1259         } else {
1260             open F, "> $save_packet_file" or die "[*] Could not open ",
1261                 "$save_packet_file: $!";
1262         }
1263         print F $msg, "\n";
1264         close F;
1265     }
1266
1267     if ($spa_over_http) {
1268
1269         ### SPA over HTTP
1270         &send_spa_packet_over_http($msg, $msg_len);
1271
1272     } elsif ($spa_established_tcp
1273             or ($server_proto =~ /tcp/i and not $spoof_src)) {
1274
1275         ### SPA over established TCP socket - useful for Tor
1276         &send_spa_packet_over_tcp($msg, $msg_len);
1277
1278     } else {
1279
1280         ### check to see if we are supposed to spoof our source address,
1281         ### or use a raw socket.  If not, then we default to sending the
1282         ### SPA packet over a normal UDP socket
1283         my $send_over_raw_socket = '';
1284
1285         if ($server_proto and $server_proto =~ /icmp/i) {
1286             $send_over_raw_socket = 'icmp';
1287         }
1288
1289         if ($spoof_proto) {
1290             $send_over_raw_socket = lc($spoof_proto);
1291         }
1292
1293         ### note that a spoofed source address is not required for sending
1294         ### over a raw socket - if not specified, then the OS will assign
1295         ### the IP of the outgoing interface.
1296         if ($spoof_src) {
1297
1298             unless ($send_over_raw_socket) {
1299                 if ($server_proto =~ /tcp/i) {
1300                     $send_over_raw_socket = 'tcp';
1301                 } else {
1302                     $send_over_raw_socket = 'udp';
1303                 }
1304             }
1305
1306             unless ($spoof_src =~ /$ip_re/) {
1307                 ### resolve to an IP
1308                 my $iaddr = inet_aton($spoof_src)
1309                     or die "[*] Could not resolve $spoof_src to IP.";
1310                 my $addr = inet_ntoa($iaddr)
1311                     or die "[*] Could not resolve $spoof_src to IP.";
1312                 $spoof_src = $addr;
1313             }
1314         }
1315
1316         if ($send_over_raw_socket) {
1317
1318             ### use Net::RawIP to spoof the packets
1319             require Net::RawIP;
1320
1321             if ($debug) {
1322                 print "[+] Net::RawIP::VERSION $Net::RawIP::VERSION\n";
1323             }
1324
1325             if ($send_over_raw_socket eq 'tcp') {
1326                 &send_spa_over_raw_tcp($msg, $msg_len, $spoof_src);
1327             } elsif ($send_over_raw_socket eq 'udp') {
1328                 &send_spa_over_raw_udp($msg, $msg_len, $spoof_src);
1329             } elsif ($send_over_raw_socket eq 'icmp') {
1330                 &send_spa_over_raw_icmp($msg, $msg_len, $spoof_src);
1331             } else {
1332                 die "[*] Unrecognized protocol: $send_over_raw_socket ",
1333                     "for raw socket.";
1334             }
1335
1336         } else {
1337
1338             ### default communication of SPA packet over UDP socket
1339             &send_spa_packet_over_udp($msg, $msg_len);
1340
1341         }
1342     }
1343
1344     unless ($quiet) {
1345         if ($NAT_access_str and $NAT_access_str =~ /($ip_re),(\d+)/) {
1346             my $internal_ip = $1;
1347             my $nat_port    = $2;
1348             print "    Requesting NAT access to $access_str on $internal_ip via ",
1349                 "port $nat_port\n\n";
1350         } else {
1351             print "\n";
1352         }
1353         if ($test_mode) {
1354             print "    --Test-mode enabled, SPA packet not sent.\n\n";
1355         }
1356     }
1357     return;
1358 }
1359
1360 sub send_spa_packet_over_http() {
1361     my ($msg, $msg_len) = @_;
1362
1363     ### default to use the pre-resolution host as the HTTP server to
1364     ### send the SPA packet to.
1365     my $http_host    = $knock_dst_pre_resolve;
1366     my $http_host_ip = $knock_dst;
1367     my $http_proxy_auth_string = '';
1368     if ($http_proxy_host) {
1369
1370         ### if we are sending the SPA packet through a proxy, set the
1371         ### SPA destination IP as the IP of the proxy host, and use the
1372         ### -D arg as part of the end host URL
1373
1374         $http_host = $http_proxy_host;
1375
1376         $knock_dst_pre_resolve =~ s|/$|| if $knock_dst_pre_resolve =~ m|/$|;
1377         $knock_dst_pre_resolve =~ s|http://||
1378             if $knock_dst_pre_resolve =~ m|^http://|;
1379         $knock_dst_pre_resolve =~ s|(.*?)/.*|$1|;
1380
1381         ### this is used as the GET request
1382         $msg = "http://${knock_dst_pre_resolve}${msg}";  ### FIXME, include http?
1383
1384         $http_host    = $http_proxy_host;
1385         $http_host_ip = $http_proxy_host;
1386
1387         unless ($http_host_ip =~ /$ip_re/) {
1388             ### resolve to an IP
1389             my $iaddr = inet_aton($http_host_ip)
1390                 or die "[*] Could not resolve $http_host_ip to an IP.";
1391             my $addr = inet_ntoa($iaddr)
1392                 or die "[*] Could not resolve $http_host_ip to an IP.";
1393             $http_host_ip = $addr;
1394         }
1395         if ($http_proxy_user) {
1396             my $proxy_auth = encode_base64($http_proxy_user . ':' . $http_proxy_pass);
1397             $http_proxy_auth_string = 'Proxy-Authorization: Basic ' .  $proxy_auth . "\r\n";
1398         }
1399     }
1400
1401     print "\n[+] Sending SPA packet over HTTP to ",
1402         "$http_host:$enc_pcap_port...\n    Sending $msg_len ",
1403         "byte message to $http_host over established ",
1404         "tcp/$enc_pcap_port socket...\n"
1405             unless $quiet;
1406
1407     my $http_request = "GET $msg HTTP/1.0\r\n" .
1408         "User-Agent: $ext_resolve_user_agent\r\n" .
1409         "Accept: */*\r\n" .
1410         "Host: $http_host\r\n" .  ### FIXME?
1411         "Connection: Keep-Alive\r\n" .
1412         "$http_proxy_auth_string" .
1413         "\r\n";
1414
1415     print "[+] Sending SPA HTTP request:\n\n$http_request" if $debug;
1416
1417     unless ($test_mode) {
1418         my $sock = IO::Socket::INET->new(
1419             PeerAddr => $http_host_ip,
1420             PeerPort => $enc_pcap_port,
1421             Proto    => 'tcp',
1422             Timeout  => 1
1423         ) or die "[*] Could not acquire TCP/$enc_pcap_port socket ",
1424                 "with $http_host_ip: $!";
1425         if (defined($sock)) {
1426
1427             print $sock $http_request;
1428             recv($sock, my $web_data, $max_resolve_http_recv, 0);
1429             close $sock;
1430
1431             print "\n[+] Closing connection...\n";
1432         } else {
1433             die "[*] Could not build TCP socket.";
1434         }
1435     }
1436     return;
1437 }
1438
1439 sub send_spa_packet_over_tcp() {
1440     my ($msg, $msg_len) = @_;
1441
1442     print "\n[+] Establishing tcp connection to ",
1443         "$knock_dst:$enc_pcap_port...\n    Sending $msg_len ",
1444         "byte message to $knock_dst over established ",
1445         "tcp/$enc_pcap_port socket...\n"
1446             unless $quiet;
1447
1448     unless ($test_mode) {
1449         my $socket = IO::Socket::INET->new(
1450             PeerAddr => $knock_dst,
1451             PeerPort => $enc_pcap_port,
1452             Proto    => 'tcp',
1453             Timeout  => 1
1454         ) or die "[*] Could not acquire TCP/$enc_pcap_port socket ",
1455                 "with $knock_dst: $!";
1456
1457         $socket->send($msg);
1458
1459         print "\n[+] Closing connection...\n";
1460         undef $socket;
1461     }
1462
1463     return;
1464 }
1465
1466 sub send_spa_packet_over_udp() {
1467     my ($msg, $msg_len) = @_;
1468
1469     print "\n[+] Sending $msg_len byte message to $knock_dst ",
1470         "over udp/$enc_pcap_port...\n" unless $quiet;
1471
1472     unless ($client_src_port) {
1473         $client_src_port = &rand_port();
1474     }
1475     unless ($test_mode) {
1476         my $socket = IO::Socket::INET->new(
1477             PeerAddr  => $knock_dst,
1478             PeerPort  => $enc_pcap_port,
1479             LocalPort => $client_src_port,
1480             Proto     => 'udp',
1481             Timeout   => 1
1482         ) or die "[*] Could not acquire UDP socket: $!";
1483
1484         $socket->send($msg);
1485         undef $socket;
1486     }
1487     return;
1488 }
1489
1490 sub send_spa_over_raw_tcp() {
1491     my ($msg, $msg_len) = @_;
1492
1493     unless ($quiet) {
1494         print
1495 "\n[+] Sending $msg_len byte message to $knock_dst over TCP/$enc_pcap_port";
1496         if ($spoof_src) {
1497             print "\n    (spoofed src IP: $spoof_src)";
1498         }
1499         print "...\n";
1500     }
1501
1502     my $rand_src_port = int(rand(65535));
1503     $rand_src_port = 65001 if $rand_src_port > 65535;
1504     $rand_src_port += 1024 if $rand_src_port < 1024;
1505
1506     my $rawpkt = new Net::RawIP({
1507         ip => {
1508             saddr => $spoof_src,
1509             daddr => $knock_dst
1510         },
1511         tcp =>{}});
1512     $rawpkt->set({ ip => {
1513             saddr => $spoof_src,
1514             daddr  => $knock_dst
1515         },
1516         tcp => {
1517             ack => 1,
1518             source => $rand_src_port,
1519             dest   => $enc_pcap_port,
1520             data => $msg
1521         }
1522     });
1523     $rawpkt->send() unless $test_mode;
1524
1525     return;
1526 }
1527
1528 sub send_spa_over_raw_udp() {
1529     my ($msg, $msg_len, $spoof_src) = @_;
1530
1531     unless ($quiet) {
1532         print
1533 "\n[+] Sending $msg_len byte message to $knock_dst over UDP/$enc_pcap_port";
1534         if ($spoof_src) {
1535             print "\n    (spoofed src IP: $spoof_src)";
1536         }
1537         print "...\n";
1538     }
1539
1540     my $rand_src_port = int(rand(65535));
1541     $rand_src_port = 65001 if $rand_src_port > 65535;
1542     $rand_src_port += 1024 if $rand_src_port < 1024;
1543
1544     my $rawpkt = new Net::RawIP({
1545         ip => {
1546             saddr => $spoof_src,
1547             daddr => $knock_dst
1548         },
1549         udp =>{}});
1550     $rawpkt->set({ ip => {
1551             saddr => $spoof_src,
1552             daddr => $knock_dst
1553         },
1554         udp => {
1555             source => $rand_src_port,
1556             dest   => $enc_pcap_port,
1557             data   => $msg,
1558         }
1559     });
1560     $rawpkt->send() unless $test_mode;
1561
1562     return;
1563 }
1564
1565 sub send_spa_over_raw_icmp() {
1566     my ($msg, $msg_len) = @_;
1567
1568     unless ($quiet) {
1569         print "\n[+] Sending $msg_len byte message to $knock_dst over ICMP";
1570         if ($spoof_src) {
1571             print "\n    (spoofed src IP: $spoof_src)";
1572         }
1573         print "...\n";
1574     }
1575
1576     my $rawpkt = new Net::RawIP({
1577         ip => {
1578             saddr => $spoof_src,
1579             daddr => $knock_dst
1580         },
1581         icmp =>{}});
1582     $rawpkt->set({ ip => {
1583             saddr => $spoof_src,
1584             daddr => $knock_dst
1585         },
1586         icmp => {
1587             type => $icmp_type,
1588             code => $icmp_code,
1589             sequence => 0,
1590             data => $msg
1591         }
1592     });
1593     $rawpkt->send() unless $test_mode;
1594
1595     return;
1596 }
1597
1598 sub knock_ports() {
1599     my $ports_aref = shift;
1600
1601     if ($test_mode) {
1602         print "[+] --Test-mode enabled, not sending sequence.\n";
1603         exit 0;
1604     }
1605
1606     print "[+] Sending port knocking sequence to knock server: $knock_dst\n"
1607         unless $quiet;
1608     my $packet_ctr = 0;
1609     for my $href (@$ports_aref) {
1610         my $proto = $href->{'proto'};
1611         my $port  = $href->{'port'};
1612         ### note that we never care if the destination replies with a
1613         ### RST or icmp echo reply (or anything else).  In fact, hopefully
1614         ### the remote firewall is configued to not reply at all
1615         if ($proto eq 'icmp') {
1616             require Net::Ping::External;
1617             Net::Ping::External->import(qw/ping/);
1618
1619             if ($debug) {
1620                 print "[+] Net::Ping::External::VERSION ",
1621                     "$Net::Ping::External::VERSION\n";
1622             }
1623
1624             print "    icmp echo request -> $knock_dst\n";
1625             ping(hostname => "$knock_dst", count => 1, timeout => 1);
1626             sleep $knock_sleep;
1627         } else {
1628             print "    -> $knock_dst $proto/$port (packet: $packet_ctr)\n";
1629             my $socket = IO::Socket::INET->new(
1630                 PeerAddr => $knock_dst,
1631                 PeerPort => $port,
1632                 Proto    => $proto,
1633                 Timeout  => 1
1634             );  ### note there is no "or die" here since we just want to throw
1635                 ### packets on the network
1636             if (defined $socket and $proto eq 'udp') {
1637                 $socket->send('0');  ### have to actually send something for udp
1638                 sleep $knock_sleep;
1639             }
1640             if ($proto eq 'tcp' and $knock_sleep >= 1) {
1641                 sleep $knock_sleep;
1642             }
1643             undef $socket if defined $socket;
1644         }
1645         $packet_ctr++;
1646     }
1647     print "[+] Finished knock sequence.\n";
1648     return;
1649 }
1650
1651 sub encrypt_sequence() {
1652     my $clear_txt = '';
1653     my $checksum = 0;
1654     my @encrypted_seq = ();
1655
1656     require Crypt::CBC;
1657
1658     if ($debug) {
1659         print "[+] Crypt::CBC::VERSION $Crypt::CBC::VERSION\n";
1660     }
1661
1662     my $cipher = Crypt::CBC->new({
1663         'key'    => $enc_key,
1664         'cipher' => $enc_alg,
1665     });
1666
1667     my @octets = split /\./, $enc_allow_ip;
1668
1669     $clear_txt .= chr($_) for @octets;
1670     $checksum += $_ for @octets;
1671
1672     my $proto_num      = 0;
1673     my $enc_allow_port = 0;
1674     if ($access_str =~ /tcp/i) {
1675         $proto_num = 6;
1676         if ($access_str =~ /(\d+)/) {
1677             $enc_allow_port = $1;
1678         }
1679     } elsif ($access_str =~ /udp/i) {
1680         $proto_num = 17;
1681         if ($access_str =~ /(\d+)/) {
1682             $enc_allow_port = $1;
1683         }
1684     } elsif ($access_str =~ /icmp/i) {
1685         $proto_num = 1;
1686         $enc_allow_port = 0;
1687     }
1688
1689     unless ($enc_allow_port) {
1690         die "[*] Must specify port to open."
1691             if $proto_num != 1;
1692     }
1693     my $port_upper_bits = $enc_allow_port;
1694     my $port_lower_bits = $enc_allow_port;
1695
1696     if ($enc_allow_port == 0) {
1697         $port_upper_bits = 0;
1698         $port_lower_bits = 0;
1699     } else {
1700         $port_upper_bits = $port_upper_bits >> 8;
1701         $port_lower_bits = $port_lower_bits % 256;
1702     }
1703
1704     $clear_txt .= chr($port_upper_bits);
1705     $clear_txt .= chr($port_lower_bits);
1706
1707     $checksum += $port_upper_bits;
1708     $checksum += $port_lower_bits;
1709
1710     $clear_txt .= chr($proto_num);
1711     $checksum += $proto_num;
1712
1713     $checksum = $checksum % 256;
1714
1715     $clear_txt .= chr($checksum);
1716
1717     ### append username
1718     ### FIXME: either the checksum should be removed, or it should
1719     ### be applied to the username as well.
1720     my $username = getlogin() || getpwuid($<) || die "[*] Could not ",
1721         "get process username.";
1722
1723     if ($username) {
1724         my @chars = split //, $username;
1725         for my $char (@chars) {
1726             if (length($clear_txt) < $enc_blocksize-1) {
1727                 $clear_txt .= $char;
1728             }
1729         }
1730     }
1731
1732     my @tmp_chars = split //, $clear_txt;
1733     print '[+] Clear-text sequence (' . length($clear_txt) . ' bytes): ';
1734     print ord($_) . ' ' for @tmp_chars;
1735     print "\n";
1736
1737     my $cipher_txt = $cipher->encrypt($clear_txt);
1738     undef $cipher;
1739
1740     @tmp_chars = split //, $cipher_txt;
1741     print '[+] Cipher-text sequence (' . length($cipher_txt) . ' bytes): ';
1742     print ord($_) . ' ' for @tmp_chars;
1743
1744     print "\n    Port offset: $enc_port_offset\n";
1745
1746     my @chars = split //, $cipher_txt;
1747     my $char_ctr = 0;
1748     for my $char (@chars) {
1749         my %hsh;
1750         if ($enc_rotate_proto) {
1751             ### alternate between tcp and udp protocols
1752             if ($char_ctr % 2 == 0) {
1753                 %hsh = ('port' => ord($char) + $enc_port_offset,
1754                     'proto' => 'tcp');
1755             } else {
1756                 %hsh = ('port' => ord($char) + $enc_port_offset,
1757                     'proto' => 'udp');
1758             }
1759         } else {
1760             ### hardcode knock sequence proto as tcp
1761             %hsh = ('port' => ord($char) + $enc_port_offset,
1762                 'proto' => 'tcp');
1763         }
1764         push @encrypted_seq, \%hsh;
1765         $char_ctr++;
1766     }
1767     return \@encrypted_seq;
1768 }
1769
1770 sub resolve_external_ip() {
1771
1772     my $external_ip = '';
1773     my $site_host   = '';
1774     my $url         = '/';
1775
1776     if ($resolve_ip_url) {
1777         die "[*] $resolve_ip_url does not begin with http://"
1778             unless $resolve_ip_url =~ m|http://|i;
1779
1780         if ($resolve_ip_url =~ m|http://(\S+?)/(\S+)|i) {
1781             $site_host = $1;
1782             $url       = "/$2";
1783         } elsif ($resolve_ip_url =~ m|http://(\S+?)/|i) {
1784             $site_host = $1;
1785         } elsif ($resolve_ip_url =~ m|http://(\S+)|i) {
1786             ### there is no trailing slash
1787             $site_host = $1;
1788         } else {
1789             die "[*] Could not get external hostname from $resolve_ip_url";
1790         }
1791     }
1792
1793     print "    Resolving external IP via: $resolve_ip_url\n"
1794         unless $quiet;
1795     my $w_ip_tmp = inet_aton($site_host)
1796         or die "[*] Could not resolve $site_host to an IP.";
1797     my $w_ip = inet_ntoa($w_ip_tmp)
1798         or die "[*] Could not resolve $site_host to an IP.";
1799
1800     my $sock = new IO::Socket::INET(
1801         PeerAddr => $w_ip,
1802         PeerPort => 80,
1803         Proto    => 'tcp',
1804         Timeout  => 7)
1805     or die "[*] Could not open tcp/80 socket with $resolve_ip_url";
1806
1807     if (defined($sock)) {
1808         print $sock "GET $url HTTP/1.0\r\n",
1809             "Host: $site_host\r\n",
1810             "User-Agent: $ext_resolve_user_agent\r\n",
1811             "Accept: */*\r\n",
1812             "Connection: Keep-Alive\r\n\r\n";
1813         recv($sock, my $web_data, $max_resolve_http_recv, 0);
1814         close $sock;
1815         $web_data =~ s/[^\w\.]/ /g;
1816         if ($debug) {
1817             print "[+] Web server data from: $resolve_ip_url\n",
1818                 $web_data, "\n";
1819         }
1820         if ($resolve_ip_url =~ /whatismyip/i) {
1821             if ($web_data =~ /WhatIsMyIP\.com\s+-\s+($ip_re)\b/i) {
1822                 $external_ip = $1;
1823             }
1824         }
1825         unless ($external_ip) {
1826             ### greedy match to the last instance of a matching
1827             ### IP regex so that we get past any HTTP header info
1828             ### that might happen to match the IP regex
1829             if ($web_data =~ /\b($ip_re)\b/) {
1830                 $external_ip = $1;
1831             }
1832         }
1833     }
1834     unless ($external_ip) {
1835         print "[*] Could not extract external IP from $resolve_ip_url\n";
1836         unless ($debug) {
1837             print
1838 "    You might try running with --debug and looking at the response from\n",
1839 "    the webserver. Maybe it is trying to set a cookie?\n";
1840         }
1841         exit 1;
1842     }
1843
1844     print "    Got external address: $external_ip\n\n" unless $quiet;
1845     return $external_ip;
1846 }
1847
1848 sub get_key() {
1849
1850     if ($gpg_signing_key or $gpg_default_key) {
1851
1852         ### load the GnuPG::Interface module
1853         require GnuPG::Interface;
1854
1855         if ($debug) {
1856             print "[+] GnuPG::Interface::VERSION ",
1857                 "$GnuPG::Interface::VERSION\n";
1858         }
1859
1860         ### we don't need a password if we are going to acquire
1861         ### a password from gpg-agent
1862         return if $use_gpg_agent;
1863     }
1864
1865     if ($get_key_file) {
1866         ### get the encryption key from file
1867         open F, "< $get_key_file" or die "[*] Could not open ",
1868             "$get_key_file: $!";
1869         my @lines = <F>;
1870         close F;
1871         for my $line (@lines) {
1872             chomp $line;
1873             if ($line =~ /$knock_dst:\s*(.*)/) {
1874                 $enc_key = $1;
1875                 last;
1876             } elsif ($line =~ /$knock_dst_pre_resolve:\s*(.*)/) {
1877                 $enc_key = $1;
1878                 last;
1879             }
1880         }
1881         unless ($enc_key) {
1882             die
1883 "[*] Could not read encryption key/password for $knock_dst_pre_resolve\n",
1884 " from $get_key_file; fwknop expects the following format:\n",
1885 "$knock_dst_pre_resolve: <KEY/password>\n";
1886         }
1887     } else {
1888         if ($gpg_signing_key or $gpg_default_key) {
1889             print
1890 "[+] Enter the GnuPG password for signing key: $gpg_signing_key\n\n"
1891             unless $quiet;
1892         } else {
1893             print
1894 "[+] Enter an encryption key. This key must match a key in the file\n",
1895 "    /etc/fwknop/access.conf on the remote system.\n\n" unless $quiet;
1896         }
1897         my $try = 0;
1898         my $max_tries = 20;
1899         ReadMode('noecho');
1900         KEY: while (1) {
1901             $try++;
1902             if ($try >= $max_tries) {
1903                 ReadMode('normal');
1904                 die "[*] Exceeded $max_tries tries to read valid password.";
1905             }
1906             if ($gpg_signing_key or $gpg_default_key) {
1907                 print "GnuPG signing password: ";
1908             } else {
1909                 print "Encryption Key: ";
1910             }
1911             my $ans = ReadLine(0);
1912             next KEY unless defined $ans;
1913             next KEY unless $ans =~ /\S/;
1914             chomp $ans;
1915             if ($gpg_signing_key or $gpg_default_key) {
1916                 $enc_key = $ans;
1917                 last KEY;
1918             } else {
1919                 if (length($ans) >= 8) {
1920                     $enc_key = $ans;
1921                     last KEY;
1922                 } else {
1923                     ReadMode('normal');
1924                     die "\n[-] The symmetric key must be at least ",
1925                         "8 characters long.\n";
1926                 }
1927             }
1928         }
1929         ReadMode('normal');
1930         print "\n";
1931
1932         die "[*] Could not read encryption key from STDIN.  Exiting."
1933             unless $enc_key;
1934     }
1935     unless ($gpg_signing_key or $gpg_default_key) {
1936         unless (length($enc_key) >= 8) {
1937             die "\n[-] The symmetric key must be at least ",
1938                 "8 characters long.\n";
1939         }
1940         ### pad out to the key size
1941         while (length($enc_key) < $enc_keysize) {
1942             $enc_key .= '0';
1943         }
1944     }
1945     return;
1946 }
1947
1948 sub import_perl_modules() {
1949
1950     my $mod_paths_ar = &get_mod_paths();
1951
1952     if ($#$mod_paths_ar > -1) {  ### /usr/lib/fwknop/ exists
1953         push @$mod_paths_ar, @INC;
1954         splice @INC, 0, $#$mod_paths_ar+1, @$mod_paths_ar;
1955     }
1956
1957     if ($debug) {
1958         print "[+] import_perl_modules(): The \@INC array:\n";
1959         print "$_\n" for @INC;
1960     }
1961
1962     ### see if the FKO module is installed
1963     unless ($skip_fko_module) {
1964         eval { require FKO };
1965         unless ($@) {
1966             $use_fko_module = 1;
1967             if ($debug or $test_fko_exists) {
1968                 print "[+] Using FKO module.\n";
1969             }
1970             exit 0 if $test_fko_exists;
1971         }
1972     }
1973
1974     require Term::ReadKey;
1975     Term::ReadKey->import(qw/ReadMode ReadLine/);
1976
1977     print "[+] Term::ReadKey::VERSION $Term::ReadKey::VERSION\n",
1978         if $debug;
1979
1980     return;
1981 }
1982
1983 sub get_mod_paths() {
1984
1985     my @paths = ();
1986
1987     unless (-d $lib_dir) {
1988         my $dir_tmp = $lib_dir;
1989         $dir_tmp =~ s|lib/|lib64/|;
1990         if (-d $dir_tmp) {
1991             $lib_dir = $dir_tmp;
1992         } else {
1993             return [];
1994         }
1995     }
1996
1997     opendir D, $lib_dir or die "[*] Could not open $lib_dir: $!";
1998     my @dirs = readdir D;
1999     closedir D;
2000
2001     push @paths, $lib_dir;
2002
2003     for my $dir (@dirs) {
2004         ### get directories like "/usr/lib/fwknop/x86_64-linux"
2005         next unless -d "$lib_dir/$dir";
2006         push @paths, "$lib_dir/$dir"
2007             if $dir =~ m|linux| or $dir =~ m|thread|
2008                 or (-d "$lib_dir/$dir/auto");
2009     }
2010     return \@paths;
2011 }
2012
2013 sub import_shared_sequence() {
2014     my $connect_file = '';
2015     my @lines = ();
2016     if ($user_rc_file and -e $user_rc_file) {
2017         $connect_file = $user_rc_file;
2018     } elsif (-e "$homedir/.fwknoprc") { ### this is the default unless -f was given
2019         $connect_file = "$homedir/.fwknoprc";
2020     } else {
2021         unless ($user_rc_file) {
2022             print "[+] Creating fwknop rc file: $homedir/.fwknoprc\n",
2023                 "    This file is used only to define shared knock sequences.  ",
2024                 "If you want\n    to send an encrypted sequence, use the ",
2025                 "--encrypt argument.\n\n[+] To send a shared sequence you will ",
2026                 "first need to define\n    the sequence in $homedir/.fwknoprc\n";
2027             open F, "> $homedir/.fwknoprc" or
2028                 die "[*] Could not open $homedir/.fwknoprc: $!";
2029 print F "# Shared knock sequence config file for fwknop.  This file adheres to the\n",
2030     "# following format:\n# <knockdst>: <proto/port>, ..., <proto/port>.  See the example ",
2031     "# below:\n\n# 192.168.10.2: tcp/5501, tcp/5502, udp/1001, tcp/5504\n\n";
2032             close F;
2033             exit 1;
2034         }
2035     }
2036
2037     open F, "< $connect_file" or die "[*] Could not open ",
2038         "$connect_file: $!";
2039     @lines = <F>;
2040     close F;
2041
2042     ### parse out the knock sequence
2043     my @knock_sequence = ();
2044     my $dst = '';
2045     my $found_dst = 0;
2046     for my $line (@lines) {
2047         chomp $line;
2048         next unless $line =~ /\S/;
2049         next if $line =~ /^\s*#/;
2050         if ($line =~ /^\s*(\S+):\s*(.*)/) {
2051             my $dst   = $1;
2052             my $ports = $2;
2053             next unless $dst;
2054             next unless $dst eq $knock_dst;
2055             my @ports_arr = split /\s*\,\s*/, $ports;
2056             next unless @ports_arr and $#ports_arr > 0;
2057             $found_dst = 1;
2058             for my $port (@ports_arr) {
2059                 my %hsh = ();
2060                 if ($port =~ m|tcp/(\d+)|) {
2061                     %hsh = ('port' => $1, 'proto' => 'tcp');
2062                 } elsif ($port =~ m|udp/(\d+)|) {
2063                     %hsh = ('port' => $1, 'proto' => 'udp');
2064                 } elsif ($port =~ m|icmp|) {
2065                     %hsh = ('port' => -1, 'proto' => 'icmp');
2066                 }
2067                 next unless %hsh;
2068                 push @knock_sequence, \%hsh;
2069             }
2070         }
2071     }
2072     die "[*] Could not find destination: $knock_dst in $connect_file"
2073         unless $found_dst;
2074     die "[*] No port sequence defined for $knock_dst in $connect_file"
2075         unless @knock_sequence;
2076     return \@knock_sequence;
2077 }
2078
2079 sub get_homedir() {
2080     my $uid = $<;
2081     if ($cmdl_homedir) {
2082         $homedir = $cmdl_homedir;
2083     } else {
2084         ### prefer homedir specified in /etc/passwd (if it exists)
2085         if (-e '/etc/passwd') {
2086             open P, "< /etc/passwd" or die "[*] Could not open /etc/passwd. ",
2087                 "Exiting.\n";
2088             my @lines = <P>;
2089             close P;
2090             for my $line (@lines) {
2091                 ### mbr:x:222:222:Michael Rash:/home/mbr:/bin/bash
2092                 chomp $line;
2093                 if ($line =~ /^(?:.*:){2}$uid:(?:.*:){2}(\S+):/) {
2094                     $homedir = $1;
2095                     last;
2096                 }
2097             }
2098         }
2099         unless ($homedir and -d $homedir) {
2100             $homedir = $ENV{'HOME'} if defined $ENV{'HOME'};
2101         }
2102     }
2103     die '[*] Could not determine homedir, use --Home option.'
2104         unless ($homedir and -d $homedir);
2105
2106     if ($gpg_signing_key or $gpg_recipient) {
2107         $gpg_home_dir = "$homedir/.gnupg" unless $gpg_home_dir;
2108     }
2109
2110     return $homedir;
2111 }
2112
2113 sub handle_server_auth_method() {
2114     if (lc($server_auth_method) eq 'crypt') {
2115         ReadMode('noecho');
2116         while (1) {
2117             $quiet == 1 ? print "UNIX crypt() password: "
2118                 : print "    UNIX crypt() password: ";
2119             my $ans = ReadLine(0);
2120             chomp $ans;
2121             next unless $ans =~ /\S/;
2122             $server_auth_crypt_pw = $ans;
2123             last;
2124         }
2125         ReadMode('normal');
2126         print "\n";
2127         return;
2128     }
2129     die "[*] --Server-auth must be 'crypt'";
2130 }
2131
2132 sub save_args() {
2133     my $save_file  = "$homedir/.fwknop.run";
2134     my $hosts_file = "$homedir/.fwknop.hosts";
2135
2136     open S, "> $save_file" or die "[*] Could not open $save_file";
2137     print S "@args_cp\n";
2138     close S;
2139
2140     if ($save_destination) {
2141         open D, "> $homedir/.fwknop.save"
2142             or die "[*] Could not open $homedir/.fwknop.save";
2143         print D "@args_cp\n";
2144         close D;
2145     }
2146
2147     my @host_lines = ();
2148     my $matched_dst = 0;
2149     if (-e $hosts_file) {
2150         open F, "< $hosts_file" or die "[*] Could not open $hosts_file";
2151         while (<F>) {
2152             if (/-(k|D)\S*\s+$knock_dst_pre_resolve/) {
2153                 ### if an older command is for the same knock destination
2154                 ### then substitute the current command (doesn't yet support
2155                 ### multiple commands per knock destination since we would
2156                 ### need a way to select among them)
2157                 push @host_lines, "@args_cp\n";
2158                 $matched_dst = 1;
2159             } else {
2160                 push @host_lines, $_;
2161             }
2162         }
2163         close F;
2164     }
2165     push @host_lines, "@args_cp\n" unless $matched_dst;
2166
2167     open H, "> $hosts_file" or die "[*] Could not open $hosts_file";
2168     print H for @host_lines;
2169     close H;
2170     return;
2171 }
2172
2173 sub handle_command_line() {
2174
2175     ### make Getopts case sensitive
2176     Getopt::Long::Configure('no_ignore_case');
2177
2178     die "[*] Use --help for usage information.\n" unless GetOptions(
2179         'Server-port=i'  => \$cmdl_spa_port,
2180         'Server-mode=s'  => \$server_mode,
2181         'Server-cmd=s'   => \$cmdline_pcap_cmd,
2182         'Server-proto=s' => \$server_proto,
2183         'Server-auth=s'  => \$server_auth_method,
2184         'Spoof-src=s'    => \$spoof_src,
2185         'icmp-type=i'    => \$icmp_type,
2186         'icmp-code=i'    => \$icmp_code,
2187         'rand-port'      => \$rand_port,
2188         'NAT-rand-port'  => \$NAT_rand_port,
2189         'NAT-local'      => \$NAT_local,
2190         'NAT-access=s'   => \$NAT_access_str,
2191         'Max-packet-size=i' => \$max_msg_len,
2192         'Max-resolve-http-size=i' => \$max_resolve_http_recv,
2193         'Source-port=i'  => \$client_src_port,
2194         'Spoof-user=s'   => \$spoof_username,
2195         'Spoof-proto=s'  => \$spoof_proto,
2196         'Save-packet'    => \$save_packet_mode,
2197         'Save-packet-file=s' => \$save_packet_file,
2198         'Save-packet-append' => \$save_packet_append,
2199         'Save-dst'       => \$save_destination,
2200         'user-rc=s'      => \$user_rc_file,
2201         'knock-dst=s'    => \$knock_dst,
2202         'Destination=s'  => \$knock_dst,
2203         'time-offset-plus=s'  => \$time_offset_plus,
2204         'time-offset-minus=s' => \$time_offset_minus,
2205         'gpg-signing-key=s' => \$gpg_signing_key,
2206         'gpg-recipient=s'   => \$gpg_recipient,
2207         'gpg-default-key'   => \$gpg_default_key,
2208         'gpg-home-dir=s'    => \$gpg_home_dir,
2209         'gpg-verbose'       => \$gpg_verbose,
2210         'gpg-agent'         => \$use_gpg_agent,
2211         'gpg-agent-info=s'  => \$gpg_agent_info,
2212         'gpg-no-options'    => \$gpg_no_options,
2213         'gpg-use-options'   => \$gpg_use_options,
2214         'gpg-prefix=s'      => \$gpg_prefix,
2215         'gpg-path=s'        => \$gpg_path,
2216         'quiet'             => \$quiet,
2217         'Forward-access=s'  => \$NAT_access_str,
2218         'TCP-sock'       => \$spa_established_tcp,
2219         'HTTP'           => \$spa_over_http,
2220         'HTTP-proxy:s'      => \$http_proxy, # the :s indicates that the argument is optional
2221         'HTTP-proxy-user=s' => \$http_proxy_user,
2222         'HTTP-proxy-password=s' => \$http_proxy_pass,
2223         'HTTP-user-agent=s' => \$ext_resolve_user_agent,
2224         'Access=s'       => \$access_str,
2225         'fw-timeout=i'   => \$cmdl_fw_timeout,
2226         'allow-IP=s'     => \$enc_allow_ip,
2227         'digest-alg=s'   => \$cmdl_digest_alg,
2228         'source-IP'      => \$enc_source_ip,
2229         'rotate-proto'   => \$enc_rotate_proto,
2230         'offset=i'       => \$cmdline_offset,
2231         'time-delay=i'   => \$knock_sleep,
2232         'test-FKO-exists' => \$test_fko_exists,
2233         'last-cmd'       => \$run_last_args,
2234         'no-save-args'   => \$no_save_last_args,
2235         'no-FKO-module'  => \$skip_fko_module,
2236         'Last-host=s'    => \$run_last_host,
2237         'Show-last-cmd'  => \$show_last_cmd,
2238         'Show-host-cmd=s' => \$show_last_host_cmd,
2239         'Resolve-external-IP' => \$resolve_external_ip,
2240         'whatismyip'     => \$resolve_external_ip, # for backwards compatibility
2241         'URL=s'          => \$resolve_ip_url,
2242         'User-agent=s'   => \$ext_resolve_user_agent,
2243         'get-key=s'      => \$get_key_file,
2244         'Home-dir=s'     => \$cmdl_homedir,
2245         'Include-salted' => \$include_salted,
2246         'Include-equals' => \$include_base64_trailing_equals,
2247         'Include-gpg-prefix' => \$include_base64_gnupg_prefix,
2248         'Test-mode'      => \$test_mode,
2249         'Lib-dir=s'      => \$lib_dir,
2250         'LC_ALL=s'       => \$locale,
2251         'locale=s'       => \$locale,
2252         'no-LC_ALL'      => \$no_locale,
2253         'no-locale'      => \$no_locale,
2254         'debug'          => \$debug,
2255         'verbose'        => \$verbose,
2256         'Version'        => \$print_version,
2257         'help'           => \$print_help
2258     );
2259
2260     ### run a few minor checks against the supplied args
2261     &validate_command_line();
2262
2263     ### if HTTP_proxy is specified, but not explicitly set, get it from the env variable
2264     if (defined $http_proxy and $http_proxy eq ''){
2265         $http_proxy = $ENV{'http_proxy'};
2266     }
2267
2268     return;
2269 }
2270
2271 sub run_last_cmdline() {
2272
2273     my $found_file = 0;
2274
2275     for my $save_file ("$homedir/.fwknop.save", "$homedir/.fwknop.run") {
2276         next unless -e $save_file;
2277         $found_file = 1;
2278
2279         open S, "< $save_file" or die "[*] Could not open $save_file: $!";
2280         my $arg_line = <S>;
2281         close S;
2282         chomp $arg_line;
2283
2284         if ($show_last_cmd) {
2285             print "[+] Last fwknop client command line: $arg_line\n";
2286             exit 0;
2287         }
2288         print "[+] Running with last command line args: $arg_line\n"
2289             unless $quiet;
2290         @ARGV = split /\s+/, $arg_line;
2291
2292         ### run GetOpt() to get command line args
2293         &handle_command_line();
2294
2295         last;
2296     }
2297
2298     unless ($found_file) {
2299         die "[*] fwknop argument save files (~/.fwknop.save and ",
2300             "~/.fwknop.run) not found.";
2301     }
2302     return;
2303 }
2304
2305 sub run_last_host_cmdline() {
2306
2307     my $found_file = 0;
2308     my $found_host = 0;
2309     for my $save_file ("$homedir/.fwknop.save", "$homedir/.fwknop.hosts") {
2310         next unless -e $save_file;
2311         $found_file = 1;
2312
2313         my $arg_line = '';
2314         open H, "< $save_file" or die "[*] Could not open $save_file: $!";
2315         while (<H>) {
2316             if (/-(k|D)\S*\s+$run_last_host/) {
2317                 $arg_line = $_;
2318                 last;
2319             }
2320         }
2321         close H;
2322
2323         if ($arg_line) {
2324             chomp $arg_line;
2325
2326             if ($show_last_host_cmd) {
2327                 print "[+] Last command run for host: $show_last_host_cmd\n",
2328                     "    $arg_line\n";
2329                 exit 0;
2330             }
2331             print "[+] Running with last command line args: $arg_line\n"
2332                 unless $quiet;
2333             @ARGV = split /\s+/, $arg_line;
2334
2335             ### run GetOpt() to get comand line args
2336             &handle_command_line();
2337
2338             $found_host = 1;
2339             last;
2340         }
2341     }
2342
2343     unless ($found_file) {
2344         die "[*] fwknop argument save files (~/.fwknop.save and ",
2345             "~/.fwknop.hosts) not found.";
2346     }
2347
2348     unless ($found_host) {
2349         print "[-] No matching destination host in ~/.fwknop.save ",
2350             "or ~/.fwknop.hosts\n";
2351     }
2352     return;
2353 }
2354
2355 sub validate_access_str() {
2356     $access_str = lc($access_str);
2357     my @ports = split /,/, $access_str;
2358     for my $str (@ports) {
2359         unless ($str =~ m|(\D+)/(\d+)|) {
2360             die "[*] -A format is: <proto>/<port>,...,<proto>/<port>\n",
2361                 "    e.g.: tcp/22,udp/53,icmp/0";
2362         }
2363     }
2364     return;
2365 }
2366
2367 sub validate_NAT_access_str() {
2368     $NAT_access_str = lc($NAT_access_str);
2369
2370     if ($NAT_rand_port) {
2371
2372         unless ($selected_random_nat_port) {
2373             $NAT_access_str =~ s/,\d+$//;
2374             $NAT_access_str =~ s/:\d+$//;
2375
2376             unless ($NAT_access_str =~ /^$ip_re$/) {
2377                 die "[*] Must specify '<internal_IP>'";
2378             }
2379
2380             ### append a random destination port (between 10,000
2381             ### and 65535); this is the port number that will be
2382             ### used on the SSH command line
2383             $NAT_access_str .= ',' . &rand_port();
2384         }
2385
2386     } else {
2387         unless ($NAT_access_str =~ /^$ip_re,\d+$/
2388                 or $NAT_access_str =~ /^$ip_re:\d+$/) {
2389             die "[*] Must specify '<internal_IP>:<external_port>'";
2390         }
2391     }
2392
2393     ### change ":" to "," for the fwknopd server (which uses colons
2394     ### to separate SPA packet fields, but colons are a better
2395     ### syntax for the fwknop command line)
2396     $NAT_access_str =~ s/:/,/;
2397     return;
2398 }
2399
2400 sub set_digest_type() {
2401     if ($cmdl_digest_alg =~ /sha256/i) {
2402         $digest_type = $SHA256_DIGEST;
2403     } elsif ($cmdl_digest_alg =~ /sha1/i) {
2404         $digest_type = $SHA1_DIGEST;
2405     } elsif ($cmdl_digest_alg =~ /md5/i) {
2406         $digest_type = $MD5_DIGEST;
2407     } else {
2408         die "[*] --digest-alg can accept one of MD5, SHA1, or SHA256";
2409     }
2410     return;
2411 }
2412
2413 sub time_offset() {
2414     my $str = shift;
2415     my $offset = 0;
2416
2417     if ($str =~ /(\d+)/) {
2418         $offset = $1;
2419     } else {
2420         die "[*] Must specify a value like 60min";
2421     }
2422     if ($str =~ /min/i) {
2423         $offset *= 60;
2424     } elsif ($str =~ /hour/i) {
2425         $offset *= 60 * 60;
2426     } elsif ($str =~ /day/i) {
2427         $offset *= 60 * 60 * 24;
2428     } elsif ($str =~ /sec/i) {
2429         ### no action
2430     } else {
2431         ### default to minutes
2432         $offset *= 60;
2433     }
2434     return $offset;
2435 }
2436
2437 sub hex_dump() {
2438     my $data = shift;
2439
2440     my @chars = split //, $data;
2441     my $ctr = 0;
2442     my $ascii_str = '';
2443     for my $char (@chars) {
2444         if ($ctr % 16 == 0) {
2445             print " $ascii_str\n" if $ascii_str;
2446             printf "        0x%.4x:  ", $ctr;
2447             $ascii_str = '';
2448         }
2449         printf "%.2x", ord($char);
2450
2451         if ((($ctr+1) % 2 == 0) and ($ctr % 16 != 0)) {
2452             print ' ';
2453         }
2454
2455         if ($char =~ /[^\x20-\x7e]/) {
2456             $ascii_str .= '.';
2457         } else {
2458             $ascii_str .= $char;
2459         }
2460         $ctr++;
2461     }
2462     if ($ascii_str) {
2463         my $remainder = 1;
2464         if ($ctr % 16 != 0) {
2465             $remainder = 16 - $ctr % 16;
2466             if ($remainder % 2 == 0) {
2467                 $remainder = 2*$remainder + int($remainder/2) + 1;
2468             } else {
2469                 $remainder = 2*$remainder + int($remainder/2) + 2;
2470             }
2471         }
2472         print ' 'x$remainder, $ascii_str;
2473     }
2474     print "\n\n";
2475     return;
2476 }
2477
2478 sub rand_port() {
2479     return int(rand($max_port - $min_port)) + $min_port;
2480 }
2481
2482 sub validate_command_line() {
2483     die "[*] Cannot run in both --quiet and --verbose modes simultaneously"
2484         if $quiet and $verbose;
2485
2486     die "[*] Must also specify a GnuPG signing key with --gpg-signing-key or\n",
2487         "    use --gpg-default-key to use a default key (specified in\n",
2488         "    ~/.gnupg/options with the default-key variable).\n"
2489         if ($gpg_recipient and (not $gpg_default_key and not $gpg_signing_key));
2490
2491     die "[*] Must specify a GnuPG recipient key (on the fwknopd side) with\n",
2492         "    --gpg-recipient"
2493         if (($gpg_default_key or $gpg_signing_key) and not $gpg_recipient);
2494
2495     die "[*] Cannot spoof source address for a real TCP socket."
2496         if ($spoof_src and $spa_established_tcp);
2497
2498     die "[*] Server auth method not supported in NAT access mode.\n"
2499         if $server_auth_method and $NAT_access_str;
2500
2501     if ($gpg_path) {
2502         die "[*] $gpg_path does not exist." unless -e $gpg_path;
2503         die "[*] $gpg_path not executable." unless -x $gpg_path;
2504     }
2505
2506     if ($gpg_no_options) {
2507         print "[-] Options are disabled by default, so --gpg-no-options ",
2508             "is not used.\n";
2509     }
2510
2511     ### if $ENV{'http_proxy'} is to be used, $http_proxy will be '' at this point
2512     $spa_over_http = 1 if defined $http_proxy;
2513
2514     return;
2515 }
2516
2517 sub usage() {
2518     my $exit_status = shift;
2519     print <<_HELP_;
2520
2521 fwknop; Single Packet Authorization client
2522
2523 [+] Version: $version (file revision: $rev_num)
2524     By Michael Rash (mbr\@cipherdyne.org)
2525     URL: http://www.cipherdyne.org/fwknop/
2526
2527 Usage: fwknop -A <port list> [-s|-R|-a] -D <spa_server> [options]
2528
2529 Options:
2530     -A, --Access  <port list>  - Provide a list of ports/protocols to open
2531                                  on the server. The format is
2532                                  "<proto>/<port>...<proto>/<port>". E.g.
2533                                  "tcp/22,udp/53".
2534     -D, --Destination <IP>     - The IP address of the fwknopd server (the
2535                                  IP want to connect to).
2536     --last-cmd                 - Run the fwknop with the same command line
2537                                  arguments as in the previous invocation.
2538                                  The args are stored in ~/fwknop.run.
2539     --Last-host <host>         - Run last command line arguments for <host>.
2540     --gpg-signing-key <key ID> - ID for key used to sign GnuPG encrypted
2541                                  message (e.g. "0xABCD1234").
2542     --gpg-recipient <recip>    - Recipient of GnuPG encrypted message.
2543     --gpg-default-key          - Use the key that GnuPG defines as the
2544                                  default (i.e. the key that is specified
2545                                  by the default-key option in
2546                                  ~/.gnupg/options).
2547     --gpg-home-dir <dir>       - Path to GnuPG home dir (e.g.
2548                                  /home/user/.gnupg).
2549     --gpg-agent                - Acquire GnuPG signing password from a
2550                                  running gpg-agent.
2551     --gpg-agent-info <info>    - Specify the value for the GPG_AGENT_INFO
2552                                  environment variable as returned by
2553                                  'gpg-agent --daemon'.
2554     --gpg-verbose              - Display all output from GnuPG process.
2555     --gpg-use-options          - In GnuPG mode, instruct GnuPG to use the
2556                                  local ~/.gnupg/options file for config
2557                                  parameters (this is disabled by default).
2558     --gpg-prefix <bytes>       - Change the bytes for the expected GnuPG
2559                                  prefix from $gpg_prefix to the specified string.
2560     --gpg-path <path>          - Specify the path to the gpg command (not
2561                                  usually necessary if gpg is in your path).
2562     -a, --allow-IP <IP>        - IP to instruct the remote fwknop server to
2563                                  allow through the firewall ruleset.
2564     -s, --source-IP            - Inform the destination fwknop server to use
2565                                  the source address from which the SPA
2566                                  packet originates (useful for
2567                                  authenticating to the SPA server from
2568                                  behind a NAT device). Note that the -w
2569                                  option should really be used instead.
2570     -F, --Forward-access <NAT> - Access an internal server (say, SSH) by
2571                                  instructing the remote fwknopd instance to
2572                                  build inbound DNAT rules. The format of the
2573                                  argument is <InternalIP>,<Port> where
2574                                  InternalIP is the internal system and Port
2575                                  is the port number that will be forwarded.
2576     -R, --Resolve-external-IP  - Resolve client IP via the
2577                                  http://www.whatismyip.org/ website. This is
2578                                  useful if fwknop is deployed on an internal
2579     -w, --whatismyip           - (Synonym for --Resolve-external-IP option).
2580     --URL <external IP URL>    - Specify a URL from which to determine the
2581                                  external IP (the default is
2582                                  http://www.whatismyip.org/).
2583     --User-agent <string>      - Specify the user agent string to use when
2584                                  resolving IP via http://www.whatismyip.org
2585                                  (must use the -R option). The default user
2586                                  agent is: $ext_resolve_user_agent
2587     -f, --fw-timeout <seconds> - Specify the time the port will remain open
2588                                  on the server (requires
2589                                  PERMIT_CLIENT_TIMEOUT in access.conf on the
2590                                  fwknopd server side).
2591     --Include-salted           - Include the encoded "Salted__" prefix; this
2592                                  is only necessary for older versions of the
2593                                  fwknopd server (< 1.9.2).
2594     --Include-equals           - Include the trailing "=" chars used by
2595                                  base64 encoding scheme; this is only
2596                                  necessary for older versions of the fwknopd
2597                                  server (< 1.9.6).
2598     --Include-gpg-prefix       - Include the base64-encoded $gpg_prefix prefix that
2599                                  GnuPG includes by default; this is only
2600                                  necessary for older versions of the fwknopd
2601                                  server (< 1.9.6).
2602     --Save-dst                 - Save the command line args for this
2603                                  invocation against the destination to the
2604                                  special file ~/.fwknop.save (this file
2605                                  provides a priority location that is only
2606                                  overwritten with --Save-dst and is useful
2607                                  for an fwknop client command that you want
2608                                  to always preserve).
2609     --Save-packet              - Save a copy of an encrypted SPA packet to
2610                                  to a file (~/fwknop_save_packet.<pid> by
2611                                  default).
2612     --Save-packet-file <file>  - Specify the path to the file where the
2613                                  encrypted SPA packet is stored when the
2614                                  --Save-packet argument is used.
2615     --Save-packet-append       - Append a newly generated SPA packet to the
2616                                  --Save-packet-file instead of overwriting
2617                                  an existing file.  This is useful for
2618                                  creating lots of SPA packets for testing
2619                                  randomness and encryption properties.
2620     --Source-port <port>       - Fix a specific source port for outgoing SPA
2621                                  packets.  This is not usually necessary,
2622                                  and the fwknop client randomizes its source
2623                                  port by default.
2624     --Server-port <port>       - Specify the port number to which to send
2625                                  the single authentication packet (this is
2626                                  only used for an fwknop server that is
2627                                  operating in pcap mode).
2628     --Server-mode <mode>       - Run in legacy port knocking mode ("mode" =
2629                                  "knock" or "shared").
2630     --Server-cmd <cmd>         - Specify a complete command that an fwknop
2631                                  server should execute (as root).
2632     --Server-auth <method>     - Provide additional authentication
2633                                  information that the fwknopd server can
2634                                  apply (such as integration with crypt()).
2635     --Spoof-src <IP>           - Spoof the source IP address (requires
2636                                  fwknop to be run as root).
2637     --Spoof-user <username>    - Supply a non-root username when spoofing
2638                                  the source address.
2639     --Spoof-proto <protocol>   - Send authentication packet over the
2640                                  specified protocol (tcp, udp, or icmp)
2641                                  when spoofing the source address.
2642     --icmp-type <type>         - Set the ICMP type when sending SPA packets
2643                                  over spoofed ICMP packets (default is
2644                                  $icmp_type for echo-request).
2645     --icmp-code <code>         - Set the ICMP code when sending SPA packets
2646                                  over spoofed ICMP packets (default is
2647                                  $icmp_code for echo-request).
2648     -r, --rotate-proto         - Rotate protocol (tcp and udp only) for
2649                                  encrypted sequences.
2650     --Max-packet-size <bytes>  - Maximum size of outbound SPA packets - the
2651                                  default is $max_msg_len bytes.
2652     --offset <port>            - Specify port offset to use when run in
2653                                  --encrypt knock mode.  The default is
2654                                  $enc_port_offset.
2655     --get-key <file>           - Get encryption key from <file> instead of
2656                                  from STDIN.
2657     --Test-mode                - Build SPA packet data but do not send it
2658                                  over the network.
2659     --time-offset-plus <str>   - Add a time offset to the advertised time
2660                                  stamp in the SPA packet (e.g. "60sec" or
2661                                  "1day").
2662     --time-offset-minus <str>  - Subtract a time offset from the advertised
2663                                  time stamp in the SPA packet (e.g. "60sec"
2664                                  or "1day").
2665     --HTTP                     - Send SPA packets over HTTP (requires that
2666                                  the system running the fwknopd server is
2667                                  also running a webserver).
2668     --TCP-sock                 - Send SPA packets over an established TCP
2669                                  socket with the fwknopd server.  This
2670                                  allows SPA packets to be sent over the Tor
2671                                  network.
2672     --no-save-args             - Do not save command line args to
2673                                  ~/.fwknop.run file.
2674     --Show-last-cmd            - Display the last fwknop command and exit.
2675     --Show-host-cmd <host>     - Display the last fwknop command that was
2676                                  executed for <host> and exit.
2677     -u, --user-rc <rc-file>    - Specify path to user connect rc file
2678                                  instead of using the default ~/.fwknoprc.
2679                                  This file is not referenced for encrypted
2680                                  port sequences; only for shared sequences.
2681     -H, --Home-dir <directory> - Specify the home directory of the current
2682                                  user that is running fwknop.
2683     --time-delay <seconds>     - (Legacy port knocking mode) Introduce a
2684                                  time delay between each connection in a
2685                                  knock sequence.  This is mainly used in
2686                                  conjunction with the MIN_TIME_DIFF access
2687                                  control directive.
2688     -k, --knock-dst <IP>       - Connection destination IP address for port
2689                                  knock sequence (synonym for -D).
2690     -d, --debug                - Run fwknop in debugging mode.
2691     --Lib-dir <path>           - Path to the perl modules directory (not
2692                                  usually necessary).
2693     --locale <locale>          - Manually define a locale setting.
2694     --no-locale                - Don't set the locale to anything (the
2695                                  default is the "C" locale).
2696     --no-FKO-module            - Revert to older perl implementation even if
2697                                  the FKO module is installed.
2698     --test-FKO-exists          - See if the FKO module is available to use
2699                                  and exit (this is used by the fwknop test
2700                                  suite).
2701     -v, --verbose              - Verbose mode.
2702     -V, --Version              - Display version and exit.
2703     -h, --help                 - Print help and exit.
2704 _HELP_
2705
2706     exit $exit_status;
2707 }