root/fwknop/tags/fwknop-0.9.7-pre2/fwknopd

Revision 492, 109.2 kB (checked in by mbr, 2 years ago)

updated to only import p0f signatures if we are running in legacy port knocking mode, minor comment fix

  • 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: fwknopd
6 #
7 # Purpose: fwknopd implements the server portion of an authorization scheme
8 #          known as Single Packet Authorization (SPA) that requires only a
9 #          single encrypted packet to communicate various pieces of
10 #          information including desired access through a Netfilter policy
11 #          and/or specific commands to execute on the target system.  The
12 #          main application of this program is to protect services such as
13 #          SSH with an additional layer of security in order to make the
14 #          exploitation of vulnerabilities (both 0-day and unpatched code)
15 #          much more difficult.  For more information, see the fwknop(8) man
16 #          page.
17 #
18 # Author: Michael Rash (mbr@cipherdyne.org)
19 #
20 # Version: 0.9.6
21 #
22 # Copyright (C) 2004-2006 Michael Rash (mbr@cipherdyne.org)
23 #
24 # License (GNU Public License):
25 #
26 #    This program is distributed in the hope that it will be useful,
27 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
28 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
29 #    GNU General Public License for more details.
30 #
31 #    You should have received a copy of the GNU General Public License
32 #    along with this program; if not, write to the Free Software
33 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
34 #    USA
35 #
36 #############################################################################
37 #
38 # $Id$
39 #
40
41 use lib '/usr/lib/fwknop';
42 use Crypt::CBC;
43 use Unix::Syslog qw(:subs :macros);
44 use Net::IPv4Addr qw(ipv4_in_network);
45 use Net::Pcap;
46 use NetPacket::IP;
47 use NetPacket::UDP;
48 use NetPacket::TCP;
49 use NetPacket::ICMP;
50 use NetPacket::Ethernet;
51 use IPTables::Parse;
52 use IPTables::ChainMgr;
53 use Digest::MD5 'md5_base64';
54 use IO::Socket;
55 use IO::Handle;
56 use MIME::Base64;
57 use Data::Dumper;
58 use POSIX;
59 use Getopt::Long;
60 use strict;
61
62 my $config_file = '/etc/fwknop/fwknop.conf';
63 my $alerting_config_file = '/etc/fwknop/alert.conf';
64
65 my $version = '0.9.6';
66
67 my %config     = ();
68 my %cmds       = ();
69 my %p0f_sigs   = ();
70 my %p0f        = ();
71 my @access     = ();
72 my @ipt_config = ();
73 my %ipt_access = ();
74 my %ip_sequences = ();
75 my %md5_msg_store = ();
76 my %required_users = ();
77
78 my $os_fprint_only = 0;
79 my $print_version  = 0;
80 my $print_help     = 0;
81 my $kill           = 0;
82 my $restart        = 0;
83 my $status         = 0;
84 my $debug          = 0;
85 my $ipt_list       = 0;
86 my $ipt_flush      = 0;
87 my $verbose        = 0;
88 my $use_gpg        = 0;
89 my $os_ipt_log     = '';
90 my $cmdline_intf   = '';
91 my $warn_msg       = '';
92 my $die_msg        = '';
93 my $err_wait_timer     = 30;  ### seconds
94 my $skipped_first_loop = 0;
95 my $pcap_sleep_interval = 1;  ### seconds
96
97 ### mode numbers
98 my $command_mode = 0;
99 my $access_mode  = 1;
100
101 ### default time values
102 my $knock_interval    = 60;
103 my $fw_access_timeout = 300;
104
105 my $enc_port_offset   = 61000;  ### default offset
106 my $enc_key           = '';
107 my $enc_alg           = 'Rijndael';
108 my $enc_blocksize     = 16;
109
110 ### packet counters
111 my $tcp_ctr  = 0;
112 my $udp_ctr  = 0;
113 my $icmp_ctr = 0;
114
115 ### tcp option types
116 my $tcp_nop_type       = 1;
117 my $tcp_mss_type       = 2;
118 my $tcp_win_scale_type = 3;
119 my $tcp_sack_type      = 4;
120 my $tcp_timestamp_type = 8;
121
122 my %tcp_p0f_opt_types = (
123     'N' => $tcp_nop_type,
124     'M' => $tcp_mss_type,
125     'W' => $tcp_win_scale_type,
126     'S' => $tcp_sack_type,
127     'T' => $tcp_timestamp_type
128 );
129
130 my $ip_re = '(?:\d{1,3}\.){3}\d{1,3}';
131
132 my @args_cp = @ARGV;
133
134 ### run GetOpt() to get comand line args
135 &handle_command_line();
136
137 &usage(0) if $print_help;
138
139 if ($print_version) {
140     print "[+] fwknopd v$version by Michael Rash ",
141         "<mbr\@cipherdyne.org>\n";
142     exit 0;
143 }
144
145 if ($os_fprint_only) {
146     print "[+] Entering OS fingerprinting mode.\n";
147 }
148
149 print STDERR "[+] ** Starting fwknopd (debug mode) **\n" if $debug;
150
151 ### setup to run
152 &fwknop_init();
153
154 if ($config{'AUTH_MODE'} eq 'KNOCK' or $os_fprint_only) {
155
156     ### we are running in traditional port knocking mode
157     &knock_loop();
158
159 } elsif ($config{'AUTH_MODE'} eq 'FILE_PCAP'
160         or $config{'AUTH_MODE'} eq 'ULOG_PCAP'
161         or $config{'AUTH_MODE'} eq 'PCAP') {
162
163     ### we are parsing the pcap file created by the ulogd pcap
164     ### writer, or in sniffing mode against an interface
165     &pcap_loop();
166 }
167 exit 0;
168 #============================ end main ==============================
169
170 sub pcap_loop() {
171
172     ### we use both a size and an inode check in the FILE_PCAP and
173     ### ULOG_PCAP modes to check if the file has been rotated
174     my $pcap_file_size  = 0;
175     my $pcap_file_inode = 0;
176
177     ### get pcap opject
178     my $pcap_t = &get_pcap_obj();
179
180     if ($config{'AUTH_MODE'} eq 'FILE_PCAP'
181             or $config{'AUTH_MODE'} eq 'ULOG_PCAP') {
182         ### get file size (we don't need a -e check here because
183         ### this is handled in get_pcap_obj()).
184         $pcap_file_size = -s $config{'PCAP_PKT_FILE'};
185
186         ### get inode associated with the sniffing file
187         $pcap_file_inode = (stat($config{'PCAP_PKT_FILE'}))[1];
188     }
189     print STDERR "[+] pcap_loop()\n" if $debug;
190
191     my $check_file_ctr = 0;
192
193     for (;;) {
194
195         Net::Pcap::loop($pcap_t, 1, \&pcap_process_pkt, 'fwknop_tag');
196
197         if ($config{'AUTH_MODE'} eq 'FILE_PCAP'
198                 or $config{'AUTH_MODE'} eq 'ULOG_PCAP') {
199
200             ### check to see if the pcap file has been rotated (we need to
201             ### close and re-open)
202             if ($check_file_ctr == 10) {
203                 if (-e $config{'PCAP_PKT_FILE'}) {
204                     my $size_tmp  = -s $config{'PCAP_PKT_FILE'};
205                     my $inode_tmp = (stat($config{'PCAP_PKT_FILE'}))[1];
206                     if ($inode_tmp != $pcap_file_inode
207                             or $size_tmp < $pcap_file_size) {
208
209                         ### the file was rotated or shrank, so get new
210                         ### pcap_t object
211                         Net::Pcap::close($pcap_t);
212
213                         &logr('[+]', "pcap file $config{'PCAP_PKT_FILE'} " .
214                             "shrank or was rotated, so re-opening", 0);
215                         $pcap_t = &get_pcap_obj();
216
217                         ### set file size and inode
218                         $pcap_file_size  = $size_tmp;
219                         $pcap_file_inode = $inode_tmp;
220                     }
221                 } else {
222                     Net::Pcap::close($pcap_t);
223                     &logr('[+]', "pcap file $config{'PCAP_PKT_FILE'} " .
224                         "was rotated, so re-opening", 0);
225                     $pcap_t = &get_pcap_obj();
226
227                     ### set file size and inode
228                     $pcap_file_size  = -s $config{'PCAP_PKT_FILE'};
229                     $pcap_file_inode = (stat($config{'PCAP_PKT_FILE'}))[1];
230                 }
231                 $check_file_ctr = 0;
232             }
233             $check_file_ctr++;
234
235             ### always check to see if we need to timeout access for IPs
236             ### For AUTH_MODE set to PCAP, knoptm will timeout access
237             &timeout_access();
238         }
239
240         sleep $pcap_sleep_interval;
241     }
242
243     Net::Pcap::close($pcap_t);
244
245     return;
246 }
247
248 sub pcap_process_pkt() {
249     my ($tag, $hdr, $pkt) = @_;
250
251     return '' unless $tag eq 'fwknop_tag';
252     return '' unless defined $hdr;
253     return '' unless defined $pkt;
254
255     my $ether_data = '';
256     my $ip         = '';
257     my $src_ip     = '';
258     my $proto      = '';
259     my $transport_obj = '';
260
261     if ($config{'AUTH_MODE'} eq 'ULOG_PCAP') {
262         ### The ulogd pcap writer does not include link layer information
263         $ip = NetPacket::IP->decode($pkt) or return '';
264     } else {
265         $ether_data = NetPacket::Ethernet::strip($pkt) or return '';
266         $ip = NetPacket::IP->decode($ether_data) or return '';
267     }
268
269     ### get the source IP address
270     $src_ip = $ip->{'src_ip'} or return '';
271
272     ### get the protocol
273     $proto = $ip->{'proto'} or return '';
274
275     if ($proto == 1) {
276         $transport_obj = NetPacket::ICMP->decode($ip->{'data'});
277     } elsif ($proto == 6) {
278         $transport_obj = NetPacket::TCP->decode($ip->{'data'});
279     } elsif ($proto == 17) {
280         $transport_obj = NetPacket::UDP->decode($ip->{'data'});
281     } else {
282         return '';
283     }
284
285     print STDERR "[+] Received packet (" if $debug;
286
287     ### make sure we have _some_ data in the packet; in practice
288     ### any valid SPA message will be longer than 10 bytes, but this
289     ### check is better than nothing
290     if (defined $transport_obj->{'data'}) {
291         my $len = length($transport_obj->{'data'});
292         if ($len > 10) {
293             print STDERR "$len bytes)\n" if $debug;
294         } else {
295             print STDERR "$len bytes, not attempting decrypt)\n" if $debug;
296             return '';
297         }
298     } else {
299         return '';
300     }
301
302     if ($debug) {
303         ### make sure not to print non-printable stuff
304         my $data_tmp = $transport_obj->{'data'};
305         $data_tmp =~ s/[^\x20-\x7e]/NA/g;
306         print STDERR "[+] Received data: $data_tmp\n"
307             if $debug;
308     }
309
310     ### first check to see if we have any matching access directives
311     ### (in access.conf) for $src_ip, and if not we will do _nothing_
312     ### with this packet.
313     my $access_nums_aref = &check_src($src_ip);
314
315     unless ($access_nums_aref) {
316         print STDERR "[-] Packet from $src_ip did not match any ",
317               "SOURCE blocks in $config{'ACCESS_CONF'}\n" if $debug;
318         return '';
319     }
320
321     NUM: for my $num (@$access_nums_aref) {
322         my $access_vars_href = $access[$num];
323
324         next NUM unless $access_vars_href->{'DATA_COLLECT_MODE'} =~ /PCAP/;
325
326         print STDERR Dumper $access_vars_href if $debug and $verbose;
327
328         ### keep track of which source block we are dealing with from
329         ### access.conf
330         my $source_block_num = $access_vars_href->{'block_num'};
331
332         ### see if we can decrypt and base64-decode
333         my $decrypted_msg = '';
334         my $decrypt_algo  = 'Rijndael';
335         my $gpg_sign_id   = '';
336         if (defined $access_vars_href->{'GPG_REMOTE_ID'}) {
337             ### attempt GPG decrypt
338             ($decrypted_msg, $gpg_sign_id) =
339                     &pcap_GPG_decrypt_msg($transport_obj->{'data'},
340                             $access_vars_href);
341             $decrypt_algo = 'GPG' if $decrypted_msg;
342         }
343
344         if (defined $access_vars_href->{'KEY'} and not $decrypted_msg) {
345             $decrypted_msg = &pcap_Rijndael_decrypt_msg($transport_obj->{'data'},
346                     $access_vars_href->{'KEY'});
347         }
348
349         if ($decrypted_msg) {
350             if ($debug) {
351                 ### make sure not to print non-printable stuff
352                 my $dec_tmp_msg = $decrypted_msg;
353                 $dec_tmp_msg =~ s/[^\x20-\x7e]/NA/g;
354                 print STDERR "[+] Decrypted message: $dec_tmp_msg\n";
355             }
356         } else {
357             print STDERR "[-] Failed decrypt for SOURCE block ",
358                 "$access_vars_href->{'SOURCE'}\n" if $debug;
359             next NUM;
360         }
361
362         ### compare with md5 store (note we calculated the md5 sum
363         ### against the original encrypted packet since we want to
364         ### allow the same command to be executed; randomness in
365         ### the encrypted packet changes the md5 sum, we just don't
366         ### want the exact same encrypted packet to be replayed).
367         my $md5sum = md5_base64($decrypted_msg);
368
369         if (defined $md5_msg_store{$md5sum}) {
370             ### Bad!  Just return.
371             &logr('[-]', "attempted message replay from: $src_ip", 1);
372             return;
373         }
374
375         ### store the md5 sum in memory and (optionally) in the
376         ### disk cache
377         $md5_msg_store{$md5sum} = '';
378
379         &diskwrite_md5_sum($md5sum)
380             if $config{'ENABLE_MD5_PERSISTENCE'} eq 'Y';
381
382         ### see if we have a valid message
383         my $msg_href = &pcap_validate_msg($decrypted_msg);
384
385         unless ($msg_href) {
386             print STDERR "[-] Shared key mis-match or broken message ",
387                 "checksum for SOURCE $access_vars_href->{'SOURCE'}\n"
388                 if $debug;
389             next NUM;
390         }
391
392         print STDERR "[+] Packet fields:\n",
393             "        Random data: $msg_href->{'random_number'}\n",
394             "        Username:    $msg_href->{'username'}\n",
395             "        Remote time: $msg_href->{'remote_time'}\n",
396             "        Remote ver:  $msg_href->{'remote_version'}\n",
397             "        Action type: $msg_href->{'action_type'}\n",
398             "        Action:      $msg_href->{'action'}\n",
399             "        MD5 sum:     $msg_href->{'md5sum'}\n" if $debug;
400
401         my $server_auth_type = '';
402         my $server_auth_crypt_pw = '';
403         if ($msg_href->{'server_auth'}) {
404             if ($msg_href->{'server_auth'} =~ /^\s*(\w+),(.*)/) {
405                 $server_auth_type = lc($1);
406                 $server_auth_crypt_pw = $2;
407                 if ($debug) {
408                     print STDERR "        Server auth: $server_auth_type,";
409                     for (my $i=0; $i<length($server_auth_crypt_pw); $i++) {
410                         print STDERR '*';
411                     }
412                     print STDERR "\n";
413                 }
414             }
415         }
416
417         if (defined $access_vars_href->{'REQUIRE_USERNAME'}) {
418             unless (defined $required_users{$msg_href->{'username'}}) {
419                 &logr('[-]', "username mismatch from $src_ip, expecting " .
420                     "$access_vars_href->{'REQUIRE_USERNAME'}, got " .
421                     "$msg_href->{'username'}", 0);
422                 next NUM;
423             }
424         }
425
426         if (defined $access_vars_href->{'REQUIRE_AUTH_METHOD'}) {
427             if ($server_auth_type
428                     eq $access_vars_href->{'REQUIRE_AUTH_METHOD'}) {
429                 if ($server_auth_type eq 'crypt') {
430                     ### check the local UNIX crypt() password associated
431                     ### with the user
432                     unless (&server_auth_verify_crypt_pw(
433                                 $msg_href->{'username'},
434                                 $server_auth_crypt_pw,
435                                 $access_vars_href->{'SHADOW_FILE'})) {
436                         &logr('[-]', "IP: $src_ip failed server-auth UNIX " .
437                             "crypt() password test", 0);
438                         next NUM;
439                     }
440                 }
441             } else {
442                 &logr('[-]', "required server-auth method " .
443                     "\"$access_vars_href->{'REQUIRE_AUTH_METHOD'}\" " .
444                     "not supplied by $src_ip", 0);
445                 next NUM;
446             }
447         }
448
449         ### all criteria met; grant access or execute command
450         if ($decrypt_algo eq 'GPG') {
451             if (defined $access_vars_href->{'GPG_REMOTE_ID'}) {
452                 &logr('[+]', "received valid $decrypt_algo encrypted packet " .
453                     "(signed with required key ID: $gpg_sign_id) from: " .
454                     "$src_ip, remote user: $msg_href->{'username'}", 0);
455             } else {
456                 &logr('[+]', "received valid $decrypt_algo encrypted packet " .
457                     "from: $src_ip, remote user: $msg_href->{'username'}", 0);
458             }
459         } else {
460             &logr('[+]', "received valid $decrypt_algo encrypted " .
461                 "packet from: $src_ip, remote user: $msg_href->{'username'}",
462                 0);
463         }
464
465         if ($msg_href->{'action_type'} == $access_mode) {
466             if (not $access_vars_href->{'DISABLE_FW_ACCESS'}) {
467                 if (not $access_vars_href->{'PERMIT_CLIENT_PORTS'}) {
468
469                     ### we don't allow the client to influence which ports
470                     ### are opened; only those that are already defined in
471                     ### OPEN_PORTS will be opened
472                     if ($msg_href->{'action'} =~ /($ip_re)/) {
473                         my $allow_ip = $1;
474
475                         $allow_ip = $src_ip if $allow_ip eq '0.0.0.0';
476
477                         &grant_access($allow_ip, '', $access_vars_href);
478                     }
479                 } else {
480                     if ($msg_href->{'action'}
481                             =~ /($ip_re),(tcp|udp|icmp),(\d+)/i) {
482                         ### single port access format (e.g. tcp,22)
483                         my $allow_ip        = $1;
484                         my $dec_allow_port  = $2;
485                         my $dec_allow_proto = $3;
486
487                         $allow_ip = $src_ip if $allow_ip eq '0.0.0.0';
488
489                         $access_vars_href->{'OPEN_PORTS'}
490                             ->{$dec_allow_proto}->{$dec_allow_port} = '';
491
492                         &grant_access($allow_ip, '', $access_vars_href);
493
494                     } elsif ($msg_href->{'action'}
495                              =~ /($ip_re),none,0/) {
496                         my $allow_ip   = $1;
497                         $allow_ip = $src_ip if $allow_ip eq '0.0.0.0';
498
499                         &grant_access($allow_ip, '', $access_vars_href);
500
501                     } elsif ($msg_href->{'action'}
502                              =~ /($ip_re),(\S+)/) {
503                         ### multi-port access format (-A was specified by
504                         ### the client)
505                         my $allow_ip   = $1;
506                         my $access_str = $2;
507
508                         $allow_ip = $src_ip if $allow_ip eq '0.0.0.0';
509                         my @dec_allow_ports = split /,/, $access_str;
510
511                         for my $port_str (@dec_allow_ports) {
512                             if ($port_str =~ m|(\D+)/(\d+)|) {
513                                 my $proto = lc($1);
514                                 my $port  = $2;
515
516                                 next unless ($proto eq 'tcp'
517                                     or $proto eq 'udp'
518                                     or $proto eq 'icmp');
519                                 $port = 0 if $proto eq 'icmp';
520                                 $access_vars_href->{'OPEN_PORTS'}
521                                     ->{$proto}->{$port} = '';
522                             }
523                         }
524                         &grant_access($allow_ip, '', $access_vars_href);
525                     }
526                 }
527             } else {
528                 &logr('[-]', "received fw access request from $src_ip, " .
529                     "but DISABLE_FW_ACCESS is set to a true value", 0);
530             }
531         } elsif ($msg_href->{'action_type'} == $command_mode) {
532             if ($access_vars_href->{'ENABLE_CMD_EXEC'}) {
533                 my $regex_match = 1;
534                 if (defined $access_vars_href->{'CMD_REGEX'}) {
535                     $regex_match = 0 unless $msg_href->{'action'}
536                         =~ /$access_vars_href->{'CMD_REGEX'}/;
537                 }
538                 if ($regex_match) {
539                     ### execute the command
540                     &logr('[+]',
541                         qq|executing command "$msg_href->{'action'}" | .
542                         "for $src_ip", 1);
543                     &exec_command($msg_href->{'action'});
544                 } else {
545                     &logr('[-]',
546                         qq|received command "$msg_href->{'action'}" | .
547                             "from $src_ip but CMD_REGEX did not " .
548                             "match $src_ip", 1);
549                 }
550             } else {
551                 &logr('[-]',
552                     "received command \"$msg_href->{'action'}\" " .
553                         "but command mode not enabled for $src_ip", 1);
554             }
555         }
556     }
557     return;
558 }
559
560 sub server_auth_verify_crypt_pw() {
561     my ($username, $pw, $shadow_file) = @_;
562
563     unless (-e $shadow_file) {
564         &logr('[-]', "shadow file $shadow_file does not exist", 0);
565         return 0;
566     }
567
568     my $shadow_hash = '';
569     open S, "< $shadow_file" or die "[*] Could not open $shadow_file: $!";
570     while (<S>) {
571         my $line = $_;
572         if ($line =~ /^\s*$username:(\S+?):/) {
573             $shadow_hash = $1;
574         }
575     }
576     close S;
577
578     ### mbr:$1$nrU****************************:13108:0:99999:7:::
579     unless ($shadow_hash) {
580         &logr('[-]', "could not get password entry for $username " .
581             "from /etc/shadow", 0);
582         return 0;
583     }
584
585     return 1 if (crypt($pw, $shadow_hash) eq $shadow_hash);
586     return 0;
587 }
588
589 sub knock_loop() {
590     print STDERR "[+] Opening $config{'FW_DATA_FILE'}, and entering main loop.\n"
591         if $debug;
592
593     ### main server loop
594     open FWLOG, $config{'FW_DATA_FILE'} or die $!;
595     for (;;) {
596         my @fw_pkts = <FWLOG>;
597         if (@fw_pkts and ($os_fprint_only or $skipped_first_loop)) {
598             &process_pkts(\@fw_pkts);
599         }
600
601         @fw_pkts = ();
602         $skipped_first_loop = 1 unless $skipped_first_loop;
603
604         ### always check to see if we need to timeout knock sequences
605         ### that exceed the KNOCK_INTERVAL
606         &timeout_invalid_sequences();
607
608         ### always check to see if we need to timeout access for IPs
609         &timeout_access();
610
611         if ($die_msg) {
612             open D, ">> $config{'FWKNOP_DIR'}/fwknopd.die" or
613                 die "[*] Could not open $config{'FWKNOP_DIR'}/fwknopd.die: $!";
614             print D scalar localtime(), " $die_msg";
615             close D;
616             $die_msg = '';
617         }
618
619         if ($warn_msg) {
620             open D, ">> $config{'FWKNOP_DIR'}/fwknopd.warn" or
621                 die "[*] Could not open $config{'FWKNOP_DIR'}/fwknopd.warn: $!";
622             print D scalar localtime(), " $warn_msg";
623             close D;
624             $warn_msg = '';
625         }
626
627         ### clearerr() on the FWLOG filehandle to be ready for new packets
628         FWLOG->clearerr();
629
630         sleep $config{'SLEEP_INTERVAL'};
631     }
632     close FWLOG;
633     return;
634 }
635
636
637 sub pcap_validate_msg() {
638     my $msg = shift;
639
640     my %msg = ();
641     my $md5sum = '';
642     my $server_auth = '';
643     my @fields = split /:/, $msg;
644
645     return '' unless @fields;
646
647     my $random_number  = $fields[0] || return '';
648     my $username       = $fields[1] || return '';
649     my $remote_time    = $fields[2] || return '';
650     my $remote_version = $fields[3] || return '';
651     my $action_type    = $fields[4];
652     my $action         = $fields[5] || return '';
653     $md5sum            = $fields[6] || return '';
654     if ($remote_version =~ /0\.9\.(\d+)/) {
655         my $minor_ver_num = $1;
656         if ($minor_ver_num > 2 and $#fields > 6) {
657             $server_auth = $fields[7] || '';
658         }
659     }
660
661     return '' unless $action_type == $command_mode
662         or $action_type == $access_mode;
663
664     %msg = (
665         'random_number'  => $random_number,
666         'username'       => $username,
667         'remote_time'    => $remote_time,
668         'remote_version' => $remote_version,
669         'action_type'    => $action_type,
670         'action'         => $action,
671         'md5sum'         => $md5sum,
672     );
673
674     if ($server_auth) {
675         $msg{'server_auth'} = $server_auth;
676     } else {
677         $msg{'server_auth'} = '';
678     }
679
680     print STDERR Dumper \%msg if $debug and $verbose;
681
682     ### validate message
683     if (&check_md5sum(\%msg)) {
684         $msg{'username'}    = decode_base64($msg{'username'});
685         $msg{'action'}      = decode_base64($msg{'action'});
686         $msg{'server_auth'} = decode_base64($msg{'server_auth'});
687
688         if ($debug) {
689             print STDERR "[+] Decoded message: $msg{'random_number'}:",
690                 "$msg{'username'}:$msg{'remote_time'}:",
691                 "$msg{'remote_version'}:$msg{'action_type'}:",
692                 "$msg{'action'}:$msg{'md5sum'}";
693             if ($msg{'server_auth'} and $msg{'server_auth'} =~ /^\s*(\w+),(.*)/) {
694                 print STDERR ":$1,";
695                 for (my $i=0; $i<length($2); $i++) {
696                     print STDERR "*";
697                 }
698                 print STDERR "\n";
699             } else {
700                 print STDERR "\n";
701             }
702         }
703         return \%msg;
704     }
705     return '';
706 }
707
708 sub check_md5sum() {
709     my $msg_href = shift;
710
711     my $md5sum = $msg_href->{'md5sum'};
712
713     my $data_str = '';
714     for my $key qw(
715         random_number
716         username
717         remote_time
718         remote_version
719         action_type
720         action
721     ) {
722         $data_str .= $msg_href->{$key} . ':';
723     }
724
725     $data_str =~ s/:$//;
726
727     if ($msg_href->{'server_auth'}) {
728         $data_str .= ':' . $msg_href->{'server_auth'};
729     }
730
731     return 1 if $md5sum eq md5_base64($data_str);
732     return 0;
733 }
734
735 sub get_pcap_obj() {
736
737     my $pcap_t  = '';
738     my $filter  = '';
739     my $err     = '';
740     my $netmask = '';
741     my $address = '';
742
743     if ($config{'AUTH_MODE'} eq 'FILE_PCAP'
744             or $config{'AUTH_MODE'} eq 'ULOG_PCAP') {
745
746         unless (-e $config{'PCAP_PKT_FILE'}) {
747             &pcap_file_exists_loop();
748         }
749
750         unless (-s $config{'PCAP_PKT_FILE'} > 0) {
751             ### required since we cannot use Net::Pcap::open_offline()
752             ### to open a zero-size pcap file.
753             &pcap_nonzero_size_loop();
754         }
755
756         print STDERR "[+] Acquiring packet data from file: ",
757             "$config{'PCAP_PKT_FILE'}\n" if $debug;
758
759         $pcap_t = Net::Pcap::open_offline($config{'PCAP_PKT_FILE'}, \$err)
760             or die "[*] Could not open $config{'PCAP_PKT_FILE'}: $err";
761
762