File Coverage

blib/lib/HoneyClient/Util/Config.pm
Criterion Covered Total %
statement 10 12 83.3
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 14 16 87.5


line stmt bran cond sub pod time code
1             #######################################################################
2             # Created on: Apr 20, 2006
3             # Package: HoneyClient::Util::Config
4             # File: Config.pm
5             # Description: Generic access to the HoneyClient configuration file.
6             #
7             # CVS: $Id: Config.pm 781 2007-07-27 19:15:54Z kindlund $
8             #
9             # @author kindlund, flindiakos
10             #
11             # Copyright (C) 2007 The MITRE Corporation. All rights reserved.
12             #
13             # This program is free software; you can redistribute it and/or
14             # modify it under the terms of the GNU General Public License
15             # as published by the Free Software Foundation, using version 2
16             # of the License.
17             #
18             # This program is distributed in the hope that it will be useful,
19             # but WITHOUT ANY WARRANTY; without even the implied warranty of
20             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21             # GNU General Public License for more details.
22             #
23             # You should have received a copy of the GNU General Public License
24             # along with this program; if not, write to the Free Software
25             # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26             # 02110-1301, USA.
27             #
28             #######################################################################
29              
30             =pod
31              
32             =head1 NAME
33              
34             HoneyClient::Util::Config - Perl extension to provide a generic interface
35             to the HoneyClient global configuration file.
36              
37             =head1 VERSION
38              
39             This documentation refers to HoneyClient::Util::Config version 0.98.
40              
41             =head1 SYNOPSIS
42              
43             use HoneyClient::Util::Config qw(getVar);
44              
45             my $address = undef;
46            
47             # Fetch the value of "address" using the default namespace.
48             $address = getVar(name => "address");
49              
50             # Fetch the value of "address" using the "HoneyClient::Agent::Driver" namespace.
51             $address = getVar(name => "address",
52             namespace => "HoneyClient::Agent::Driver");
53              
54             # Fetch the value of "address" using the "HoneyClient::Manager" namespace.
55             $address = getVar(name => "address",
56             namespace => "HoneyClient::Manager");
57              
58             # Set the value of "address" using the default namespace
59             setVar( name => 'address',
60             value => 'new_address' );
61              
62             # Set the value using a specified namespace
63             setVar( name => 'address',
64             namespace => 'HoneyClient::Agent::Driver',
65             value => 'new_address' );
66              
67             =head1 DESCRIPTION
68              
69             This library allows any HoneyClient module to quickly access the
70             global configuration options, associated with this program.
71              
72             This library makes extensive use of the XML::XPath module.
73              
74             =cut
75              
76             package HoneyClient::Util::Config;
77              
78 2     2   1132554 use strict;
  2         6  
  2         205  
79 2     2   12 use warnings;
  2         6  
  2         121  
80 2     2   12 use Carp ();
  2         9  
  2         53  
81 2     2   13824 use XML::XPath;
  0            
  0            
