root/fwknop/tags/fwknop-0.9.7-pre1/fwknopd

Revision 480, 109.1 kB (checked in by mbr, 2 years ago)

updated to use default initialization vector strategy from Crypt::CBC, this allow the vulnerability fix in Crypt::CBC 2.18 to take the most effect

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