root/fwknop/tags/fwknop-0.9.7-pre3/fwknop

Revision 498, 39.4 kB (checked in by mbr, 2 years ago)

0.9.7 version

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
Line 
1 #!/usr/bin/perl -w
2 #
3 #############################################################################
4 #
5 # File: fwknop
6 #
7 # Purpose: fwknop implements an authorization scheme known as Single Packet
8 #          Authorization (SPA) that requires only a single encrypted packet
9 #          to communicate various pieces of information including desired
10 #          access through a Netfilter policy and/or specific commands to
11 #          execute on the target system.  The main application of this
12 #          program is to protect services such as SSH with an additional
13 #          layer of security in order to make the exploitation of
14 #          vulnerabilities (both 0-day and unpatched code) much more
15 #          difficult.  For more information, see the fwknop(8) man page.
16 #
17 # Author: Michael Rash (mbr@cipherdyne.org)
18 #
19 # Version: 0.9.7
20 #
21 # Copyright (C) 2004-2006 Michael Rash (mbr@cipherdyne.org)
22 #
23 # License (GNU Public License):
24 #
25 #    This program is distributed in the hope that it will be useful,
26 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
27 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
28 #    GNU General Public License for more details.
29 #
30 #    You should have received a copy of the GNU General Public License
31 #    along with this program; if not, write to the Free Software
32 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
33 #    USA
34 #
35 #############################################################################
36 #
37 # $Id$
38 #
39
40 use lib '/usr/lib/fwknop';
41 use Crypt::CBC;
42 use Unix::Syslog qw(:subs :macros);
43 use Net::IPv4Addr qw(ipv4_in_network);
44 use Net::Ping::External qw(ping);
45 use Digest::MD5 'md5_base64';
46 use IO::Socket;
47 use IO::Handle;
48 use MIME::Base64;
49 use Data::Dumper;
50 use POSIX;
51 use Term::ReadKey;
52 use Getopt::Long;
53 use strict;
54
55 my $version = '0.9.7';
56
57 my $print_version  = 0;
58 my $print_help     = 0;
59 my $run_last_args  = 0;
60 my $debug          = 0;
61 my $quiet          = 0;
62 my $verbose        = 0;
63 my $whatismyip_src = 0;
64 my $cmdl_homedir   = '';
65 my $knock_sleep    = 1;  ### default to 1 second difference between port knocks
66 my $knock_dst      = '';
67 my $homedir        = '';
68 my $spoof_src      = '';
69 my $server_mode    = 'pcap';
70 my $user_rc_file   = '';
71 my $server_proto   = '';
72 my $run_last_host  = '';
73 my $gpg_verbose    = 0;
74 my $gpg_signing_key = '';
75 my $gpg_recipient   = '';
76 my $gpg_home_dir    = '';
77 my $max_msg_len        = 1500;
78 my $whatismyip_host    = 'www.whatismyip.com';
79 my $no_save_last_args  = 0;
80 my $server_auth_method   = '';
81 my $spa_established_tcp  = 0;
82 my $server_auth_crypt_pw = '';
83 my $err_wait_timer       = 30;  ### seconds
84 my $cmdline_pcap_cmd     = '';
85 my $pcap_sleep_interval  = 1;  ### seconds
86 my $knock_dst_pre_resolve = '';
87
88 ### User agent for contacting http://www.whatismyip.com/, we don't
89 ### advertise the fwknop client (can override with --User-agent)
90 my $whatismyip_user_agent = "Firefox/1.0.5.4";
91
92 ### mode numbers
93 my $command_mode = 0;
94 my $access_mode  = 1;
95
96 ### default time values
97 my $knock_interval    = 60;
98 my $fw_access_timeout = 300;
99
100 ### default to root (client must run as root in this mode)
101 my $spoof_username = 'root';
102 my $spoof_proto = 'udp'### default to udp
103
104 ### encrypted port knock vars
105 my $cmdline_offset    = 0;
106 my $enc_port_offset   = 61000;  ### default offset
107 my $enc_key           = '';
108 my $enc_alg           = 'Rijndael';
109 my $enc_blocksize     = 16;
110 my $enc_shared_secret = '';
111 my $enc_allow_ip      = '';
112 my $enc_source_ip     = '';
113 my $enc_rotate_proto  = 0;
114 my $get_key_file      = 0;  ### get key from file
115 my $enc_pcap_port     = 62201;  ### default pcap port
116 my $access_str        = '';
117
118 ### packet counters
119 my $tcp_ctr  = 0;
120 my $udp_ctr  = 0;
121 my $icmp_ctr = 0;
122
123 ### tcp option types
124 my $tcp_nop_type       = 1;
125 my $tcp_mss_type       = 2;
126 my $tcp_win_scale_type = 3;
127 my $tcp_sack_type      = 4;
128 my $tcp_timestamp_type = 8;
129
130 my %tcp_p0f_opt_types = (
131     'N' => $tcp_nop_type,
132     'M' => $tcp_mss_type,
133     'W' => $tcp_win_scale_type,
134     'S' => $tcp_sack_type,
135     'T' => $tcp_timestamp_type
136 );
137
138 my $ip_re = '(?:\d{1,3}\.){3}\d{1,3}';
139
140 my @args_cp = @ARGV;
141
142 ### run GetOpt() to get comand line args
143 &handle_command_line();
144
145 &usage(0) if $print_help;
146
147 if ($print_version) {
148     print "[+] fwknop v$version by Michael Rash ",
149         "<mbr\@cipherdyne.org>\n";
150     exit 0;
151 }
152
153 die "[*] Cannot spoof source address for a real TCP socket."
154     if ($spoof_src and $spa_established_tcp);
155
156 &get_homedir();
157
158 ### save a copy
159 $knock_dst_pre_resolve = $knock_dst;
160
161 if ($run_last_args) {
162     ### run fwknop with same command line args as the previous
163     ### execution
164     &run_last_cmdline();
165 } elsif ($run_last_host) {
166     ### run fwknop with the last args for this particular knock destination
167     &run_last_host_cmdline();
168 }
169
170 die "[*] Must specify a knock destination with -k <IP|Host>"
171     unless $knock_dst;
172
173 print "[+] ** Running in client debug mode. **\n" if $debug;
174 print "[+] Starting fwknop in client mode.\n" unless $quiet;
175
176 unless ($knock_dst =~ /$ip_re/) {
177     print "[+] Resolving hostname: $knock_dst\n" unless $quiet;
178     ### resolve to an IP
179     my $iaddr = inet_aton($knock_dst)
180         or die "[*] Could not resolve $knock_dst to an IP.";
181     my $addr = inet_ntoa($iaddr)
182         or die "[*] Could not resolve $knock_dst to an IP.";
183     $knock_dst = $addr;
184 }
185
186 unless (lc($server_mode) eq 'pcap'
187         or lc($server_mode) eq 'knock'
188         or lc($server_mode) eq 'shared') {
189     die "[*] Unknown server mode: $server_mode ",
190         qq|(must be "pcap", "knock", or "shared"\n|;
191 }
192
193 &validate_access_str() if $access_str;
194
195 if (lc($server_mode) eq 'pcap' or lc($server_mode) eq 'knock') {
196     die "[*] Must also specify: -k <knock destination>\n"
197         unless $knock_dst;
198
199     unless ($cmdline_pcap_cmd) {
200         unless ($enc_allow_ip
201                 or $enc_source_ip
202                 or $whatismyip_src) {
203             die "[*] Must either specify: --allow-IP <IP>, ",
204                 "--source-IP, or --whatismyip\n";
205         }
206
207         ### make fwknop server see "0.0.0.0" in the encrypted sequence.
208         ### This will instruct the server to open the port for whatever
209         ### source IP the sequence comes from.  This is useful for
210         ### clients that are behind a NAT device.
211         $enc_allow_ip = '0.0.0.0' if $enc_source_ip;
212
213         ### resolve the extenal IP via http://www.whatismyip.com
214         $enc_allow_ip = &get_ip_whatismyip() if $whatismyip_src;
215
216         if ($spoof_src) {
217             $< == 0 && $> == 0 or
218                 die '[*] You must be root (or equivalent ',
219                     "UID 0 account) to spoof the source address.\n";
220         }
221         unless ($enc_allow_ip =~ /$ip_re/) {
222             ### resolve to an IP
223             my $iaddr = inet_aton($enc_allow_ip)
224                 or die "[*] Could not resolve $enc_allow_ip to IP.";
225             my $addr = inet_ntoa($iaddr)
226                 or die "[*] Could not resolve $enc_allow_ip to IP.";
227             $enc_allow_ip = $addr;
228         }
229     }
230     if ($cmdline_offset) {
231         if (lc($server_mode) eq 'pcap') {
232             die "[*] Port offset is meaningless in pcap mode ",
233                 "(only a single packet is sent).";
234         }
235         unless ($cmdline_offset < 65280 and $cmdline_offset > 0) {
236             die "[*] Port offset must be 0 < port < 65280";
237         }
238         $enc_port_offset = $cmdline_offset;
239     }
240     if (lc($server_mode) eq 'pcap') {
241         unless ($enc_pcap_port < 65535 and $enc_pcap_port > 0) {
242             die "[*] Port offset must be 0 < port < 65535";
243         }
244     }
245 } else {
246     if ($enc_rotate_proto) {
247         die '[*] Can only specify --rotate-proto with ',
248             'encrypted sequences.';
249     }
250 }
251
252 ### save our command line args (so -l can be used next time)
253 &save_args() unless $run_last_args or $run_last_host or $no_save_last_args;
254
255 if (lc($server_mode) eq 'pcap' or lc($server_mode) eq 'knock') {
256
257     ### get the encryption key from the --get-key file
258     ### or from STDIN if it's not in the file.
259     &get_key();
260
261     &handle_server_auth_method() if $server_auth_method;
262
263     if (lc($server_mode) eq 'pcap') {
264
265         ### construct and send the encrypted message to the server
266         ### (sends a single packet).
267         &pcap_send_encrypted_msg(&pcap_build_enc_msg());
268
269     } else {
270         ### we are running in port knocking mode, so get the
271         ### encrypted port sequence (16 ports)
272         &knock_ports(&encrypt_sequence());
273     }
274 } else {
275     ### we are running in non-encrypted port knocking mode, so get
276     ### the port sequence
277     &knock_ports(&import_shared_sequence());
278 }
279 exit 0;
280 #============================ end main ==============================
281
282 sub pcap_build_enc_msg() {
283
284     my $msg         = '';
285     my $msg_part1   = ''### contains everything before the md5 sum
286     my $server_auth = ''### contains stuff after the md5 sum
287     my $user        = '';
288     my $random_num  = '';
289     my $timestamp   = time();
290
291     ### message format (all fields are separated by ":" characters
292     #
293     #  random number (16 bytes)
294     #  username
295     #  timestamp
296     #  software version
297     #  mode (command mode (0) or access mode (1))
298     #  if command mode => command to execute
299     #  if access mode  => IP,proto,port
300     #  message md5 sum
301     #  server_auth (post 0.9.2 release)
302
303     ### some of the fields below might happen to contain ":" chars,
304     ### so we base64 encode them
305
306     print "\n[+] Building encrypted single-packet authorization (SPA) ",
307         "message...\n" unless $quiet;
308
309     if ($spoof_src) {
310         $user = $spoof_username;
311     } else {
312         ### getlogin() is better than using ENV{'USER'}, which is
313         ### easily manipulated, so only use as a last resort.
314         $user = getlogin() || getpwuid($<) ||
315             die '[*] Could not determine user.'
316     }
317
318     $random_num = int(rand(100000000000000));
319     $random_num .= int(rand(10)) while (length($random_num) < 16);
320
321     print "[+] Packet fields:\n\n",
322         "        Random data: $random_num\n",
323         "        Username:    $user\n",
324         "        Timestamp:   $timestamp\n",
325         "        Version:     $version\n"
326         unless $quiet;
327
328     ### append username and timestamp
329     $msg_part1 = $random_num . ':' . encode_base64($user) .
330         ':' . $timestamp . ':' . $version;
331
332     if ($cmdline_pcap_cmd) {
333         ### a specific command will be executed on the server
334         $msg_part1 .= ":$command_mode:" . encode_base64($cmdline_pcap_cmd);
335
336         print "        Action:      $command_mode (command mode)\n",
337             "        Cmd:         $cmdline_pcap_cmd\n"
338             unless $quiet;
339     } else {
340         ### access to port(s)/protocol(s) will be granted on the
341         ### server
342         $msg_part1 .= ":$access_mode:";
343         print "        Action:      $access_mode (access mode)\n"
344             unless $quiet;
345         if ($access_str) {
346             $msg_part1 .= encode_base64("$enc_allow_ip,$access_str");
347             print "        Access:      $enc_allow_ip,$access_str\n"
348                 unless $quiet;
349         } else {
350             $msg_part1 .= encode_base64("$enc_allow_ip,none,0");
351             print "        Access:      $enc_allow_ip,none,0\n"
352                 unless $quiet;
353         }
354     }
355
356     $msg_part1 =~ s/\n//g;
357
358     if (lc($server_auth_method) eq 'crypt') {
359         $server_auth = ':' . encode_base64("crypt,$server_auth_crypt_pw");
360     }
361
362     ### calculate md5 hash over entire message
363     my $md5sum = md5_base64($msg_part1 . $server_auth);
364     $msg = "$msg_part1:$md5sum" . $server_auth;
365
366     print "        MD5 sum:     $md5sum\n" unless $quiet;
367
368     if (lc($server_auth_method) eq 'crypt') {
369         print "        Server auth: $server_auth_method,";
370         for (my $i=0; $i<length($server_auth_crypt_pw); $i++) {
371             print '*';
372         }
373         print "\n";
374     }
375     print "\n[+] Clear text message: $msg\n" if $debug;
376
377     if ($gpg_signing_key) {
378         return &pcap_GPG_encrypt_msg($msg);
379     }
380     return &pcap_Rijndael_encrypt_msg($msg);
381 }
382
383 sub pcap_GPG_encrypt_msg() {
384     my $msg = shift;
385
386     my $gnupg = GnuPG::Interface->new();
387
388     $gpg_home_dir = "$homedir/.gnupg" unless $gpg_home_dir;
389
390     if ($gpg_verbose) {
391         $gnupg->options->hash_init(
392             'homedir' => $gpg_home_dir);
393     } else {
394         $gnupg->options->hash_init(
395             'batch' => 1,
396             'homedir' => $gpg_home_dir);
397     }
398
399     $gnupg->options->default_key($gpg_signing_key);
400     $gnupg->options->push_recipients($gpg_recipient);
401
402     my ($input, $output, $error, $pw, $status) =
403         (IO::Handle->new(),
404         IO::Handle->new(),
405         IO::Handle->new(),
406         IO::Handle->new(),
407         IO::Handle->new());
408
409     my $handles = GnuPG::Handles->new(
410         stdin  => $input,
411         stdout => $output,
412         stderr => $error,
413         passphrase => $pw,
414         status => $status
415     );
416
417     my $pid = $gnupg->sign_and_encrypt('handles' => $handles);
418
419     print $pw $enc_key;
420     close $pw;
421
422     print $input $msg;
423     close $input;
424
425     my @ciphertext = <$output>;
426     close $output;
427
428     my @errors = <$error>;
429     close $error;
430
431     waitpid $pid, 0;
432
433     my $ctext = '';
434     if (@ciphertext) {
435         $ctext = join '', @ciphertext;
436     }
437
438     unless ($ctext) {
439         print "[*] GPG encrypt failed.\n";
440         unless ($gpg_verbose) {
441             print "    GPG errors:\n";
442             print for @errors;
443         }
444         exit 1;
445     }
446
447     my $encoded_msg = encode_base64($ctext);
448
449     $encoded_msg =~ s/=*$//;
450     $encoded_msg =~ s/\n//g;
451
452     print "[+] Encrypted message: $encoded_msg\n" if $debug;
453     return $encoded_msg;
454 }
455
456 sub pcap_Rijndael_encrypt_msg() {
457     my $msg = shift;
458
459     my $cipher = Crypt::CBC->new(
460         {
461             'key'             => $enc_key,
462             'cipher'          => $enc_alg
463         }
464     );
465     my $encoded_msg = encode_base64($cipher->encrypt($msg));
466
467     ### remove trailing "==" (the decrypt function will put
468     ### them back before attempting to decrypt)... this is to
469     ### make it more difficult for an IDS to detect fwknop
470     ### traffic
471     $encoded_msg =~ s/=*$//;
472     $encoded_msg =~ s/\n//g;
473
474     print "[+] Encrypted message: $encoded_msg\n" if $debug;
475     return $encoded_msg;
476 }
477
478 sub pcap_send_encrypted_msg() {
479     my $msg = shift;
480
481     my $msg_len = length($msg);
482
483     if ($msg_len > $max_msg_len) {
484         die "[*] Message length is too long ($msg_len bytes), ",
485             "must be less than $max_msg_len bytes";
486     }
487
488     if ($verbose) {
489         print "\n[+] Packet data:\n\n", $msg, "\n\n" unless $quiet;
490     }
491
492     if ($spoof_src) {
493         unless ($spoof_src =~ /$ip_re/) {
494             ### resolve to an IP
495             my $iaddr = inet_aton($spoof_src)
496                 or die "[*] Could not resolve $spoof_src to IP.";
497             my $addr = inet_ntoa($iaddr)
498                 or die "[*] Could not resolve $spoof_src to IP.";
499             $spoof_src = $addr;
500         }
501
502         print
503 "\n[+] Sending $msg_len byte message to $knock_dst over $spoof_proto",
504     "/$enc_pcap_port\n    (spoofed src ip: $spoof_src).\n" unless $quiet;
505         my $rand_src_port = int(rand(65535));
506         $rand_src_port = 65001 if $rand_src_port > 65535;
507         $rand_src_port += 1024 if $rand_src_port < 1024;
508
509         ### use Net::RawIP to spoof the packets
510         require Net::RawIP;
511
512         if ($spoof_proto eq 'udp') {
513             my $rawpkt = new Net::RawIP({
514                 ip => {
515                     saddr => $spoof_src,
516                     daddr => $knock_dst
517                 },
518                 udp =>{}});
519             $rawpkt->set({ ip => { saddr  => $spoof_src,
520                     daddr  => $knock_dst
521                 },
522                 udp => {
523                     source => $rand_src_port,
524                     dest   => $enc_pcap_port,
525                     data   => $msg,
526                 }
527             });
528             $rawpkt->send();
529         } elsif ($spoof_proto eq 'icmp') {
530             my $rawpkt = new Net::RawIP({
531                 ip => {
532                     saddr => $spoof_src,
533                     daddr => $knock_dst
534                 },
535                 icmp =>{}});
536             $rawpkt->set({ ip => { saddr  => $spoof_src,
537                     daddr  => $knock_dst
538                 },
539                 icmp => {
540                     type => 0,
541                     code => 0,
542                     sequence => 0,
543                     data => $msg
544                 }
545             });
546             $rawpkt->send();
547         } elsif ($spoof_proto eq 'tcp') {
548             my $rawpkt = new Net::RawIP({
549                 ip => {
550                     saddr => $spoof_src,
551                     daddr => $knock_dst
552                 },
553                 tcp =>{}});
554             $rawpkt->set({ ip => { saddr => $spoof_src,
555                     daddr  => $knock_dst
556                 },
557                 tcp => {
558                     ack => 1,
559                     source => $rand_src_port,
560                     dest   => $enc_pcap_port,
561                     data => $msg
562                 }
563             });
564             $rawpkt->send();
565         }
566     } else {
567
568         if ($spa_established_tcp) {  ### useful for Tor
569             print "\n[+] Sending $msg_len byte message to $knock_dst ",
570                 "over established tcp/$enc_pcap_port socket...\n"
571                 unless $quiet;
572
573             my $socket = IO::Socket::INET->new(
574                 PeerAddr => $knock_dst,
575                 PeerPort => $enc_pcap_port,
576                 Proto    => 'tcp',
577                 Timeout  => 1
578             ) or die "[*] Could not acquire TCP/$enc_pcap_port socket ",
579                     "with $knock_dst: $!";
580
581             $socket->send($msg);
582             undef $socket;
583
584         } else {
585             print "\n[+] Sending $msg_len byte message to $knock_dst ",
586                 "over udp/$enc_pcap_port...\n" unless $quiet;
587
588             my $socket = IO::Socket::INET->new(
589                 PeerAddr => $knock_dst,
590                 PeerPort => $enc_pcap_port,
591                 Proto    => 'udp',
592                 Timeout  => 1
593             ) or die "[*] Could not acquire UDP socket: $!";
594
595             $socket->send($msg);
596             undef $socket;
597         }
598     }
599     return;
600 }
601
602 sub knock_ports() {
603     my $ports_aref = shift;
604
605     print "[+] Sending port knocking sequence to knock server: $knock_dst\n"
606         unless $quiet;
607     for my $href (@$ports_aref) {
608         my $proto = $href->{'proto'};
609         my $port  = $href->{'port'};
610         ### note that we never care if the destination replies with a
611         ### RST or icmp echo reply (or anything else).  In fact, hopefully
612         ### the remote firewall is configued to not reply at all
613         if ($proto eq 'icmp') {
614             print "[+] icmp echo request -> $knock_dst\n";
615             ping(hostname => "$knock_dst", count => 1, timeout => 1);
616             sleep $knock_sleep;
617         } else {
618             printf "%-14s%s\n", "[+] $proto/$port", "-> $knock_dst";
619             my $socket = IO::Socket::INET->new(
620                 PeerAddr => $knock_dst,
621                 PeerPort => $port,
622                 Proto    => $proto,
623                 Timeout  => 1
624             );  ### note there is no "or die" here since we just want to throw
625                 ### packets on the network
626             if (defined $socket and $proto eq 'udp') {
627                 $socket->send('0');  ### have to actually send something for udp
628                 sleep $knock_sleep;
629             }
630             if ($proto eq 'tcp' and $knock_sleep > 1) {
631                 sleep $knock_sleep;
632             }
633             undef $socket if defined $socket;
634         }
635     }
636     print "[+] Finished knock sequence.\n";
637     return;
638 }
639
640 sub encrypt_sequence() {
641     my $clear_txt = '';
642     my $checksum = 0;
643     my @encrypted_seq = ();
644
645     my $cipher = Crypt::CBC->new(
646         {
647             'key'             => $enc_key,
648             'cipher'          => $enc_alg,
649         }
650     );
651
652     my @octets = split /\./, $enc_allow_ip;
653
654     $clear_txt .= chr($_) for @octets;
655     $checksum += $_ for @octets;
656
657     my $proto_num = 6;
658     my $enc_allow_port = 0;
659     if ($access_str =~ /udp/i) {
660         $proto_num = 17;
661         if ($access_str =~ /(\d+)/) {
662             $enc_allow_port = $1;
663         }
664     } elsif ($access_str =~ /icmp/i) {
665         $proto_num = 1;
666         $enc_allow_port = 0;
667     }
668
669     unless ($enc_allow_port) {
670         die "[*] Must specify port to open."
671             if $proto_num != 1;
672     }
673     my $port_upper_bits = $enc_allow_port;
674     my $port_lower_bits = $enc_allow_port;
675
676     if ($enc_allow_port == 0) {
677         $port_upper_bits = 0;
678         $port_lower_bits = 0;
679     } else {
680         $port_upper_bits = $port_upper_bits >> 8;
681         $port_lower_bits = $port_lower_bits % 256;
682     }
683
684     $clear_txt .= chr($port_upper_bits);
685     $clear_txt .= chr($port_lower_bits);
686
687     $checksum += $port_upper_bits;
688     $checksum += $port_lower_bits;
689
690     $clear_txt .= chr($proto_num);
691     $checksum += $proto_num;
692
693     $checksum = $checksum % 256;
694
695     $clear_txt .= chr($checksum);
696
697     ### append username
698     ### FIXME: either the checksum should be removed, or it should
699     ### be applied to the username as well.
700     my $username = getlogin() || getpwuid($<) || die "[*] Could not ",
701         "get process username.";
702
703     if ($username) {
704         my @chars = split //, $username;
705         for my $char (@chars) {
706             if (length($clear_txt) < $enc_blocksize-1) {
707                 $clear_txt .= $char;
708             }
709         }
710     }
711
712     ### pad out with zeros until we have a full block (actually
713     ### 15 bytes)
714     while (length($clear_txt) < $enc_blocksize-1) {
715         $clear_txt .= chr(0);
716     }
717
718     my @tmp_chars = split //, $clear_txt;
719     print "[+] clear text sequence: ";
720     print ord($_) . ' ' for @tmp_chars;
721     print "\n";
722
723     my $cipher_txt = $cipher->encrypt($clear_txt);
724     undef $cipher;
725
726     @tmp_chars = split //, $cipher_txt;
727     print "[+] cipher text sequence: ";
728     print ord($_) . ' ' for @tmp_chars;
729     print "\n";
730
731     my @chars = split //, $cipher_txt;
732     my $char_ctr = 0;
733     for my $char (@chars) {
734         my %hsh;
735         if ($enc_rotate_proto) {
736             ### alternate between tcp and udp protocols
737             if ($char_ctr % 2 == 0) {
738                 %hsh = ('port' => ord($char) + $enc_port_offset,
739                     'proto' => 'tcp');
740             } else {
741                 %hsh = ('port' => ord($char) + $enc_port_offset,
742                     'proto' => 'udp');
743             }
744         } else {
745             ### hardcode knock sequence proto as tcp
746             %hsh = ('port' => ord($char) + $enc_port_offset,
747                 'proto' => 'tcp');
748         }
749         push @encrypted_seq, \%hsh;
750         $char_ctr++;
751     }
752     return \@encrypted_seq;
753 }
754
755 sub get_ip_whatismyip() {
756     my $external_ip = '';
757     print "    Resolving external IP via: http://$whatismyip_host/\n"
758         unless $quiet;
759     my $w_ip_tmp = inet_aton($whatismyip_host)
760         or die "[*] Could not resolve $whatismyip_host to an IP.";
761     my $w_ip = inet_ntoa($w_ip_tmp)
762         or die "[*] Could not resolve $whatismyip_host to an IP.";
763
764     my $sock = new IO::Socket::INET(
765         PeerAddr => $w_ip,
766         PeerPort => 80,
767         Proto    => 'tcp',
768         Timeout  => 7)
769     or die "[*] Could not open socket with http://$whatismyip_host/";
770
771     if (defined($sock)) {
772         print $sock "GET / HTTP/1.0\r\n",
773             "User-Agent: $whatismyip_user_agent\r\n",
774             "Host:$whatismyip_host\r\n",
775             "Accept: */*\r\n",
776             "Connection: Keep-Alive\r\n\r\n";
777         recv($sock, my $web_data, 1000, 0);
778         if ($web_data =~ /WhatIsMyIP\.com\s+-\s+($ip_re)/i) {
779             $external_ip = $1;
780         }
781         close $sock;
782     }
783     die "[*] Could not extract external IP from http://$whatismyip_host/"
784         unless $external_ip;
785     print "    Got external address: $external_ip\n\n" unless $quiet;
786     return $external_ip;
787 }
788
789 sub get_key() {
790     if ($get_key_file) {
791         ### get the encryption key from file
792         open F, "< $get_key_file" or die "[*] Could not open ",
793             "$get_key_file: $!";
794         my @lines = <F>;
795         close F;
796         for my $line (@lines) {
797             chomp $line;
798             if ($line =~ /$knock_dst:\s*(.*)/) {
799                 $enc_key = $1;
800             }
801         }
802 die "[*] Could not read encryption key for $knock_dst from $get_key_file\n",
803     "    fwknop expects the format \"$knock_dst: <KEY>\n in $get_key_file\n"
804             unless $enc_key;
805     } else {
806         if ($gpg_signing_key) {
807
808             ### load the GnuPG::Interface module
809             require GnuPG::Interface;
810             print
811 "[+] Enter the GPG password for signing key: $gpg_signing_key\n\n"
812             unless $quiet;
813         } else {
814             print
815 "[+] Enter an encryption key. This key must match a key in the file\n",
816 " /etc/fwknop/access.conf on the remote system.\n\n" unless $quiet;
817         }
818         ReadMode 'noecho';
819         while (1) {
820             if ($gpg_signing_key) {
821                 $quiet == 1 ? print "GPG signing password: "
822                     : print "    GPG signing password: ";