82             use XML::Tidy;
83             use Log::Log4perl qw(:easy);
84             use Sys::Syslog;
85             use Data::Dumper;
86             use Log::Dispatch::Syslog;
87              
88             #######################################################################
89             # Module Initialization #
90             #######################################################################
91              
92             BEGIN {
93             # Defines which functions can be called externally.
94             require Exporter;
95             our (@ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS, $VERSION);
96              
97             # Set our package version.
98             $VERSION = 0.98;
99              
100             @ISA = qw(Exporter);
101              
102             # Symbols to export automatically
103             @EXPORT = qw(getVar setVar);
104              
105             # Items to export into callers namespace by default. Note: do not export
106             # names by default without a very good reason. Use EXPORT_OK instead.
107             # Do not simply export all your public functions/methods/constants.
108              
109             # This allows declaration use HoneyClient::Util::Config ':all';
110             # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
111             # will save memory.
112              
113             %EXPORT_TAGS = (
114             'all' => [ qw(getVar setVar) ],
115             );
116              
117             # Symbols to autoexport (when qw(:all) tag is used)
118             @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
119              
120             $SIG{PIPE} = 'IGNORE'; # Do not exit on broken pipes.
121             }
122             our (@EXPORT_OK, $VERSION);
123              
124             =pod
125              
126             =begin testing
127              
128             # Make sure Log::Log4perl loads
129             BEGIN { use_ok('Log::Log4perl', qw(:nowarn))
130             or diag("Can't load Log::Log4perl package. Check to make sure the package library is correctly listed within the path.");
131            
132             # Suppress all logging messages, since we need clean output for unit testing.
133             Log::Log4perl->init({
134             "log4perl.rootLogger" => "DEBUG, Buffer",
135             "log4perl.appender.Buffer" => "Log::Log4perl::Appender::TestBuffer",
136             "log4perl.appender.Buffer.min_level" => "fatal",
137             "log4perl.appender.Buffer.layout" => "Log::Log4perl::Layout::PatternLayout",
138             "log4perl.appender.Buffer.layout.ConversionPattern" => "%d{yyyy-MM-dd HH:mm:ss} %5p [%M] (%F:%L) - %m%n",
139             });
140             }
141             require_ok('Log::Log4perl');
142             use Log::Log4perl qw(:easy);
143              
144             # Make sure the module loads properly, with the exportable
145             # functions shared.
146             BEGIN { use_ok('HoneyClient::Util::Config', qw(getVar setVar))
147             or diag("Can't load HoneyClient::Util::Config package. Check to make sure the package library is correctly listed within the path."); }
148             require_ok('HoneyClient::Util::Config');
149             can_ok('HoneyClient::Util::Config', 'getVar');
150             can_ok('HoneyClient::Util::Config', 'setVar');
151             use HoneyClient::Util::Config qw(getVar setVar);
152              
153             # Suppress all logging messages, since we need clean output for unit testing.
154             Log::Log4perl->init({
155             "log4perl.rootLogger" => "DEBUG, Buffer",
156             "log4perl.appender.Buffer" => "Log::Log4perl::Appender::TestBuffer",
157             "log4perl.appender.Buffer.min_level" => "fatal",
158             "log4perl.appender.Buffer.layout" => "Log::Log4perl::Layout::PatternLayout",
159             "log4perl.appender.Buffer.layout.ConversionPattern" => "%d{yyyy-MM-dd HH:mm:ss} %5p [%M] (%F:%L) - %m%n",
160             });
161              
162             # Make sure XML::XPath loads.
163             BEGIN { use_ok('XML::XPath')
164             or diag("Can't load XML::XPath package. Check to make sure the package library is correctly listed within the path."); }
165             require_ok('XML::XPath');
166             can_ok('XML::XPath', 'findnodes');
167             use XML::XPath;
168              
169             # Make sure XML::Tidy loads
170             BEGIN { use_ok('XML::Tidy')
171             or diag("Can't load XML::Tidy package. Check to make sure the package library is correctly listed within the path."); }
172             require_ok('XML::Tidy');
173             can_ok('XML::Tidy','tidy');
174             can_ok('XML::Tidy','write');
175             use XML::Tidy;
176              
177             # Make sure Sys::Syslog loads
178             BEGIN { use_ok('Sys::Syslog')
179             or diag("Can't load Sys::Syslog package. Check to make sure the package library is correctly listed within the path."); }
180             require_ok('Sys::Syslog');
181             use Sys::Syslog;
182              
183             # Make sure Data::Dumper loads
184             BEGIN { use_ok('Data::Dumper')
185             or diag("Can't load Data::Dumper package. Check to make sure the package library is correctly listed within the path."); }
186             require_ok('Data::Dumper');
187             use Data::Dumper;
188              
189             # Make sure Log::Dispatch::Syslog loads
190             BEGIN { use_ok('Log::Dispatch::Syslog')
191             or diag("Can't load Log::Dispatch::Syslog package. Check to make sure the package library is correctly listed within the path."); }
192             require_ok('Log::Dispatch::Syslog');
193             use Log::Dispatch::Syslog;
194              
195             =end testing
196              
197             =cut
198              
199             #######################################################################
200              
201             # Global Configuration Variables
202              
203             # Relative path to the Global Configuration.
204             # Note: We leave this path relative, so that
205             # corresponding unit testing can work before
206             # we actually install the configuration
207             # file into /etc.
208             our $CONF_FILE = "etc/honeyclient.xml";
209              
210             # The XPath object that points to the config file
211             our $xp;
212              
213             # Temporarily Initialize Logging Subsystem
214             # Note: We use these sane values initially, until we can reinitialize
215             # the logger with values from the global configuration file.
216             Log::Log4perl->init_once({
217             "log4perl.rootLogger" => "INFO, Screen",
218             "log4perl.appender.Screen" => "Log::Log4perl::Appender::ScreenColoredLevels",
219             "log4perl.appender.Screen.stderr" => 0,
220             "log4perl.appender.Screen.Threshold" => "INFO",
221             "log4perl.appender.Screen.layout" => "Log::Log4perl::Layout::PatternLayout",
222             "log4perl.appender.Screen.layout.ConversionPattern" => "%d{yyyy-MM-dd HH:mm:ss} %5p [%M] (%F:%L) - %m%n",
223             });
224              
225             # The global logging object.
226             our $LOG = get_logger();
227              
228             # Make Dumper format more terse.
229             $Data::Dumper::Terse = 1;
230             $Data::Dumper::Indent = 0;
231              
232             #######################################################################
233             # Private Methods Implemented #
234             #######################################################################
235              
236             # Helper function designed to read the global configuration file
237             #
238             # Inputs: config
239             # Outputs: None
240             sub _parseConfig {
241              
242             # Extract arguments.
243             my ($class, $config) = @_;
244              
245             # Sanity check. Make sure the file exists.
246             if (!-f $config) {
247             # Okay, if the relative path didn't work, try the absolute
248             # path.
249             $config = "/" . $config;
250             if (!-f $config) {
251             $LOG->fatal("Unable to parse global configuration file ($CONF_FILE)!");
252             Carp::croak("Error: Unable to parse global configuration file ($CONF_FILE)!");
253             }
254             # The absolute path worked, update the global variable to reflect this.
255             $CONF_FILE = $config;
256             }
257              
258             # Read in the configuration settings.
259             eval {
260             $xp = XML::XPath->new(filename => $CONF_FILE);
261             };
262              
263             # Sanity check
264             if ($@ || !$xp->exists("HoneyClient")) {
265             $LOG->fatal("Unable to parse global configuration file ($CONF_FILE)!" . $@);
266             Carp::croak("Error: Unable to parse global configuration file ($CONF_FILE)!" . $@);
267             }
268             }
269              
270             # Helper function designed to check the arguments passed to getVar()
271             #
272             # Inputs: $args
273             # Outputs: None
274             sub _checkArgs{
275             # Hashref of arguments
276             our ($args) = @_;
277              
278             # Make sure we have args
279             if (!%$args) {
280             $LOG->fatal("No variables specified!");
281             Carp::croak("Error: No variables specified!");
282             }
283              
284             # Process the args
285             # If you do not specify a default value, it will croak if undefined
286             _process('name');
287             _process('namespace', caller(1)); # We want the namespace of the caller to getVar(),
288             # not of the caller to _checkArgs(); hence, we
289             # use caller(1).
290              
291             # Add any special statements to check for depending on the caller
292             # Just specify the calling sub in the regex and any operations in the do{}
293             # Why can't perl actually have switch statements :(
294             for((split(/::/,((caller(1))[3])))[-1]){
295             /getVar/ && do { };
296             /setVar/ && do { _process('value') };
297             }
298              
299              
300             # Accepts the key to check and the default value.
301             # If no default value is given, undef will be used
302             sub _process{
303             my ($name, $val) = @_;
304             if ( !defined($args->{$name} )) {
305             $args->{$name} = $val;
306              
307             # Sanity checking after
308             unless( $args->{$name} ) {
309             $LOG->fatal("No variable $name specified!");
310             Carp::croak("Error: No variable $name specified!");
311             }
312             }
313             }
314             }
315              
316             #######################################################################
317             # Public Methods Implemented #
318             #######################################################################
319              
320             =pod
321              
322             =head1 EXPORTS
323              
324             =head2 getVar(name => $varName, namespace => $caller, attribute => $attribute)
325              
326             =over 4
327              
328             If $attribute is undefined or not specified, then this function will
329             attempt to retrieve the contents of the B $varName, as it is set
330             within the HoneyClient global configuration file.
331              
332             If $attribute is defined, then this function will attempt to retrieve
333             specified B listed within the contents the contents of the
334             element $varName, as it is set within the HoneyClient global configuration
335             file.
336              
337             If $caller is undefined or not specified, then this function may return
338             different values, depending upon which module is calling this function.
339              
340             For example, if module HoneyClient::Agent::Driver calls this function
341             as getVar(name => "address"), then this function will attempt to search for
342             a value like the following, within the global configuration file:
343              
344            
345            
346            
347            
localhost
348            
349            
350            
351              
352             If the "address" value is not found at this level within the XML tree,
353             then the function will attempt to locate values, like the following:
354              
355             # First try:
356              
357            
358            
359            
localhost
360            
361            
362              
363             # Last try:
364              
365            
366            
localhost
367            
368              
369             This function will stop its recursive search at the first value found,
370             closest to the child module's XML namespace.
371              
372             Even after performing a recursive search, if no variable name exists,
373             then the function will issue a warning and return undef.
374              
375             If the variable found is an element that contains child elements, then
376             a corresponding hashtable will be returned. For example, if we perform
377             a getVar(name => "foo") on the following XML:
378              
379            
380            
381             123
382             456
383             789
384             xxx
385            
386            
387              
388             Then the following $hashref will be returned:
389              
390             $hashref = {
391             'bar' => [ '123', '456' ],
392             'yok' => [ '789', 'xxx' ],
393             }
394              
395             I:
396             B<$varName> is the variable name to search for, within the global
397             configuration file.
398             B<$caller> is an optional argument, signifying the module namespace
399             to use, when searching for the variable's value.
400             B<$attribute> is an optional argument, signifying that the function
401             should return the attribute associated with the variable's element.
402              
403             I: The variable's element/attribute value or hashtable (for
404             multi-value elements), if found; warns and returns undef otherwise.
405              
406             B: If the target variable to return is an element that contains
407             B of text and sub-elements, then only the text within
408             the sub-elements will be returned in the previously mentioned
409             $hashref format.
410              
411             For example, if we perform a getVar(name => "foo") on the following XML:
412              
413            
414            
415             THIS_TEXT_WILL_BE_LOST
416             123
417             456
418             789
419             xxx
420             zzz
421            
422            
423              
424             Then the following $hashref will be returned:
425              
426             $hashref = {
427             'bar' => [ '123', '456' ],
428             'yok' => [ '789', 'xxx', 'zzz' ],
429             }
430              
431             Notice how the B string got dropped and that
432             the BCHILDE> tags were silently stripped from the B
433             string. In other words, in each target element, B
434             with sub-elements> and B if you want the
435             nested structure preserved when a getVar() is called on the
436             B.
437              
438             =back
439              
440             =begin testing
441              
442             my $value = getVar(name => "address", namespace => "HoneyClient::Util::Config::Test");
443             is($value, "localhost", "getVar(name => 'address', namespace => 'HoneyClient::Util::Config::Test')")
444             or diag("The getVar() call failed. Attempted to get variable 'address' using namespace 'HoneyClient::Util::Config::Test' within the global configuration file.");
445              
446             $value = getVar(name => "address", namespace => "HoneyClient::Util::Config::Test", attribute => 'default');
447             is($value, "localhost", "getVar(name => 'address', namespace => 'HoneyClient::Util::Config::Test', attribute => 'default')")
448             or diag("The getVar() call failed. Attempted to get attribute 'default' for variable 'address' using namespace 'HoneyClient::Util::Config::Test' within the global configuration file.");
449              
450             # This check tests to make sure getVar() is able to use valid output
451             # from undefined namespaces (but where some of the parent namespace is
452             # partially known).
453             $value = getVar(name => "address", namespace => "HoneyClient::Util::Config::Test::Undefined::Child", attribute => 'default');
454             is($value, "localhost", "getVar(name => 'address', namespace => 'HoneyClient::Util::Config::Test::Undefined::Child', attribute => 'default')")
455             or diag("The getVar() call failed. Attempted to get attribute 'default' for variable 'address' using namespace 'HoneyClient::Util::Config::Test::Undefined::Child' within the global configuration file.");
456              
457             # This check tests to make sure getVar() returns the expected hashref
458             # when getting data from a target element that contains child sub-elements.
459             $value = getVar(name => "Yok", namespace => "HoneyClient::Util::Config::Test");
460             my $expectedValue = {
461             'childA' => [ '12345678', 'ABCDEFGH' ],
462             'childB' => [ '09876543', 'ZYXVTUWG' ],
463             };
464             is_deeply($value, $expectedValue, "getVar(name => 'Yok', namespace => 'HoneyClient::Util::Config::Test')")
465             or diag("The getVar() call failed. Attempted to get variable 'Yok' using namespace 'HoneyClient::Util::Config::Test' within the global configuration file.");
466              
467             =end testing
468              
469             =cut
470              
471             sub getVar {
472              
473             # Get the arguments and check their validity
474             my (%args) = @_;
475             _checkArgs(\%args);
476              
477             # Log resolved arguments.
478             $LOG->debug(sub {
479             # Make Dumper format more terse.
480             $Data::Dumper::Terse = 1;
481             $Data::Dumper::Indent = 0;
482             Dumper(\%args);
483             });
484            
485             # Get a copy of the original namespace.
486             my $namespace = $args{namespace};
487              
488             # Fix the namespace so it is compatible with XPath
489             $namespace =~ s/::/\//g; # Turn package delim :: into XPath delim /
490              
491             # Split the namespace into an array.
492             my @ns = split(/\//, $namespace);
493              
494             # Check to make sure the namespace exists within our XML configuration.
495             # XML::XPath does not know how to deal with unknown paths (even if the parent
496             # path is known). Thus, we recursively check the path's existance, providing
497             # the first valid ancestor path found.
498             while (!$xp->exists($namespace) and
499             (scalar(@ns) > 1)) {
500             pop(@ns);
501             $namespace = join('/', @ns);
502             @ns = split(/\//, $namespace);
503             }
504              
505             # Get the nodeset that we need
506             # The first string is the path that matches the node we want and all ancestors
507             # The second string tells us whether to get the text() or an attribute
508             my $exp = $namespace . "/ancestor-or-self::*/$args{name}" .
509             (defined $args{attribute} ? "/attribute::" . $args{attribute} : "");
510             my $nodeset = $xp->findnodes($exp);
511              
512             # The list of nodes required. Because this is a top down list of the results,
513             # if there are multiple results, we want the bottom one (most specific)
514             if ($nodeset->size() == 0) {
515             $LOG->warn("Warning: Unable to locate specified value in variable '" .
516             $args{'name'} . "' using namespace '" . $args{'namespace'} .
517             "' within the global configuration file ($CONF_FILE)!");
518             return;
519             }
520            
521             # Figure out if the (most specific) node has any children.
522             my $parent = $nodeset->pop();
523             $nodeset = $xp->findnodes("*", $parent);
524             my $val = undef;
525             if ($nodeset->size() <= 0) {
526             # There are no child elements, thus stingify
527             # all textual components.
528              
529             $val = $parent->string_value();
530              
531             # Trail leading and trailing whitespace
532             $val =~ s/^\s+|\s+$//g;
533             } else {
534              
535             # There are child elements; return a
536             # hashtable accordingly.
537             my @children = $nodeset->get_nodelist();
538              
539             # Now, build the hashtable of array references.
540             $val = {};
541             foreach my $child (@children) {
542             push (@{$val->{$child->getName()}}, $child->string_value());
543             }
544             }
545              
546             return $val;
547             }
548              
549             =pod
550              
551             =head2 setVar(name => $varName, namespace => $caller, attribute => $attribute, value => $value)
552              
553             =over 4
554              
555             This will set the desired value.
556             If the required attribute or element does not exist, it (and any parents) will be created
557              
558             I:
559             B<$varName> is the variable name to search for, within the global
560             configuration file.
561             B<$caller> is an optional argument, signifying the module namespace
562             to use, when searching for the variable's value.
563             B<$attribute> is an optional argument, signifying that the function
564             should return the attribute associated with the variable's element.
565             B<$value> is the value to set the element or attribute to
566              
567             =back
568              
569             =begin testing
570              
571             # Test setting an existing value
572             my $oldval = getVar(name => 'address', namespace => 'HoneyClient::Util::Config::Test' );
573             setVar(name => 'address', namespace => 'HoneyClient::Util::Config::Test', value => 'foobar' );
574             my $value = getVar(name => 'address', namespace => 'HoneyClient::Util::Config::Test' );
575             is($value, 'foobar', "setVar(name => 'address', namespace => 'HoneyClient::Util::Config::Test', value => 'foobar' )")
576             or diag("The setVar() call failed. Attempted to set variable 'address' using namespace 'HoneyClient::Util::Config::Test' to 'foobar' within the global configuration file.");
577             setVar(name => 'address', namespace => 'HoneyClient::Util::Config::Test', value => $oldval );
578              
579             # Test setting an attribute
580             $oldval = getVar(name => 'address', attribute => 'default', namespace => 'HoneyClient::Util::Config::Test' );
581             setVar(name => 'address', namespace => 'HoneyClient::Util::Config::Test', attribute => 'default', value => 'foobar' );
582             $value = getVar(name => 'address', attribute => 'default', namespace => 'HoneyClient::Util::Config::Test' );
583             is($value, 'foobar', "setVar(name => 'address', namespace => 'HoneyClient::Util::Config::Test', attribute => 'default', value => 'foobar' )")
584             or diag("The setVar() call failed. Attempted to set 'default' attribute of variable 'address' using namespace 'HoneyClient::Util::Config::Test' to 'foobar' within the global configuration file.");
585             setVar(name => 'address', namespace => 'HoneyClient::Util::Config::Test', attribute => 'default', value => $oldval );
586              
587             # Test creating a value
588             setVar(name => 'zingers', namespace => 'HoneyClient::Util::Config::Test', value => 'foobar');
589             $value = getVar(name => 'zingers', namespace => 'HoneyClient::Util::Config::Test' );
590             is($value, 'foobar', "setVar(name => 'zingers', namespace => 'HoneyClient::Util::Config::Test', value => 'foobar' )")
591             or diag("The setVar() call failed. Attempted to create variable 'zing' using namespace 'HoneyClient::Util::Config::Test' with a value of 'foobar' within the global configuration file.");
592              
593             # Test creating an attribute
594             setVar(name => 'address', namespace => 'HoneyClient::Util::Config::Test', attribute => 'zing', value => 'foobar');
595             $value = getVar(name => 'address', attribute => 'zing', namespace => 'HoneyClient::Util::Config::Test' );
596             is($value, 'foobar', "setVar(name => 'address', namespace => 'HoneyClient::Util::Config::Test', attribute => 'zing', value => 'foobar' )")
597             or diag("The setVar() call failed. Attempted to create attribute 'zing' using namespace 'HoneyClient::Util::Config::Test' with a value of 'foobar' within the global configuration file.");
598              
599             # Creating new namespaces
600             setVar(name => 'address', namespace => 'HoneyClient::Util::Config::Test::Foo::Bar', value => 'baz');
601             $value = getVar(name => 'address', namespace => 'HoneyClient::Util::Config::Test::Foo::Bar');
602             is($value, 'baz', "setVar(name => 'address', namespace => 'HoneyClient::Util::Config::Test::Foo::Bar', value => 'baz')")
603             or diag("The setVar() call failed. Attempted to create attribute 'address' using namespace 'HoneyClient::Util::Config::Test::Foo::Bar' with a value of 'baz' within global configuration file.");
604              
605             =end testing
606              
607             =cut
608              
609             sub setVar {
610             # Get the arguments and check their validity
611             my (%args) = @_;
612             _checkArgs(\%args);
613              
614             # Log resolved arguments.
615             $LOG->debug(sub {
616             # Make Dumper format more terse.
617             $Data::Dumper::Terse = 1;
618             $Data::Dumper::Indent = 0;
619             Dumper(\%args);
620             });
621              
622             # Fix the namespace so it is compatible with XPath
623             my $namespace = $args{namespace};
624             $namespace =~ s/::/\//g; # Turn package delim :: into XPath delim /
625              
626             # Get the nodeset that we need
627             # The first string is the path that matches the node we want
628             # The second string tells us whether to get the text() or an attribute
629             my $exp = $namespace . "/$args{name}" .
630             (defined $args{attribute} ? "/attribute::" . $args{attribute} : "");
631             if(!$xp->exists($exp)){
632             $xp->createNode($exp);
633             }
634             $xp->setNodeText($exp,$args{value});
635              
636             # Create the tidy object with our document root and write out the stuff to the new conf_file
637             my $tidy_obj = XML::Tidy->new(context => $xp->find('/'));
638             $tidy_obj->tidy(' ');
639             $tidy_obj->write($CONF_FILE);
640              
641             # Parse the conf_file again just for good measure
642             _parseConfig(undef, $CONF_FILE);
643             }
644              
645             #######################################################################
646              
647             # Parse the global configuration file, upon using the package.
648             _parseConfig(undef, $CONF_FILE);
649              
650             # Reinitialize Logging Subsystem
651             # TODO: Need to account for absolute "/etc" directories!
652             Log::Log4perl->init(getVar(name => "log_config"));
653              
654             # Initialize Syslog Support
655             $Sys::Syslog::host = getVar(name => "syslog_address");
656              
657             1;
658              
659             #######################################################################
660             # Additional Module Documentation #
661             #######################################################################
662              
663             __END__