root/honeyclient/branches/bug/42/lib/HoneyClient/Agent/Integrity/Registry/Parser.pm

Revision 123, 50.2 kB (checked in by kindlund, 2 years ago)

Completed alpha version of bug fix. Still have to test it out on our test VM network.

  • Property svn:keywords set to Id "$file"
Line 
1 ####################################################################
2 #
3 #    This file was generated using Parse::Yapp version 1.05.
4 #
5 #        Don't edit this file, use source file instead.
6 #
7 #             ANY CHANGE MADE HERE WILL BE LOST !
8 #
9 ####################################################################
10 package HoneyClient::Agent::Integrity::Registry::Parser;
11 use vars qw ( @ISA );
12 use strict;
13
14 @ISA= qw ( Parse::Yapp::Driver );
15 use Parse::Yapp::Driver;
16
17 #line 1 "Parser.yp"
18
19 #######################################################################
20 # Created on:  Dec 10, 2006
21 # Package:     HoneyClient::Agent::Integrity::Registry::Parser
22 # File:        Parser.pm
23 # Description: Parses static hive dumps of the Windows OS registry.
24 #
25 # CVS: $Id$
26 #
27 # @author kindlund
28 #
29 # Copyright (C) 2006 The MITRE Corporation.  All rights reserved.
30 #
31 # This program is free software; you can redistribute it and/or
32 # modify it under the terms of the GNU General Public License
33 # as published by the Free Software Foundation, using version 2
34 # of the License.
35 #
36 # This program is distributed in the hope that it will be useful,
37 # but WITHOUT ANY WARRANTY; without even the implied warranty of
38 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
39 # GNU General Public License for more details.
40 #
41 # You should have received a copy of the GNU General Public License
42 # along with this program; if not, write to the Free Software
43 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
44 # 02110-1301, USA.
45 #
46 #######################################################################
47
48 =pod
49
50 =head1 NAME
51
52 HoneyClient::Agent::Integrity::Registry::Parser - Perl extension to parse
53 static hive dumps of the Windows OS registry.
54
55 =head1 VERSION
56
57 This documentation refers to HoneyClient::Agent::Integrity::Registry::Parser version 1.0.
58
59 =head1 SYNOPSIS
60
61   use HoneyClient::Agent::Integrity::Registry::Parser;
62   use IO::File;
63   use Data::Dumper;
64
65   # Initialize the parser object.
66   my $parser = HoneyClient::Agent::Integrity::Registry::Parser->init(
67                    input_file => "dump.reg",
68                );
69
70   # Print each registry group found, until there are no more left.
71   my $registryGroup = $parser->nextGroup();
72   while(scalar(keys(%{$registryGroup}))) {
73       print Dumper($registryGroup);
74       $registryGroup = $parser->nextGroup();
75   }
76
77   # $registryGroup refers to hashtable reference, which has the
78   # following format:
79   #
80   # $registryGroup = {
81   #     # The registry directory name.
82   #     'key' => 'HKEY_LOCAL_MACHINE\Software...',
83   #
84   #     # An array containing the list of entries within the
85   #     # registry directory.
86   #     'entries'  => [ {
87   #         'name' => "\"string\"",  # A (potentially) quoted string;
88   #                                  # "@" for default
89   #         'value' => "data",
90   #     }, ],
91   # };
92
93 =head1 DESCRIPTION
94
95 This library allows the Registry module to easily parse and enumerate
96 each Windows OS registry hive.
97
98 =cut
99
100 use strict;
101 use warnings;
102 use Carp ();
103
104 # Include Global Configuration Processing Library
105 use HoneyClient::Util::Config qw(getVar);
106
107 # Include Logging Library
108 use Log::Log4perl qw(:easy);
109
110 # Use Dumper Library.
111 use Data::Dumper;
112
113 # Use IO File Library.
114 use IO::File;
115
116 # Use Seek Library.
117 use Fcntl qw(:seek);
118
119 # Use Binary Search Library.
120 use Search::Binary;
121
122 # Use Progress Bar Library.
123 use Term::ProgressBar;
124
125 #######################################################################
126 # Module Initialization                                               #
127 #######################################################################
128
129 BEGIN {
130     # Defines which functions can be called externally.
131     require Exporter;
132     our (@ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS, $VERSION);
133
134     # Set our package version.
135     $VERSION = 0.9;
136
137     @ISA = qw(Exporter);
138
139     # Symbols to export on request
140     @EXPORT = qw( );
141
142     # Items to export into callers namespace by default. Note: do not export
143     # names by default without a very good reason. Use EXPORT_OK instead.
144     # Do not simply export all your public functions/methods/constants.
145
146     # This allows declaration use HoneyClient::Agent::Integrity::Registry ':all';
147     # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
148     # will save memory.
149
150     %EXPORT_TAGS = (
151         'all' => [ qw( ) ],
152     );
153
154     # Symbols to autoexport (:DEFAULT tag)
155     @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
156
157     $SIG{PIPE} = 'IGNORE'; # Do not exit on broken pipes.
158 }
159 our (@EXPORT_OK, $VERSION);
160
161 =pod
162
163 =begin testing
164
165 # Make sure Log::Log4perl loads
166 BEGIN { use_ok('Log::Log4perl', qw(:nowarn))
167         or diag("Can't load Log::Log4perl package. Check to make sure the package library is correctly listed within the path.");
168        
169         # Suppress all logging messages, since we need clean output for unit testing.
170         Log::Log4perl->init({
171             "log4perl.rootLogger"                               => "DEBUG, Buffer",
172             "log4perl.appender.Buffer"                          => "Log::Log4perl::Appender::TestBuffer",
173             "log4perl.appender.Buffer.min_level"                => "fatal",
174             "log4perl.appender.Buffer.layout"                   => "Log::Log4perl::Layout::PatternLayout",
175             "log4perl.appender.Buffer.layout.ConversionPattern" => "%d{yyyy-MM-dd HH:mm:ss} %5p [%M] (%F:%L) - %m%n",
176         });
177 }
178 require_ok('Log::Log4perl');
179 use Log::Log4perl qw(:easy);
180
181 # Make sure the module loads properly, with the exportable
182 # functions shared.
183 BEGIN { use_ok('HoneyClient::Util::Config', qw(getVar setVar))
184         or diag("Can't load HoneyClient::Util::Config package.  Check to make sure the package library is correctly listed within the path."); }
185 require_ok('HoneyClient::Util::Config');
186 can_ok('HoneyClient::Util::Config', 'getVar');
187 can_ok('HoneyClient::Util::Config', 'setVar');
188 use HoneyClient::Util::Config qw(getVar setVar);
189
190 # Suppress all logging messages, since we need clean output for unit testing.
191 Log::Log4perl->init({
192     "log4perl.rootLogger"                               => "DEBUG, Buffer",
193     "log4perl.appender.Buffer"                          => "Log::Log4perl::Appender::TestBuffer",
194     "log4perl.appender.Buffer.min_level"                => "fatal",
195     "log4perl.appender.Buffer.layout"                   => "Log::Log4perl::Layout::PatternLayout",
196     "log4perl.appender.Buffer.layout.ConversionPattern" => "%d{yyyy-MM-dd HH:mm:ss} %5p [%M] (%F:%L) - %m%n",
197 });
198
199 # Make sure Data::Dumper loads
200 BEGIN { use_ok('Data::Dumper')
201         or diag("Can't load Data::Dumper package. Check to make sure the package library is correctly listed within the path."); }
202 require_ok('Data::Dumper');
203 use Data::Dumper;
204
205 # Make sure IO::File loads
206 BEGIN { use_ok('IO::File')
207         or diag("Can't load IO::File package. Check to make sure the package library is correctly listed within the path."); }
208 require_ok('IO::File');
209 use IO::File;
210
211 # Make sure Fcntl loads
212 BEGIN { use_ok('Fcntl')
213         or diag("Can't load Fcntl package. Check to make sure the package library is correctly listed within the path."); }
214 require_ok('Fcntl');
215 use Fcntl qw(:seek);
216
217 # Make sure Search::Binary loads
218 BEGIN { use_ok('Search::Binary')
219         or diag("Can't load Search::Binary package. Check to make sure the package library is correctly listed within the path."); }
220 require_ok('Search::Binary');
221 can_ok('Search::Binary', 'binary_search');
222 use Search::Binary;
223
224 # Make sure Term::ProgressBar loads
225 BEGIN { use_ok('Term::ProgressBar')
226         or diag("Can't load Term::ProgressBar package. Check to make sure the package library is correctly listed within the path."); }
227 require_ok('Term::ProgressBar');
228 use Term::ProgressBar;
229
230 # Make sure HoneyClient::Agent::Integrity::Registry::Parser loads
231 BEGIN { use_ok('HoneyClient::Agent::Integrity::Registry::Parser')
232         or diag("Can't load HoneyClient::Agent::Integrity::Registry::Parser package. Check to make sure the package library is correctly listed within the path."); }
233 require_ok('HoneyClient::Agent::Integrity::Registry::Parser');
234 use HoneyClient::Agent::Integrity::Registry::Parser;
235
236 =end testing
237
238 =cut
239
240 #######################################################################
241 # Global Configuration Variables
242 #######################################################################
243
244 # The global logging object.
245 our $LOG = get_logger();
246
247 # Make Dumper format more terse.
248 $Data::Dumper::Terse = 1;
249 $Data::Dumper::Indent = 1;
250
251
252
253 sub new {
254         my($class)=shift;
255         ref($class)
256     and $class=ref($class);
257
258     my($self)=$class->SUPER::new( yyversion => '1.05',
259                                   yystates =>
260 [
261     {#State 0
262         ACTIONS => {
263             'DIR_NAME' => 2,
264             'HEADER' => 5,
265             'NEWLINE' => 6
266         },
267         DEFAULT => -1,
268         GOTOS => {
269             'group' => 1,
270             'registry' => 3,
271             'groups' => 4
272         }
273     },
274     {#State 1
275         DEFAULT => -4
276     },
277     {#State 2
278         ACTIONS => {
279             'KEY_NAME' => 7
280         },
281         DEFAULT => -9,
282         GOTOS => {
283             'entry' => 8,
284             'entries' => 9
285         }
286     },
287     {#State 3
288         ACTIONS => {
289             '' => 10
290         }
291     },
292     {#State 4
293         DEFAULT => -2
294     },
295     {#State 5
296         ACTIONS => {
297             'DIR_NAME' => 2,
298             'NEWLINE' => 6
299         },
300         GOTOS => {
301             'group' => 1,
302             'groups' => 11
303         }
304     },
305     {#State 6
306         ACTIONS => {
307             'DIR_NAME' => 2
308         },
309         GOTOS => {
310             'group' => 12
311         }
312     },
313     {#State 7
314         ACTIONS => {
315             'KEY_VALUE' => 13
316         }
317     },
318     {#State 8
319         ACTIONS => {
320             'KEY_NAME' => 7
321         },
322         DEFAULT => -10,
323         GOTOS => {
324             'entry' => 8,
325             'entries' => 14
326         }
327     },
328     {#State 9
329         DEFAULT => -8
330     },
331     {#State 10
332         DEFAULT => 0
333     },
334     {#State 11
335         DEFAULT => -3
336     },
337     {#State 12
338         ACTIONS => {
339             'DIR_NAME' => 2,
340             'NEWLINE' => 16
341         },
342         DEFAULT => -5,
343         GOTOS => {
344             'group' => 1,
345             'groups' => 15
346         }
347     },
348     {#State 13
349         DEFAULT => -12
350     },
351     {#State 14
352         DEFAULT => -11
353     },
354     {#State 15
355         DEFAULT => -7
356     },
357     {#State 16
358         ACTIONS => {
359             'DIR_NAME' => 2
360         },
361         DEFAULT => -6,
362         GOTOS => {
363             'group' => 12
364         }
365     }
366 ],
367                                   yyrules  =>
368 [
369     [#Rule 0
370          '$start', 2, undef
371     ],
372     [#Rule 1
373          'registry', 0,
374 sub
375 #line 247 "Parser.yp"
376 {
377             $LOG->debug("Reached end of input stream.");
378             # Finished parsing the entire file, return empty hash ref.
379             return { };
380         }
381     ],
382     [#Rule 2
383          'registry', 1,
384 sub
385 #line 252 "Parser.yp"
386 {
387             $LOG->debug("Reached end of input stream.");
388             # Finished parsing the entire file, return empty hash ref.
389             return { };
390         }
391     ],
392     [#Rule 3
393          'registry', 2,
394 sub
395 #line 257 "Parser.yp"
396 {
397             $LOG->debug("Reached end of input stream.");
398             # Finished parsing the entire file, return empty hash ref.
399             return { };
400         }
401     ],
402     [#Rule 4
403          'groups', 1, undef
404     ],
405     [#Rule 5
406          'groups', 2, undef
407     ],
408     [#Rule 6
409          'groups', 3, undef
410     ],
411     [#Rule 7
412          'groups', 3, undef
413     ],
414     [#Rule 8
415          'group', 2,
416 sub
417 #line 274 "Parser.yp"
418 {
419             my $ret = { };
420             $_[0]->YYData->{'latest_group'}->{'key'} = $_[1];
421             if (!exists($_[0]->YYData->{'latest_group'}->{'entries'})) {
422                 # Make sure the 'entries' key exists.
423                 $_[0]->YYData->{'latest_group'}->{'entries'} = [];
424             }
425             $ret = $_[0]->YYData->{'latest_group'};
426             $_[0]->YYData->{'latest_group'} = { };
427             $_[0]->YYData->{'dir_count'}++;
428             $_[0]->YYAccept; # Terminate the parse, early.
429
430             return $ret;
431         }
432     ],
433     [#Rule 9
434          'group', 1,
435 sub
436 #line 288 "Parser.yp"
437 {
438             my $ret = { };
439             $_[0]->YYData->{'latest_group'}->{'key'} = $_[1];
440             if (!exists($_[0]->YYData->{'latest_group'}->{'entries'})) {
441                 # Make sure the 'entries' key exists.
442                 $_[0]->YYData->{'latest_group'}->{'entries'} = [];
443             }
444             $ret = $_[0]->YYData->{'latest_group'};
445             $_[0]->YYData->{'latest_group'} = { };
446             $_[0]->YYData->{'dir_count'}++;
447             $_[0]->YYAccept; # Terminate the parse, early.
448
449             return $ret;
450         }
451     ],
452     [#Rule 10
453          'entries', 1, undef
454     ],
455     [#Rule 11
456          'entries', 2, undef
457     ],
458     [#Rule 12
459          'entry', 2,
460 sub
461 #line 312 "Parser.yp"
462 {
463             my $entry = {
464                 name  => $_[1],
465                 value => $_[2],
466             };
467             push(@{$_[0]->YYData->{'latest_group'}->{entries}}, $entry);
468             $_[0]->YYData->{'entry_count'}++;
469         }
470     ]
471 ],
472                                   @_);
473     bless($self,$class);
474 }
475
476 #line 322 "Parser.yp"
477
478
479 #######################################################################
480 # Private Methods Implemented                                         #
481 #######################################################################
482
483 # Helper function, designed to tokenize specific data from the input stream.
484 #
485 # Inputs: parser
486 # Outputs: (token_id, data) pair
487 sub _lexer {
488     # Identify NEWLINE token.
489     if ($_[0]->YYData->{DATA} =~ m/\G\n/cg) {
490         $_[0]->YYData->{'in_group'} = 0;
491         $LOG->debug("Found NEWLINE token ending at offset (" . pos($_[0]->YYData->{DATA}) . ").");
492         $_[0]->YYData->{'line_count'}++;
493         return ("NEWLINE", "\n");
494     }
495
496     # Check to see if we're inside a group block...
497     if (!$_[0]->YYData->{'in_group'}) {
498
499         $_[0]->YYData->{'input_pos'} = pos($_[0]->YYData->{DATA});
500         $_[0]->YYData->{'input_pos'} = $_[0]->YYData->{'input_pos'} ?
501                                        $_[0]->YYData->{'input_pos'} : 0;
502         $_[0]->YYData->{'input_pos'} = $_[0]->YYData->{'input_pos'} +
503                                        $_[0]->YYData->{'abs_offset'};
504
505         # Update progress bar, if defined.
506         if (defined($_[0]->YYData->{'progress'}) &&
507             ($_[0]->YYData->{'input_pos'} > $_[0]->YYData->{'progress_next_update'})) {
508             $_[0]->YYData->{'progress_next_update'} =
509                 $_[0]->YYData->{'progress'}->update($_[0]->YYData->{'input_pos'});
510         }
511
512         # Identify DIR_NAME token.
513         if ($_[0]->YYData->{DATA} =~ m/\G\[(.*)\]\n/cg) {
514             $_[0]->YYData->{'in_group'} = 1;
515             $LOG->debug("Found DIR_NAME token ending at offset (" . pos($_[0]->YYData->{DATA}) . ").");
516             $_[0]->YYData->{'last_group_line_number'} = $_[0]->YYData->{'line_count'};
517             $_[0]->YYData->{'line_count'}++;
518             return ("DIR_NAME", $1);
519         }
520
521         # Identify HEADER token. It's always only at the beginning.
522         if ($_[0]->YYData->{DATA} =~ m/\GREGEDIT4\n/cg) {
523             $LOG->debug("Found HEADER token ending at offset (" . pos($_[0]->YYData->{DATA}) . ").");
524             $_[0]->YYData->{'line_count'}++;
525             return ("HEADER", "REGEDIT4\n");
526         }
527
528     } else {
529
530         # Check to see if we're in a value segment...
531         if (!$_[0]->YYData->{'in_value'}) {
532
533             # Identify KEY_NAME token.
534             if ($_[0]->YYData->{DATA} =~ m/\G\"(|[^\\]|.*(?:\\[^\\]|\\\\|[^\\][^\\]))\"(?==)/cg) {
535                 $_[0]->YYData->{'in_value'} = 1;
536                 $LOG->debug("Found KEY_NAME token ending at offset (" . pos($_[0]->YYData->{DATA}) . ").");
537                 return ("KEY_NAME", $1);
538             }
539
540             # Identify default KEY_NAME token (@).
541             if ($_[0]->YYData->{DATA} =~ m/\G\@(?==)/cg) {
542                 $_[0]->YYData->{'in_value'} = 1;
543                 $LOG->debug("Found KEY_NAME token ending at offset (" . pos($_[0]->YYData->{DATA}) . ").");
544                 return ("KEY_NAME", "@");
545             }
546
547         } else {
548
549             # Identify string KEY_VALUE token.
550             if ($_[0]->YYData->{DATA} =~ m/\G=\"(|[^\\]|.*?(?:\\[^\\]|\\\\|[^\\][^\\]))\"\n/cgs) {
551                 $_[0]->YYData->{'in_value'} = 0;
552                 $LOG->debug("Found KEY_VALUE token ending at offset (" . pos($_[0]->YYData->{DATA}) . ").");
553                 $_[0]->YYData->{'line_count'} += 1 + @{[$1 =~ /\n/g]};
554                 return ("KEY_VALUE", $1);
555             }
556
557             # Identify binary KEY_VALUE token.
558             if ($_[0]->YYData->{DATA} =~ m/\G=(|.*?[^\\])\n/cgs) {
559                 $_[0]->YYData->{'in_value'} = 0;
560                 $LOG->debug("Found KEY_VALUE token ending at offset (" . pos($_[0]->YYData->{DATA}) . ").");
561                 $_[0]->YYData->{'line_count'} += 1 + @{[$1 =~ /\n/g]};
562                 return ("KEY_VALUE", $1);
563             }
564         }
565     }
566    
567     # Croak if encountered a token error.
568     if ($_[0]->YYData->{DATA} =~ m/\G(.*\n)/cg) {
569         $_[0]->YYData->{'input_pos'} = pos($_[0]->YYData->{DATA});
570         $LOG->fatal("Error: Unknown token (" . $1 . ") at offset (". $_[0]->YYData->{'input_pos'} .")");
571         Carp::croak("Error: Unknown token (" . $1 . ") at offset (". $_[0]->YYData->{'input_pos'} .")");
572     }
573     return ('', undef);
574 }
575
576 # Helper function, designed to report when any parsing error
577 # occurs.
578 #
579 # Inputs: parser
580 # Outputs: None
581 sub _error {
582
583     $LOG->fatal("Error: Malformed input found at offset (" . $_[0]->YYData->{'input_pos'} . ").");
584     Carp::croak("Error: Malformed input found at offset (" . $_[0]->YYData->{'input_pos'} . ").");
585 }
586
587 # Helper function, designed to reset the parser's file stream back to the
588 # beginning, allowing the parser to reparse from the beginning.  Or, if
589 # specified, the function will seek the parser to the specified offset.
590 #
591 # Inputs: parser, absolute offset (optional)
592 # Outputs: none
593 sub _reset {
594     # Extract arguments.
595     my ($self, $offset) = @_;
596
597     $LOG->debug("Resetting parser.");
598
599     $self->YYData->{'file_handle'} = undef;
600
601     my $fh = new IO::File($self->YYData->{'filename'}, "r");
602     if (!defined($fh)) {
603         $LOG->fatal("Error: Unable to read file '" . $self->YYData->{'filename'} . "'!");
604         Carp::croak("Error: Unable to read file '" . $self->YYData->{'filename'} . "'!");
605     }
606
607     $self->YYData->{'file_handle'} = $fh;
608
609     # Check the offset.
610     if (!defined($offset)) {
611         $offset = 0;
612     }
613     seek($fh, $offset, SEEK_SET);
614
615     undef $/;
616     $self->YYData->{DATA} = <$fh>;
617
618     # Strip all CRs.
619     $self->YYData->{DATA} =~ s/\r//g;
620
621     # Total size of input file.
622     $self->YYData->{'file_size'} = (stat($fh))[7];
623
624     # Reinitialize helper variables.
625     # Hashtable, to represent the latest, extracted group chunk.
626     $self->YYData->{'latest_group'} = { };
627
628     # Boolean, to indicate when we're parsing inside a group chunk.
629     $self->YYData->{'in_group'} = 0;
630
631     # Boolean, to indicate when we're parsing inside a value segment.
632     $self->YYData->{'in_value'} = 0;
633    
634     # Regexp offset, used to record where the parser is within
635     # the file (relative position).
636     $self->YYData->{'input_pos'} = 0;
637
638     # Absolute offset, recording where the parser initially seeked to.
639     $self->YYData->{'abs_offset'} = $offset;
640
641     # Initialize statistics.
642     # Total number of directories parsed.
643     $self->YYData->{'dir_count'} = 0;
644
645     # Total number of key/value pairs parsed.
646     $self->YYData->{'entry_count'} = 0;
647
648     # Total number of lines parsed.
649     $self->YYData->{'line_count'} = 0;
650
651     # Last line number that corresponded to a group separation point.
652     $self->YYData->{'last_group_line_number'} = 0;
653
654     # Progress bar information.
655     if ($self->YYData->{'show_progress'}) {
656         $self->YYData->{'progress'} = Term::ProgressBar->new({ name  => 'Progress',
657                                                                count => $self->YYData->{'file_size'},
658                                                                ETA   => 'linear', });
659         $self->YYData->{'progress'}->minor(0);
660         $self->YYData->{'progress'}->max_update_rate(1);
661         $self->YYData->{'progress_next_update'} = $self->YYData->{'progress'}->update($offset);
662     } else {
663         $self->YYData->{'progress'} = undef;
664     }
665 }
666
667 # Helper function, designed to index all groups, based upon beginning file
668 # offsets.
669 #
670 # Inputs: parser
671 # Outputs: None
672 sub _index {
673     # Extract arguments.
674     my $self = shift;
675
676     $LOG->debug("Starting group index process.");
677
678     $self->YYData->{'group_index_offsets'} = [0, ];
679     $self->YYData->{'group_index_linenums'} = [0, ];
680
681     my $registryGroup = $self->nextGroup();
682     while(scalar(keys(%{$registryGroup}))) {
683         push (@{$self->YYData->{'group_index_offsets'}}, $self->YYData->{'input_pos'});
684         push (@{$self->YYData->{'group_index_linenums'}}, $self->YYData->{'last_group_line_number'});
685         $registryGroup = $self->nextGroup();
686     }
687
688     # Reset the parser.
689     $self->_reset();
690
691     $LOG->debug("Finished group index process.");
692 }
693
694 # Helper function, designed to be called from within the
695 # Search::Binary::binary_search() function, in order to allow
696 # the binary_search to properly read in group index data from
697 # the default parser reference.
698 #
699 # For more information about how this function operates, please
700 # see the Search::Binary POD documentation.
701 #
702 # Inputs: parser, value_to_compare, current_array_index
703 # Outputs: comparison, last_valid_array_index
704 sub _search {
705     # Extract arguments.
706     my ($parser, $value_to_compare, $current_array_index) = @_;
707
708     # Increment the search index, if the current one is undef.
709     if (defined($current_array_index)) {
710         $parser->YYData->{'last_search_index'} = $current_array_index;
711     } else {
712         $parser->YYData->{'last_search_index'}++;
713     }
714
715     # Perform a comparison, if the array entry is defined.
716     # Check to see if the search is for line numbers or offsets.
717     if ($parser->YYData->{'search_is_linenum'}) {
718         if (defined(@{$parser->YYData->{'group_index_linenums'}}[$parser->YYData->{'last_search_index'}])) {
719             return($value_to_compare <=> @{$parser->YYData->{'group_index_linenums'}}[$parser->YYData->{'last_search_index'}],
720                    $parser->YYData->{'last_search_index'});
721         }
722     } else {
723         if (defined(@{$parser->YYData->{'group_index_offsets'}}[$parser->YYData->{'last_search_index'}])) {
724             return($value_to_compare <=> @{$parser->YYData->{'group_index_offsets'}}[$parser->YYData->{'last_search_index'}],
725                    $parser->YYData->{'last_search_index'});
726         }
727     }
728
729     # Array entry not found, return undef with this position.
730     return (undef, $parser->YYData->{'last_search_index'});
731 }
732
733 #######################################################################
734 # Public Methods Implemented                                          #
735 #######################################################################
736
737 =pod
738
739 =head1 METHODS IMPLEMENTED
740
741 The following functions have been implemented by any Parser object.
742
743 =head2 HoneyClient::Agent::Integrity::Registry::Parser->init(input_file => $filename,
744                                                              index_groups => $perform_index,
745                                                              show_progress => $progress)
746
747 =over 4
748
749 Creates a new Parser object, using the specified input file as its data
750 source.
751
752 I<Inputs>:
753  B<$filename> is an required parameter, specifying the file to open for parsing.
754  B<$perform_index> is an optional parameter.  1 specifies that the parser should go
755 ahead and scan the entire file, indexing the file offsets of where groups start and
756 end.  Otherwise, this indexing process is not performed.
757  B<$progress> is an optional parameter.  1 specifies that the parser should display
758 a progress bar, as it scans through a specified file.  Otherwise, a progress bar
759 is not displayed.
760  
761 I<Output>: The instantiated Parser B<$object>, fully initialized.
762
763 =back
764
765 =begin testing
766
767 my $test_registry_file = $ENV{PWD} . "/" . getVar(name      => "registry_file",
768                                                   namespace => "HoneyClient::Agent::Integrity::Registry::Parser::Test");
769
770 # Create a generic Parser object, with test state data.
771 my $parser = HoneyClient::Agent::Integrity::Registry::Parser->init(input_file => $test_registry_file);
772 isa_ok($parser, 'HoneyClient::Agent::Integrity::Registry::Parser', "init(input_file => $test_registry_file)") or diag("The init() call failed.");
773
774 =end testing
775
776 =cut
777
778 sub init {
779
780     # Extract arguments.
781     my ($self, %args) = @_;
782
783     # Log resolved arguments.
784     # Make Dumper format more terse.
785     $Data::Dumper::Terse = 1;
786     $Data::Dumper::Indent = 0;
787     $LOG->debug(Dumper(\%args));
788
789     # Sanity check, don't initialize, unless input_file_handle
790     # was provided.
791     my $argsExist = scalar(%args);
792     if (!$argsExist ||
793         !exists($args{'input_file'}) ||
794         !defined($args{'input_file'})) {
795         $LOG->fatal("Error: Unable to create parser - no 'input_file' specified!");
796         Carp::croak("Error: Unable to create parser - no 'input_file' specified!");
797     }
798
799     my $parser = HoneyClient::Agent::Integrity::Registry::Parser->new();
800     my $fh = new IO::File($args{'input_file'}, "r");
801     if (!defined($fh)) {
802         $LOG->fatal("Error: Unable to read file '" . $args{'input_file'} . "'!");
803         Carp::croak("Error: Unable to read file '" . $args{'input_file'} . "'!");
804     }
805    
806     # Check if show progress was specified.
807     if ($argsExist &&
808         exists($args{'show_progress'}) &&
809         defined($args{'show_progress'}) &&
810         $args{'show_progress'}) {
811         $parser->YYData->{'show_progress'} = 1;
812     } else {
813         $parser->YYData->{'show_progress'} = 0;
814     }
815
816     # Save the file name.
817     $parser->YYData->{'filename'} = $args{'input_file'};
818
819     # Save the file handle.
820     $parser->YYData->{'file_handle'} = $fh;
821
822     # Reset the parser.
823     $parser->_reset();
824
825     # Perform group indexing, if specified.
826     if ($argsExist &&
827         exists($args{'index_groups'}) &&
828         defined($args{'index_groups'}) &&
829         $args{'index_groups'}) {
830         $parser->_index();
831     } else {
832         $parser->YYData->{'group_index_offsets'} = [0, ];
833         $parser->YYData->{'group_index_linenums'} = [0, ];
834     }
835
836     # Return parser object.
837     return $parser;
838 }
839
840 =pod
841
842 =head2 $object->nextGroup()
843
844 =over 4
845
846 Provides the next registry group, in the form of a hashtable reference.
847 This hashtable has the following format:
848