#!/pkg/bin/perl -w
use strict;

# $Id: sm,v 1.42 1997/05/22 23:04:45 rowe Exp $

# This is the main source file for the Software Manager within
# GrIDS.

# To do:

# Update Static_message_syntax to know about software manager messages.

# Note:  the module controller uses command line arguments in its own
# format.  For test purposes we need to pass command line arguments into
# the software manager.  I don't want to have to *always* run the sm
# under a module controller during testing.  The two uses of command
# line variables are incompatible.  The present, admittedly unattractive,
# solution to this is a global variable use_control_vars which is set
# at the very top of the file and controls whether or not the sm expects
# to be run under a module controller with control variable inititialization
# or not.

#============================================================================#
#============================================================================#

#				PREAMBLE

# Inclusions, global variables, etc.

#============================================================================#
#============================================================================#

# Other code to utilize

use English;			# Mnemonic names for Perl special variables.
# use Storable;			# Store data structures.  Part of CPAN.
use Static_message_syntax;	# Checking GCPF message syntax
use Clog;			# For managing our log file.
use Timeout_recur;		# For managing timeouts.
use Message_center;		# I/O.

#============================================================================#

# Constants which affect the operation of the program.

%global::allowed_headers = ('shv',1,	# Models the set of headers we know.
		    	    'ghv',1, 	# Any not in this set will create an
		   	    'sdv',1,	# error condition
		    	    'gdv',1,		
		    	    'new',1);		
$global::state_directory = "sm_state.$$";# Directory to store state in between 
					# invocations
$global::parameter_file = "parameter";  
#$global::transaction_file = "transactions";
$global::log_file 	= "sm_log.root";	# Name of local file for log messages
$global::max_requests	= -1;		# Maximum number of requests to handle
					# (-1 means infinity)
$global::original_dir = `pwd`; chomp $global::original_dir;
$global::output_mode	= 'stdio';
$global::sm_port	= 2100;
@global::delayed_sets   = ();
$global::initialized_root = 0;
$global::current_dept = 'ROOT_temp';
$global::current_user = '';
$global::current_pass = '';

# Models the set of variables we agree to set via an sdv message
%global::sdvable = (
			'local_managers',	'hash',
			'department',		'scalar',
			'dept_polling_freq',	'scalar',	
			'host_polling_freq',	'scalar',
			'host_child',		'hash',
			'dept_child',		'hash',
			'dept_child_pass',	'hash',
			'our_aggregator',	'scalar',
			'listen_tcp',		'scalar',
			'listen_udp',		'scalar',
			'parent',		'scalar',   			
			'parent_aggregator',	'scalar',
			'parent_manager',	'scalar',
			'aggregator_mc',	'scalar',
			'old_dept',		'scalar',
			'rulesets',		'hash',
			'inherited_rulesets',	'hash',
			'local_ACL',		'hash',
			'inherited_ACL',	'hash',
		   );		

# Models set of variables we will agree to give out via a gdv
%global::gdvable = (
			'department','scalar',
			'parent','scalar',  
			'dept_polling_freq', 'scalar',	
			'host_polling_freq', 'scalar',
			'host_child', 'hash',
			'dept_child', 'hash',
			'rulesets',	'hash',
			'inherited_rulesets',	'hash',
			'our_aggregator',	'scalar',
			'listen_tcp',	'scalar',
			'listen_udp',	'scalar',
			'parent_aggregator',	'scalar',
			'parent_manager',	'scalar',
			'aggregator_mc',	'scalar',
			'local_ACL_names',	'hash',
			'inherited_ACL_names',	'hash',
                        'ALL_STATE_NAMES',	'scalar',
                        'ALL_STATE_VARS',	'scalar',
		   );		

# Array of functions which handle particular kinds of sdv sets.
%global::sdv_handlers =
		    (
	 	     'host_child' => \&host_child_change,
	 	     'dept_child' => \&dept_child_change,
	 	     'rulesets' => \&ruleset_update,
	 	     'inherited_rulesets' => \&inherited_ruleset_update,
	 	     'local_ACL' => \&local_ACL_change,
	 	     'inherited_ACL' => \&inherited_ACL_change,
	 	     'local_managers' => \&local_managers_change,
	             'department' => \&notify_engine_department,
                     'parent_aggregator' => \&notify_engine_parent_aggregator,
                     'parent_manager' => \&notify_engine_parent_manager,
		    );

# Array of functions which handle particular kinds of gdv sets.
%global::gdv_handlers =
		    (
		    );

			
#============================================================================#

# Control variables which affect our operation

# All control variables are contained in the associative array
# %$cv{dept}.  Later, we may want to use a control_vars object, but right
# now that seems kind of complicated.  The fields are

# $department  		- The name of the department we manage.
# $department_pass  	- The password of the department we manage.
# $parent  		- The name of our parent department
# $dept_polling_freq	- The default frequency to poll department children
# $host_polling_freq	- The default frequency to poll host children
 
# %dept_child	- Child departments of this one.  The hash indices
#	are the department names.  The hash values are strings containing
#	the following whitespace separated quantities:
#		* Child software manager hostname
#		* Child software manager port

# %dept_child_pass   - Child departments of this one.  The hash indices
#       are the department names.  The hash values are strings containing
#       a randomly generated password.

# %host_child	- Hosts in our department.  The hash indices
#	are the host names.  The hash values are strings containing
#	the following whitespace separated quantities:
#		* Module controller port

# %dept_child_status 	- Hash of department children.  Keys are department
#	names, values are whitespace separated lists of the
#	things which we cache:
#		* Up:  1 or 0

# %host_child_status	- Hash of hosts in our department.  Keys are host
#	names, values are whitespace separated lists of the
#	things which we cache:
#		* Up:  1 or 0

# %rulesets - copy of the rulesets started at our department.  Keys are 
#       ruleset names,	values are ruleset text as defined in the engine 
#       chapter of the TR.
#       NOTE:  When this variable is gdv'd, it returns inherited rulesets
#              also.

# %inherited_rulesets - copy of the rulesets started above our department.  
#       Keys are ruleset names, values are ruleset text as defined in the 
#       engine chapter of the TR.

# %local_ACL - copy of the ACL started at our department.  Keys are
#       user names,  values are passwords.
 
# %inherited_ACL - copy of the ACL started above our department.
#       Keys are user names, values are passwords.
 
# $parent_aggregator - "host:port" for that module.

# $parent_manager - "host:port" for that module.

# $our_aggregator - "host:port" for the aggregator for our department.

# $aggregator_mc - "host:port" for the module controller for our aggregator.

# $old_dept - not a real control variable.  Used to let us now how
# to approach the Module controller to update a moved host.

#============================================================================#

# Global variables and data structures.

#$global::timeout	= new Timeout;	# Our timeout manager instance.
$global::log		= {};   	# will be a reference to our log obj.
$global::delayed_sets_exist = undef;	# whether we have sets to do as a unit.
$global::return_address = undef;	# "host:port" to reply to.
$global::my_address = '';		# "host:port" of mine in file mode.
# Get our fully qualified name.
$global::local_managers = {};

#============================================================================#
#============================================================================#

#				MAIN PROGRAM

#	Loops over getting messages, doing a limited amount of
#	sanity checking, and then handing off the processing to a function
#	specific to the message in question.

#	Also sets alarms for itself at regular intervals in order to do 
#	polling of its children
			
#============================================================================#
#============================================================================#

&initialize();

my $request_id = 0;
while(1)
{
  # Quit if we've done enough transactions.
  $request_id++;
  last if $global::max_requests >= 0 && $request_id > $global::max_requests;

  $global::log->separator(); 
  $global::log->{'prefix'} = "$request_id: "; 
  
  # Get our next request.
  my($header,$messageref) = &get_incoming_message();
  # Basic sanity checking of message
  last unless defined $header && defined $messageref;
  unless($global::allowed_headers{$header})
		#&& &message_syntax_ok($header,$messageref))
   {
    $global::log->warn("Bad syntax in SM request:$header @$messageref");
    next;
   }

  # Now call the particular subroutine for this kind of message.
  # These all have names of the form process_transaction_name;
  # We pass in the message array minus the header.
  my $function_call = "\&process_$header";
  unless(eval "$function_call".'($messageref)')
   {
    $global::log->warn("Unexpected problem with $function_call: $@");
   }
   
  &process_backlog(); # Old sets which could not be implemented.
  &poll_children();   # Get up to date info on our kids.
}

$global::log->separator();
&save_state();
$global::cv{$global::current_dept}->shutdown() 
      if $ENV{'GRIDS_STARTER'} eq 'module_controller';
$global::log->warn("SM exiting normally.  Thank you and good night.");

#============================================================================#
#============================================================================#

#			MAJOR OPERATIONAL FUNCTIONS
			
#============================================================================#
#============================================================================#

# Function Name: initialize

# This function is called by the main program when we are very first 
# started up.  It parses command line arguments, sets up logging, and loads any 
# saved state from a previous incarnation of the program.

# Inputs:
#  Actual Arguments:
#   None
#  Global:
#   @ARGV - the command line argument array
#   $global::log - the log object

# Return Value(s):
#   None
 
# Global variable modifications:
#  $global::log - creates the object

# Example of usage:
#  &initialize();

sub initialize
{
  $ENV{'GRIDS_STARTER'} = 'none' unless $ENV{'GRIDS_STARTER'};

  if($ENV{'GRIDS_STARTER'} eq 'module_controller')
   {
    $global::output_mode = 'comm'
   }
  else
   {
    foreach (@ARGV) 
      { eval '$'.$_; } # 'process possible changes to variables
   }

  if($ENV{'GRIDS_STARTER'} eq 'module_controller')
   {
    # initialize our control variables
    use control_vars;
    my $key;

    $global::cv{$global::current_dept} = new control_vars();
    $global::cv{$global::current_dept}->
       add_straight(grep($global::sdvable{$_} eq 'scalar',
                         keys %global::sdvable));
    $global::cv{$global::current_dept}->
       add_straight(grep($global::gdvable{$_} eq 'scalar',
                         keys %global::gdvable));
    $global::cv{$global::current_dept}->
       add_indexed(grep($global::sdvable{$_} eq 'hash', keys %global::sdvable));
    $global::cv{$global::current_dept}->
       add_indexed(grep($global::gdvable{$_} eq 'hash', keys %global::gdvable));
    $global::cv{$global::current_dept}->{'module'} = 'sm';
    $global::cv{$global::current_dept}->{'version'} = 'v.v';

    $global::initialized_root = 1; # Root must be already initialized

    # Now we change the array from the initial_root to the new name of the root.
    my $temp_dept = $global::current_dept;

    $global::current_dept = 
        $global::cv{$global::current_dept}->{'department'};
    $global::cv{$global::current_dept}
           = $global::cv{$temp_dept};
    delete $global::cv{$temp_dept};
        
    $global::local_managers{$global::current_dept} = 1;

    $global::sm_port = 0;

   }
  else
   {
    # Reference to control variable hash.
    $global::cv{$global::current_dept}		= {};	
 
   }

  # Initialize Comm.pm if necessary.
  if($global::output_mode eq 'comm')
   {
    my($status,$tcp,$udp) = Comm::init($global::sm_port,$global::sm_port);
    unless($status eq 'ok')
     {
      die "SM could not initialize Comm.pm correctly\n";
     }
    $global::cv{$global::current_dept}->{'listen_tcp'} = $tcp;
    $global::cv{$global::current_dept}->{'listen_udp'} = $udp;
 
    ($global::localhost) = Comm::host_name();
   }

  if($ENV{'GRIDS_STARTER'} eq 'module_controller')
   {
    $global::cv{$global::current_dept}->update();

    $global::log_file = "sm_log.".$global::current_dept;
   }

  my $department = $global::current_dept;
  $department = 'N/A' unless $department;

  $global::dept_log{$global::current_dept} =
        new Clog('sm',$department,$global::log_file);

  # No central logging - presently broken.
  $global::dept_log{$global::current_dept}->{'central_log'} = 0;
   $global::dept_log{$global::current_dept}->warn
    ("Debugging $0 \[$$\] at ".localtime());
  if($global::output_mode eq 'file')
   {$global::dept_log{$global::current_dept}->
        warn("My address is $global::my_address.");}
  $global::dept_log{$global::current_dept}->
        warn("Started by Module controller.");

  $global::log = $global::dept_log{$global::current_dept};

  &load_state();

  $global::log->warn("Started via $ENV{'GRIDS_STARTER'}");
}

#============================================================================#

# Function Name: load_state

# This function is called by the initialize function.  Its purpose is to 
# determine if appropriate state information was saved by a previous 
# incarnation of the program, and load it into this incarnation if it was.

# Convention on the state directory is as follows.  If it exists, it is 
# assumed to have a complete correct account of the current state of the
# hierarchy.  If it does not have such a complete, correct account, then
# it should not exist.


# Inputs:
#  Actual Arguments:
#   None
#  Global:
#   $global::log - the log object
#   $global::state_directory - directory for state information storage
#   $global::parameter_file - where old parameters are stored.

# Return Value(s):
#   None
 
# Global variable modifications:
#  None

# Example of usage:
#  &load_state();


sub load_state
{
  if(-d $global::state_directory)
   {
    # we have state - load it in.
    chdir $global::state_directory;
    do $global::parameter_file;
   }
  else
   {
    # no state.  Create an empty directory to use later.
    system("mkdir $global::state_directory");
    chdir $global::state_directory;
   }
}


#============================================================================#
#
# Function Name: process_new
#
# This function is called from the main program any time a "new" message has
# been received by the software manager.  new messages tell the manager that
# it is now managing another department.
# 
# Inputs:
# $username - user who sent this message
# $password - password supplied by that user
# $dept - name of the new dept
# $sdv_ref - reference to array which contains alternately
#                names and values from the sdv message.
# 
#  Global:
#   $global::log - the log object
#   %global::cv - the control variables.
# 
# Return Value(s):
#   0 in case of error
#   1 otherwise
# 
# Global variable modifications:
# %global::cv - the point of this function is to change control variables.
#
# Example of usage:
#  &process_new($username,$password,$dept,[$name1,$val1,$name2,$val2,...]);
#
#----------------------------------------------------------------------------# 
 
sub process_new
{
  my $username = shift @{$_[0]};
  my $password = shift @{$_[0]};
  my $dept = shift @{$_[0]};
  my @sdv_ref = @{$_[0]};

  $global::log->warn("Processing new message: ".join(' ',@{$_[0]}));
 
  $global::current_dept = $dept;
  $global::cv{$global::current_dept} = new control_vars();
  $global::cv{$global::current_dept}->
     add_straight(grep($global::sdvable{$_} eq 'scalar',
                       keys %global::sdvable));
  $global::cv{$global::current_dept}->
     add_straight(grep($global::gdvable{$_} eq 'scalar',
                       keys %global::gdvable));
  $global::cv{$global::current_dept}->
     add_indexed(grep($global::sdvable{$_} eq 'hash', keys %global::sdvable));    $global::cv{$global::current_dept}->
     add_indexed(grep($global::gdvable{$_} eq 'hash', keys %global::gdvable));    $global::cv{$global::current_dept}->{'module'} = 'sm';
  $global::cv{$global::current_dept}->{'version'} = 'v.v';
  $global::local_managers{$global::current_dept} = 1;


  $global::cv{$global::current_dept}->{'department'} = $dept;


  $global::cv{$global::current_dept}->update();
 
  $global::log_file{$global::current_dept} = "sm_log.".$global::current_dept;
 
  my $department = $global::current_dept;
  $department = 'N/A' unless $department;
 
  $global::dept_log{$global::current_dept} = 
        new Clog('sm',$department,$global::log_file{$global::current_dept});

  # No central logging - presently broken.
  $global::dept_log{$global::current_dept}->{'central_log'} = 0; 
   $global::dept_log{$global::current_dept}->warn
    ("Debugging $0 \[$$\] at ".localtime());
   $global::dept_log{$global::current_dept}->warn("Started in SM.");


  &process_sdv([$username,$password,$dept,@sdv_ref]);


  # Send the acknowledgement back to the sender.
  return 1;

}#end process_new
  
#============================================================================#
#
# Function Name: process_sdv
#
# This function is called from the main program any time an "sdv" message has
# been received by the software manager.  sdv messages set department-wide
# control variables which are managed by this software manager.  
#
# In the generic case, this function just updates the appropriate control 
# variable and then replies to the caller with a sdvr (sdv response) message.  
# There are also a lot of special cases.  There is a global hash, 
# "sdv_handlers" of references to functions which are called to handle the 
# special case.
#
# Inputs:
# $username - user who sent this message
# $password - password supplied by that user
# $dept - name of department we are setting for
# $sdv_ref - reference to array which contains alternately 
#		# names and values from the sdv message.
#
#  Global:
#   $global::log - the log object
#   %global::sdvable - whether a control variable can be set this way
#   %global::sdv_handlers - functions for special cases.
#   %global::cv - the control variables.
#
# Return Value(s):
#   0 in case of error
#   1 otherwise
#
# Global variable modifications:
# %global::cv - the point of this function is to change control variables.
# $global::delayed_sets_exist - used to handle sets to children which should 
#	be delayed until they can all be done in batch mode.
#
# Example of usage:
#  &process_sdv($username,$password,$dept,[$name1,$val1,$name2,$val2,...]);
#
#----------------------------------------------------------------------------#

sub process_sdv
{
  my $username = shift @{$_[0]};
  my $password = shift @{$_[0]};
  my $dept = shift @{$_[0]};
  my %mesg_hash = @{$_[0]};
  my($variable,$index,$full_variable);

  if ($global::initialized_root == 0)
   { 
    unless (($username eq $global::init_user) && 
            ($password eq $global::init_pass)) 
     {
      $global::log->warn("Invalid initial username/password.");
      reply('sdve',{});
      return 0;
     }

    $global::log->warn("Initialize_root == 0: ".
                   "Current dept is $global::current_dept ".
		       "and request dept is $dept");

    $global::cv{$dept} = $global::cv{$global::current_dept};

    $global::cv{$dept}->{'local_ACL'}{$global::init_user}
       = $global::init_pass;
    $global::cv{$dept}->{'local_ACL_names'}{$global::init_user} = 1;

    #$global::cv{$dept}->{'parent'} = $global::parent;
    $global::cv{$dept}->{'parent'} = "NULL";
    $global::dept_log{$dept} = $global::dept_log{$global::current_dept};
    delete $global::cv{$global::current_dept};
    delete $global::dept_log{$global::current_dept};

#  Added by J.Rowe to fix Hierarchy_interface::move_manager bug 8-may-97
    $global::local_managers{$dept} = 1;
#

    $global::initialized_root = 1;
   } 


  unless (defined $global::cv{$dept})
   {
    $global::log->warn("$dept dept is not known to the SM.");
    $global::log->warn("The sdv message was: ".join(' ',@{$_[0]}));
    $global::log->warn("NOTE: this warning is likely to be in the wrong log, ".
                       "since the department was not known.");

    reply('sdve',{});
    return 0;
   }

  $global::current_dept = $dept;
  $global::current_user = $username;
  $global::current_pass = $password;
  $global::log = $global::dept_log{$global::current_dept};

  $global::log->warn("Processing sdv message: ".join(' ',@{$_[0]}));

  unless(&authorization_ok($username,$password))
   {
    reply('sdve',{});
    return 0;
   }

  # Loop over variables which are to be set.   
  foreach $full_variable (keys %mesg_hash)
   {
    # Figure out if it is a straight or indexed control variable
    ($variable,$index) = &parse_var_name($full_variable);
    unless($variable){ return undef;}

    # Are they allowed to set this?
    unless(($global::sdvable{$variable}) && 
      (($global::sdvable{$variable} eq 'hash' && $index) ||
       ($global::sdvable{$variable} eq 'scalar' && !$index)))
     {
      # This variable does not exist or is not settable via an sdv.
#
#  Modified by J.Rowe 8-May-97: Instead of bailing out, only skip over
#  variables that are un-sdvable. This way one can gdv ALL_STATE_VARS
#  from an old SM and immediatly sdv them to a new SM without error.
#  This is necissary since some variables are gdv-able but not sdv-able.
#  Of course this may screw up the local_ACL!!!
#
#      $global::log->warn("Bad sdv on variable $full_variable\n");
#      &reply('sdve',{});
#      return 0;
#
      $global::log->warn("WARNING: Bad sdv on variable $full_variable".
                          ": skipping to next one\n");
      next;

     }

    # Handle special cases
    my $success = 1;
    if(defined $global::sdv_handlers{$variable})
     {
      # We have to do some special variable-specific work
      $success = &{$global::sdv_handlers{$variable}}
					($mesg_hash{$full_variable},$index);
     }

    # Update our copy of the variable and delete (don't return it in sdvr)
    unless ($success)
     {
      &reply('sdve',{});
      return 0;
     }
    if($index)
     {
      # An indexed control variable
      $global::cv{$global::current_dept}->{$variable}{$index} = 
          delete $mesg_hash{$full_variable};

      unless(defined $global::cv{$global::current_dept}->{$variable}{$index}
            && $global::cv{$global::current_dept}->{$variable}{$index} )
       { 
	# We are deleting this control variable because it has been set to null
        delete $global::cv{$global::current_dept}->{$variable}{$index};
       }

     }#if
    else
     {
      # Straight control variable
      $global::cv{$global::current_dept}->{$variable} = 
         delete $mesg_hash{$full_variable};
 
      unless (defined $global::cv{$global::current_dept}->{$variable} && 
              $global::cv{$global::current_dept}->{$variable})
       { 
        # We are deleting this control variable because it has been set to null
        delete $global::cv{$global::current_dept}->{$variable};
       } 
     } # end else
   }

  # This will do all sets that need to be done on children as a unit.
  if($global::delayed_sets_exist)
   {
    &do_delayed_sets($username,$password,\%mesg_hash);
   }
  else
   {
    # Send the acknowledgement back to the sender.
    reply('sdvr',\%mesg_hash);
    $global::log->warn("Sent sdvr response");
    return 1;
   }
}

#============================================================================#

sub do_delayed_sets
{
  my($username,$password,$reply_hash_ref) = @_;
  my($address,$sdv_ref);
  my($error) = '';

  return unless @global::delayed_sets > 0; # Nothing to do.

  # Insert username and passwords into delayed sets
  foreach $sdv_ref (@global::delayed_sets)
   {
    next unless $sdv_ref->[2] eq 'sdv';
    unshift(@{$sdv_ref->[3]},$username,$password);
   }

  # Send out replies and get responses.

  my @temp_delayed_sets = @global::delayed_sets;
  my (@temp_array1, @temp_array2);
  
  # We first must find any sets which are to another internal
  # SM and complete those now.  These are removed from the array.
  while (@temp_delayed_sets)
   {
    @temp_array1 = shift (@temp_delayed_sets);
   
    if ($temp_array1[0][2] eq 'sdv')
     {
      my $temp_dept = $temp_array1[0][3][2];

      if (defined $global::cv{$temp_dept})
       {
        my $temp_return = $global::return_address;
        $global::return_address = 'local_sm';
      
        &process_sdv([@{$temp_array1[0][3]}]);
        $global::return_address =  $temp_return;
       }
      else
       { push (@temp_array2,@temp_array1); }

     }
    else
     { push (@temp_array2,@temp_array1); }

   }# end while

  @global::delayed_sets = @temp_array2;

  my $send_success = send_messages(\@global::delayed_sets,$global::log);
  my($status,$reply_ref) = 
			receive_messages(\@global::delayed_sets,$global::log);
 
  # Did we get replies at all?
  unless($status)
   {
    $error .= "Communication failure in receive_messages for ".
		 	"sm[$global::current_dept}]\n";
   }

  # Did we get correct replies?
  my($reply,$host,$port,$header,$mesgref);
  foreach $reply (@$reply_ref)
   {
    ($host,$port,$header,$mesgref) = @$reply;
    if($header eq 'sr' && !($mesgref->[0] =~ /^OK/))
     { 
      # Error on a module controller set.
      my $err_string = "Got err/warn from module_controller: ".
						"$header $mesgref->[0]";
      $global::log->warn($err_string);
      unless($mesgref->[0] =~ /^WARN/)
       {
        # Ok - this is really bad, going to have to respond with an error.
        $error .= "$err_string\n";
       }
     }
    if($header eq 'sdve')
     { 
      # Error on an sm set.
      my $err_string = "Sdve from $host:$port - $mesgref->[0]";
      $error .= "$err_string\n";
     }
   }

  # Don't need these any more - cleanup
  @global::delayed_sets = ();
  undef $global::delayed_sets_exist;

  # Now reply to the caller.
  if($error)
   {
    reply('sdve',[$error]);
    $global::log->warn("Sent sdve response");
    $global::log->warn($error);
   }
  else
   {
    reply('sdvr',[1]);
    $global::log->warn("Sent sdvr response");
   }
  return 1;
}

#============================================================================#
#
# Function Name: process_gdv
#
# This function is called from the main program any time a "gdv" message has
# been received by the software manager.  gdv messages get department-wide
# control variables which are managed by this software manager.  
#
# In the generic case, this function just replies to the caller with a gdvr 
# (gdv response) message containing the requested information.  
# There are also a lot of special cases.  There is a global hash, 
# "gdv_handlers" of references to functions which are called to handle the 
# special cases.
#
# Inputs:
# $username - user who sent this message
# $password - password supplied by that user
# $dept - the department name
# $gdv_ref - reference to array which contains alternately 
#		# names and values from the gdv message.
#
#  Global:
#   $global::log - the log object
#   %global::gdvable - whether a control variable can be set this way
#   %global::gdv_handlers - functions for special cases.
#   %global::cv - the control variables.
#
# Return Value(s):
#   0 in case of error
#   1 otherwise
#
# Global variable modifications:
# None.
#
# Example of usage:
#  &process_gdv($username,$password,[$name1,$val1,$name2,$val2,...]);
#
#----------------------------------------------------------------------------#

sub process_gdv
{
  my $username = shift @{$_[0]};
  my $password = shift @{$_[0]};
  my $dept = shift @{$_[0]};
  my %mesg_hash = @{$_[0]};
  my($variable,$index,$full_variable);

  $global::log->warn("Processing gdv message: ".join(' ',%mesg_hash).
          " for department $dept");

  unless ( defined $global::cv{$dept}){
    $global::log->warn("Department $dept does not exist, return gdve.");
    reply('gdve',{});
    return 0;
  }

  $global::current_dept = $dept;
  $global::log = $global::dept_log{$global::current_dept};

  unless(&authorization_ok($username,$password))
   {
    reply('gdve',{});
    return 0;
   }

  # Loop over the requested variables
  foreach $full_variable (keys %mesg_hash)
   {
    # Parse out if this is a scalar or indexed variable.
    ($variable,$index) = &parse_var_name($full_variable);
    unless($variable){ return undef;}

    # Barf if they aren't allowed to see it.
    unless($global::gdvable{$variable} && 
      (($global::gdvable{$variable} eq 'hash' && $index) ||
       ($global::gdvable{$variable} eq 'scalar' && !$index)))
     {
      # This variable does not exist or is not gettable via a gdv.
      $global::log->warn("Bad gdv on variable $full_variable\n");
      &reply('gdve',{});
     return 0;
     }

    # If there is a special case to handle
    if(defined $global::gdv_handlers{$variable})
     {
      # We have to do some special variable-specific work
      &{$global::gdv_handlers{$variable}}($mesg_hash{$full_variable},$index);
     }
    else
     {
      # Just supply our copy of the variable
      if($index)
       {
        if ($index eq 'ALL_STATE_NAMES') 
         {
          delete $mesg_hash{$full_variable}; # remove ALL_STATE_VARS.
          my $key;
          foreach $key (sort keys %{$global::cv
           {$global::current_dept}->{$variable}}){
            $mesg_hash{$variable.'{'.$key.'}'} = 1;}
         }
        elsif ($index eq 'ALL_STATE_VARS') 
         {
          delete $mesg_hash{$full_variable}; # remove ALL_STATE_VARS.
          my $key;
          foreach $key (sort keys(%{$global::cv
           {$global::current_dept}->{$variable}})) 
            {$mesg_hash{$variable.'{'.$key.'}'} =
		$global::cv{$global::current_dept}->{$variable}{$key};}
         }# end elsif 
        else 
         {
          $mesg_hash{$full_variable} = $global::cv{$global::current_dept}->
             {$variable}{$index};
          if($variable eq 'rulesets')
           {
            unless (defined $mesg_hash{$full_variable})
             {
              my $rule;
              foreach $rule (%{$global::cv{$global::current_dept}->{'rulesets'}})
               {
                if ($rule =~ /^$index\.\w+$/)
                 {
                  $mesg_hash{"rulesets\{$rule\}"} = 
                    $global::cv{$global::current_dept}->{$variable}{$rule};
                 }#end if
               }#end foreach
             }# end unless
           }#end if
          if($variable eq 'inherited_rulesets')
           {
            unless (defined $mesg_hash{$full_variable})
             {
              my $rule;  
              foreach $rule (%{$global::cv{$global::current_dept}->
                              {'inherited_rulesets'}})
               { 
                if ($rule =~ /^$index\.\w+$/)
                 {
                  $mesg_hash{"inherited_rulesets\{$rule\}"} =
                    $global::cv{$global::current_dept}->{$variable}{$rule};
                 }#end if  
               }#end foreach
             }# end unless 
           }#end if
         }# end else
       }#end if 
      else # not ($index)
       {
        if ($variable eq 'ALL_STATE_NAMES') 
         {
          delete $mesg_hash{$variable};
          my($key,$index_key);
          foreach $key (sort keys(%{$global::cv{$global::current_dept}})) 
           {
            if($global::gdvable{$key} eq 'hash') 
             {
              foreach $index_key (sort keys %{$global::cv
                 {$global::current_dept}->{$key}})
               {$mesg_hash{$key.'{'.$index_key.'}'} = 1;}
             }
            else
             {$mesg_hash{$key} = 1;}
           }
         }
        elsif ($variable eq 'ALL_STATE_VARS') 
         {
          delete $mesg_hash{$full_variable}; # remove ALL_STATE_VARS.
          my($key,$index_key);
          #foreach $key (sort keys(%{$global::cv{$global::current_dept}})) 
          foreach $key (sort keys(%global::gdvable)) 
           {
            next if (($key eq 'ALL_STATE_VARS') || ($key eq 'ALL_STATE_NAMES'));
            if($global::gdvable{$key} eq 'hash') 
             {              
              foreach $index_key (sort keys %{$global::cv
                {$global::current_dept}->{$key}})
               {$mesg_hash{$key.'{'.$index_key.'}'} 
		= $global::cv{$global::current_dept}->{$key}{$index_key};}
             }
            else
             { $mesg_hash{$key} = $global::cv{$global::current_dept}->{$key};}
           } # end foreach
         }# end elsif
        else
         {$mesg_hash{$full_variable} = $global::cv{$global::current_dept}->
           {$variable};}
       }
     }
   }

  # Send back the information to the caller
  &reply('gdvr',\%mesg_hash);
  $global::log->warn("Sent gdvr response");
  return 1;
}

#============================================================================#
#
# Function Name: process_ghv
#
# This function is called from the main program any time a "ghv" message has
# been received by the software manager.  ghv messages get control variables 
# from particular hosts.  
#
# This function just replies to the caller with a ghvr (ghv response) 
# message containing the requested information.  
#
# Inputs:
# $username - user who sent this message
# $password - password supplied by that user
# $ghv_ref - reference to array which contains 
#		# the remaining fields from the ghv message.
#
#  Global:
#   $global::log - the log object
#   %global::cv - the control variables.
#
# Return Value(s):
#   0 in case of error
#   1 otherwise
#
# Global variable modifications:
# None.
#
# Example of usage:
#  &process_ghv($username,$password,[$name1,$val1,$name2,$val2,...]);
#
#----------------------------------------------------------------------------#

sub process_ghv
{
  my($username,$password,$dept,$host,@mesg_array) = @{$_[0]};
  my($mc_port,$header,$error,$junk,$reply_ref);

  $global::log->warn("Processing ghv message: ".join(' ',@{$_[0]}).
          " for department $dept.");

  $global::current_dept = $dept;
  $global::log = $global::dept_log{$global::current_dept};

  # Check that the user is allowed.
  unless(&authorization_ok($username,$password))
   {
    reply('ghve',['Error','Not authorized']);
    return 0;
   }

  # At present, we simply forward the information to the module controller
  # In future we should check whether we have it cached or not, or can short
  # circuit the query with information of our own (eg with rulesets).
  # We should also check if the host is up or not.

  # For now, we do not check the cache to see if the value is in there, but
  # when we receive the response, we check the cache to make sure it is up
  # to date.  To get the variables from the cache, a gdv can be made.
  
  # Make sure the host is in our department.
  unless($mc_port = $global::cv{$global::current_dept}->{'host_child'}{$host})
   {
    reply('ghve',['Error',"No such host $host"]);
    return 0;
   }
  
  # Construct the array for the get call to the module controller
  unshift @mesg_array, 'GET';
  my $mc_message = [$host,$mc_port,'g',\@mesg_array];


  # Send and receive
  my($successes) = send_messages([$mc_message],$global::log);

  unless ($successes == 1)
   {
    reply('ghve',["Error: Unable to send to host"]);
    $global::log->warn("Unable to send to host $host");
    return 0;
   }

  my($status,$replies) = receive_messages([$mc_message],$global::log);
  ($junk,$junk,$header,$reply_ref) = @{$replies->[0]};
  $error = shift @$reply_ref;

  unless($header eq 'gr' && ($error =~ /^OK|^WARN/))
   {
    reply('ghve',["Error: $error"]);
    $global::log->warn("Bad reply: $header $error @$reply_ref");
    return 0;
   }
  # Get rid of crap fields
  splice(@$reply_ref,0,3);

  # Check to see if we have the values cached, and if so, if the values
  # we have correspond to those just received.

#  my @temp_reply_array = @$reply_ref;

#  while (@temp_reply_array[0]) {
#    unless (exists ($global::cv{$global::current_dept}->{"@temp_reply_array[0]".'_cache'}->{"$host"}) 
#     && ($global::cv{$global::current_dept}->{"@temp_reply_array[0]".'_cache'}->{"$host"} eq
#            @temp_reply_array[1])) 
#     {
#      $global::log->warn(
#           "@temp_reply_array[0] cache value different/non-existent");
      # Cache the value
#      $global::cv{$global::current_dept}->{"@temp_reply_array[0]".'_cache'}->{"$host"} = 
#               @temp_reply_array[1];
#      # Make sure cv is in the cached gdvable hash.
#      $global::gdvable{"@temp_reply_array[0]".'_cache'} = 'hash';
#     } # end unless
#
#    shift @temp_reply_array; # Remove variable
#    shift @temp_reply_array; # Remove value
#   } # end while

  # Send back the information to the caller
  &reply('ghvr',$reply_ref);
  $global::log->warn("Sent ghvr response");
  return 1;
}

#============================================================================#
#
# Function Name: process_shv
#
# This function is called from the main program any time an "shv" message has
# been received by the software manager.  shv messages set control variables 
# on particular hosts.  
#
# This function replies to the caller with a shvr (shv response).
#
# Inputs:
# $username - user who sent this message
# $password - password supplied by that user
# $ghv_ref - reference to array which contains 
#		 the remaining fields from the ghv message.
#
#  Global:
#   $global::log - the log object
#   %global::cv - the control variables.
#
# Return Value(s):
#   0 in case of error
#   1 otherwise
#
# Global variable modifications:
# None.
#
# Example of usage:
#  &process_shv($username,$password,[$name1,$val1,$name2,$val2,...]);
#
#----------------------------------------------------------------------------#

sub process_shv
{
  my($username,$password,$dept,$host,@mesg_array) = @{$_[0]};
  my($mc_port,$header,$error,$junk,$reply_ref);

  $global::log->warn("Processing shv message: ".join(' ',@{$_[0]}));

  $global::current_dept = $dept;
  $global::log = $global::dept_log{$global::current_dept};

  # Check that the user is allowed.
  unless(&authorization_ok($username,$password))
   {
    reply('shve',['Error','Not authorized']);
   $global::log->warn("User $username not authorized to do shv.");
   return 0;
   }

  # At present, we simply forward the information to the module controller
  # In future we might need to update cached information and check whether
  # it is permissible to set this information).
  # We should also check if the host is up or not.
  
  # Make sure the host is in our department.
  unless($mc_port = $global::cv{$global::current_dept}->
       {'host_child'}{$host})
   {
    reply('shve',['Error','No such host']);
    $global::log->warn("No such host: $host.");
    return 0;
   }
  
  # Construct the array for the set call to the module controller
  unshift @mesg_array, 'SET';
  my $mc_message = [$host,$mc_port,'s',\@mesg_array];

  # Send and receive
  my $send_success = send_messages([$mc_message],$global::log);
  $global::log->warn("Failed in send_messages") unless $send_success;
  my($status,$replies) = receive_messages([$mc_message],$global::log);
  $global::log->warn("Failed in receive_messages") unless $status;
  ($junk,$junk,$header,$reply_ref) = @{$replies->[0]};
  $error = shift @$reply_ref;


  unless($header eq 'sr' && ($error =~ /^OK|^WARN/))
   {
    reply('shve',["Error $error"]);
    $global::log->warn("Bad reply: $header $error @$reply_ref");
    return 0;
   }

  # Get rid of crap fields
  splice(@mesg_array,0,3);
  splice(@$reply_ref,0,3);

#  # Cache the variables and their values
#  while (@mesg_array[0]) {
#    $global::cv{$global::current_dept}->
#      {"@mesg_array[0]".'_cache'}->{"$host"} = @mesg_array[1];
#    $global::gdvable{"@mesg_array[0]".'_cache'} = 'hash';
#    shift @mesg_array;  # Remove variable
#    shift @mesg_array;  # Remove value
#
#  $global::log->warn("just cached ".$global::cv->{"@mesg_array[0]".'_cache'}->{"$host"});
#  }
# 
  # Send back the information to the caller
  &reply('shvr',$reply_ref);
  $global::log->warn("Sent shvr response");
  return 1;
}

#============================================================================#


# Function Name: process_backlog

# !!! STUB FUNCTION ONLY !!!


# This function is called from the main program any time a message has been 
# handled, and is also called from a signal handler on timeouts.  Its purpose  
# is to handle old sets which could not be implemented at the time because
# a host was not responding.

# Inputs:

#  Actual arguments
#  Global:

# Return Value(s):

# Global variable modifications:

# Example of usage:


sub process_backlog
{
  return 1;
}

#============================================================================#

# Function Name: process_backlog

# !!! STUB FUNCTION ONLY !!!


# The purpose of this function is to contact all our children, and check if 
# they are up, and if the information we maintain about them is currently up to 
# date.

# Inputs:

#  Actual arguments
#  Global:

# Return Value(s):

# Global variable modifications:

# Example of usage:


sub poll_children
{
  # When to poll a child is maintained in the timeout manager.  The various
  # frequencies are themselves set by set messages to us.

}

#============================================================================#

# Function Name: save_state

# This function is called by the main program.  Its purpose is to save our 
# state information for future use.

# Inputs:
#  Actual Arguments:
#   None
#  Global:
#   $global::parameter_file - File containing parameters
 
# Return Value(s):
#   None
 
# Global variable modifications:
#  None
 
# Example of usage:
#  &save_state();


sub save_state
{
  open(PARAMETERS,">$global::parameter_file"); 
  select(PARAMETERS);

  select(STDOUT);
  close(PARAMETERS);
}


#============================================================================#
#============================================================================#

#			SPECIAL MESSAGE FUNCTIONS
		
# Handles sets and gets which are not regular in their nature.

#============================================================================#
#============================================================================#

#============================================================================#
#
# Function Name: notify_engine
#
# This notifies the engine of changes to the pertinant variables.
#
#----------------------------------------------------------------------------#
 
sub notify_engine
 
{ 
  my($full_variable,$value) = @_; 
  
  $global::delayed_sets_exist = 1; # We aren't going to do this yet.
  my ($mc_host,$mc_port) = split(':',$global::cv{$global::current_dept}
          ->{'aggregator_mc'});
 
  my $mesg_ref = ['SET','engine','v.v',$global::current_dept,
                  $full_variable,$value];
  push(@global::delayed_sets,[$mc_host,$mc_port,'s',$mesg_ref]);
 
} 

#============================================================================#
#
# Function Name: notify_engine_department
#
# This notifies the engine of changes to the department variable.
#
#----------------------------------------------------------------------------#
 
sub notify_engine_department

{
  my($value) = @_;
  notify_engine('department',$value);
}

#============================================================================#
#
# Function Name: notify_engine_parent_aggregator
#
# This notifies the engine of changes to the parent_aggregator variable.
#
#----------------------------------------------------------------------------#
 
sub notify_engine_parent_aggregator
 
{
  my($value) = @_;
  notify_engine('parent_aggregator',$value);
}

#============================================================================#
#
# Function Name: notify_engine_parent_manager
#
# This notifies the engine of changes to the parent_manager variable.
#
#----------------------------------------------------------------------------#
 
sub notify_engine_parent_manager
 
{
  my($value) = @_;
  notify_engine('parent_manager',$value);
}

#============================================================================#
#  
# Function Name: local_ACL_change
#
# Called from process_sdv when a new user is added or updated.
#
# Inputs:
#
#  Actual arguments
#  Global:
#
# Return Value(s): Returns 1 on success, 0 on failure.
#
# Global variable modifications:
#
# Example of usage:
#
#----------------------------------------------------------------------------#

sub local_ACL_change
{
  my($pass,$user) = @_;
 
  $global::log->warn("Change to local ACL.");

  # The local user must not be the same name as that of an inherited one.
  if (defined
      $global::cv{$global::current_dept}->{'inherited_ACL'}{$user})
   {
    $global::log->warn("Attempted change of Inherited ACL user $user as a local user.");
    return 0;
   }

  $global::cv{$global::current_dept}->{'local_ACL_names'}{$user} = 1;
   
  my($child,$address);
  # For each of our department children
  while(($child,$address) = each %{$global::cv{$global::current_dept}->
         {'dept_child'}})
   {
    $global::delayed_sets_exist = 1; # We aren't going to do this immediately.
    my($host,$port) = split(' ',$address);
    # Note, really we need to username and pass here, but we don't know
    # it.  do_delayed sets will patch it in later.
    push(@global::delayed_sets,[$host,$port,'sdv',
              [$child,'inherited_ACL{'.$user.'}',$pass]]);
   }
  return 1;
}   

#============================================================================#
#
# Function Name: inherited_ACL_change
#
# Only the parent sm is able to change the inherited ACL.
#
# Inputs:
#   user name, password
#
# Return Value(s): Returns 1 on success, 0 on failure.
#
# Global variable modifications:
#
# Example of usage:
#
#----------------------------------------------------------------------------#
 
sub inherited_ACL_change
{
  my($pass,$user) = @_;
 
  $global::log->warn("Change to inherited ACL.");

  # Additional access control is necessary.  Inhereted ACL can only be
  # changed by our parent SM.  Thus, the return address of the request must
  # be the same as our parent's address.
 
  unless (($global::return_address eq
           $global::cv{$global::current_dept}->{'parent_manager'}) ||
          ($global::return_address eq 'local_sm'))
   {
    $global::log->warn("Attempted change of Inherited ACL by other ".
                       "than parent SM.");
    return 0;
   }
 
  $global::cv{$global::current_dept}->{'inherited_ACL_names'}{$user} = 1;

  my($child,$address);
  # For each of our department children
  while(($child,$address) = each %{$global::cv{$global::current_dept}->
         {'dept_child'}})
   {
    $global::delayed_sets_exist = 1; # We aren't going to do this immediately.
    my($host,$port) = split(' ',$address);
    # Note, really we need to username and pass here, but we don't know
    # it.  do_delayed sets will patch it in later.
    push(@global::delayed_sets,[$host,$port,'sdv',
              [$child,'inherited_ACL{'.$user.'}',$pass]]);
   }
  return 1;
}
 
#============================================================================#
#
# Function Name: dept_child_change
#
# This handles sets in which a child is being removed or added.  It notifies
# the department aggregator with the change.
#
# Inputs:
#   Child value and name
#
# Return Value(s): Returns 1 on success, 0 on failure.
#
# Global variable modifications:
#
# Example of usage:
#
#----------------------------------------------------------------------------#

sub dept_child_change
{
  my($child_value,$child_index) = @_;
  my ($mesg_ref,$mc_message,$child_message);
  my ($mc_host,$mc_port) = split(':',$global::cv{$global::current_dept}->
        {'aggregator_mc'});


  # If we aren't in comm mode, there is no module controller to talk to.
  return 1 unless($global::output_mode eq 'comm');

  # Note - a robust version would first check if the child was actually up
  # or not.  This goes with implementation of child polling.

  # This is removing an existing child - tell our aggregator;
  if($global::cv{$global::current_dept}->
            {'dept_child'}{$child_index} && !$child_value)
   {
    $global::delayed_sets_exist = 1; # We aren't going to do this immediately.

    $mesg_ref = ['SET','engine','version',$global::current_dept,
                      'children{'.$child_index.'}',''];
    push(@global::delayed_sets,[$mc_host,$mc_port,'s',$mesg_ref]); 

    # Now disinherit any rulesets we gave the child
    my($child_host,$child_port) 
	= split(' ',$global::cv{$global::current_dept}->
             {'dept_child'}{$child_index});

    my ($rule);
    foreach $rule (keys %{$global::cv{$global::current_dept}->
                {'rulesets'}})
     {
      my $sdv_ref = [$child_index,"inherited_rulesets\{$rule\}",''];
      my $sm_message = [$child_host,$child_port,'sdv',$sdv_ref];
      push(@global::delayed_sets,$sm_message);
     }
#    foreach $rule (keys %{$global::cv{$global::current_dept}->
#                {'inherited_rulesets'}})
#     {   
#      my $sdv_ref = [$child_index,"inherited_rulesets\{$rule\}",''];
#      my $sm_message = [$child_host,$child_port,'sdv',$sdv_ref];
#      push(@global::delayed_sets,$sm_message);
#     }

   }
  # This is for a newly created child dept.
  elsif(!$global::cv{$global::current_dept}->{'dept_child'}{$child_index} 
           && $child_value)
   {

    $global::delayed_sets_exist = 1; # We aren't going to do this immediately.

    my($child_host,$child_port) = split(' ',$child_value);
    # Construct the array for the set call to the module controller.
    # This will tell our aggregator about its new child.
    $mesg_ref = ['SET','engine','version',$global::current_dept,
                      'children{'.$child_index.'}',1];
    push(@global::delayed_sets,[$mc_host,$mc_port,'s',$mesg_ref]); 

    # Construct the array for the set call to the child SM.
    # This will tell the child SM all of its inherited rulesets.
    my ($rule,$rule_value);
    foreach $rule (keys %{$global::cv{$global::current_dept}->{'rulesets'}})
     {
      $rule_value = $global::cv{$global::current_dept}->{'rulesets'}->{$rule};
      push(@global::delayed_sets,[$child_host,$child_port,'sdv',
                 [$child_index,'inherited_rulesets{'.$rule.'}',$rule_value]]);
     }
#    foreach $rule (keys %{$global::cv{$global::current_dept}->
#                          {'inherited_rulesets'}})
#     {   
#      $rule_value = $global::cv{$global::current_dept}->
#                                       {'inherited_rulesets'}->{$rule};
#      push(@global::delayed_sets,[$child_host,$child_port,'sdv',
#                 [$child_index,'inherited_rulesets{'.$rule.'}',$rule_value]]);
#     }
   }
  else
   {
    # We are changing the SM of a child dept.
    $global::log->warn("We are moving the SM of a child dept.\n");
    $global::cv{$global::current_dept}->{'dept_child'}{$child_index} = 
	$child_value;
   }
 
  return 1;
}# end dept_child_change

#============================================================================#
#
# Function Name: host_child_change
#
# This handles sets in which a child is being removed or added.  It notifies
# the department aggregator with the change.
#
# Inputs:
#   Child value and name
#
# Return Value(s): Returns 1 on success, 0 on failure.
#
# Global variable modifications:
#
# Example of usage:
#
#----------------------------------------------------------------------------#

sub host_child_change
{
  my($child_value,$child_index) = @_;
  my ($mesg_ref,$mc_message);
  my ($mc_host,$mc_port) = split(':',$global::cv{$global::current_dept}->
         {'aggregator_mc'});

  # NB - this routine will probably require significant changes to cope with
  # access control.

  # If we aren't in comm mode, there is no module controller to talk to.
  return 1 unless($global::output_mode eq 'comm');

  $global::delayed_sets_exist = 1; # We aren't going to do this immediately.

  # Note - a robust version would first check if the child was actually up
  # or not.  This goes with implementation of child polling.

  if($global::cv{$global::current_dept}->
     {'host_child'}{$child_index} && !$child_value )
   {
    # This is removing an existing child

    # Construct the array for the set call to the aggregator MC
    $mesg_ref = ['SET','engine','version',$global::current_dept,
                      'children{'.$child_index.'}',$child_value];
    push(@global::delayed_sets,[$mc_host,$mc_port,'s',$mesg_ref]); 

   }
  elsif(!$global::cv{$global::current_dept}->
         {'host_child'}{$child_index} && $child_value >= 0 )
   {
    # Construct the array for the set call to the aggregator MC
    $mesg_ref = ['SET','engine','version',$global::current_dept,
                      'children{'.$child_index.'}',1];
 
    push(@global::delayed_sets,[$mc_host,$mc_port,'s',$mesg_ref]); 

    # This is updating a child.  Contact the appropriate module
    # controller and tell it the scoop.

    if(defined $global::cv{$global::current_dept}->{'old_dept'} && 
        $global::cv{$global::current_dept}->{'old_dept'})
     {
      # This must have been set by our caller in order for us to know
      # how to address the modules we need.  Hopefully it showed up in
      # the sdv command before the host_child_change.
      my $mod_set = ['SET','*','*',
                $global::cv{$global::current_dept}->{'old_dept'},
 		'parent_aggregator', 
                $global::cv{$global::current_dept}->{'our_aggregator'},
 		'department', $global::current_dept,
 		'parent_manager', "$global::localhost:".
                $global::cv{$global::current_dept}->{'listen_tcp'},];
      push(@global::delayed_sets,[$child_index,$child_value,'s',$mod_set]); 
      my $mc_set = ['SET','module_controller','*',
                $global::cv{$global::current_dept}->{'old_dept'},
 		'parent_aggregator', 
                $global::cv{$global::current_dept}->{'our_aggregator'},
 		'parent_manager', 
 		("$global::localhost:".
                  $global::cv{$global::current_dept}->{'listen_tcp'}),];
      push(@global::delayed_sets,[$child_index,$child_value,'s',$mc_set]);  
      # Don't want to confuse anyone else.
      delete $global::cv{$global::current_dept}->{'old_dept'};
     }
   }
  else
   {
    # We don't presently understand this request.
    $global::log->warn("sm could not handle request in host_child_change.\n"
		."Supposed to set host_child{$child_index} from "
		.$global::cv{$global::current_dept}->{'host_child'}{$child_index}." to "
		."$child_value.");
    return 0;
   }
 
  return 1;
}# end host_child_change

#============================================================================#
#
# Function Name: ruleset_update
#
# Called from process_sdv when a ruleset variable must be updated.
#
# Inputs:
#
#  Actual arguments
#  Global:
#
# Return Value(s): Returns 1 on success, 0 on failure.
#
# Global variable modifications:
#
# Example of usage:
#
#----------------------------------------------------------------------------#

sub ruleset_update
{
  my($rule_value,$rule_index) = @_;

  # The local ruleset must not be the same name as that of an inherited one.
  if (defined
      $global::cv{$global::current_dept}->{'inherited_rulesets'}{$rule_index})
   {
    $global::log->warn("Attempted change of Inherited Rulesets as a local set.$rule_index");
    return 0;
   }

  $global::delayed_sets_exist = 1; # We aren't going to do this immediately.

  if($global::output_mode eq 'comm')
   {
    # First tell our aggregator what is going on.
    my $set_ref = ['SET','engine','v.v',$global::current_dept,
                'rulesets{'.$rule_index.'}', $rule_value];
    my($host,$port) = split(':',$global::cv{$global::current_dept}->
           {'aggregator_mc'});
    push(@global::delayed_sets,[$host,$port,'s',$set_ref]);
   }
  my($child,$address);
  # For each of our department children
  while(($child,$address) = each %{$global::cv{$global::current_dept}->
         {'dept_child'}})
   {
    my($host,$port) = split(' ',$address);
    # Note, really we need to username and pass here, but we don't know
    # it.  do_delayed sets will patch it in later.
    push(@global::delayed_sets,[$host,$port,'sdv',
              [$child,'inherited_rulesets{'.$rule_index.'}',$rule_value]]);
   }
  return 1;
}

#============================================================================#
#
# Function Name: inherited_ruleset_update
#
# Called from process_sdv when a ruleset variable must be updated.
#
# Inputs:
#
#  Actual arguments
#  Global:
#
# Return Value(s): Returns 1 on success, 0 on failure.
#
# Global variable modifications:
#
# Example of usage:
#
#----------------------------------------------------------------------------#
 
sub inherited_ruleset_update
{
  my($rule_value,$rule_index) = @_;

  $global::log->warn("Entered inherited_ruleset_update");

  # Additional access control is necessary.  Inhereted rulesets can only be
  # changed by our parent SM.  Thus, the return address of the request must
  # be the same as our parent's address.

  unless (($global::return_address eq 
           $global::cv{$global::current_dept}->{'parent_manager'}) ||
          ($global::return_address eq 'local_sm'))
   {
    $global::log->warn("Attempted change of Inherited Rulesets by other ".
                       "than parent SM.");
#
#  Added by J.Rowe for debugging 15-may-97
    $global::log->warn("Global return address is $global::return_address ".
                 "but current dept parent is ".
                "$global::cv{$global::current_dept}->{'parent_manager'}");
#
#
    return 0;
   }
 
  $global::delayed_sets_exist = 1; # We aren't going to do this immediately.
 
  if($global::output_mode eq 'comm')
   {
    # First tell our aggregator what is going on.
    $global::log->warn("Push to inform our aggregator");

    my $set_ref = ['SET','engine','v.v',$global::current_dept,
                'rulesets{'.$rule_index.'}', $rule_value];
    my($host,$port) = split(':',$global::cv{$global::current_dept}->
           {'aggregator_mc'});
    push(@global::delayed_sets,[$host,$port,'s',$set_ref]);
   }

  my($child,$address);
  # For each of our department children
  while(($child,$address) = each %{$global::cv{$global::current_dept}->
         {'dept_child'}})
   {
    my($host,$port) = split(' ',$address);
    # Note, really we need to username and pass here, but we don't know
    # it.  do_delayed sets will patch it in later.
    $global::log->warn("Push to our department child $child");
    push(@global::delayed_sets,[$host,$port,'sdv',
              [$child,'inherited_rulesets{'.$rule_index.'}',$rule_value]]);
   }

  # We must add this into the rulesets also.  This allows a single get to
  # retreive all rulesets.

  $global::cv{$global::current_dept}->{'rulesets'}{$rule_index} = $rule_value;
  # Remove the ruleset if that is the call.
  unless(defined $global::cv{$global::current_dept}->{'rulesets'}{$rule_index}
        && $global::cv{$global::current_dept}->{'rulesets'}{$rule_index})
   { 
    # We are deleting this control variable because it has been set to null
    delete $global::cv{$global::current_dept}->{'rulesets'}{$rule_index};
   } 

  return 1;
}

#============================================================================#
#
# Function Name: local_managers_change
#
# This is called to terminate a SM and its children SM's.  If there are more
# than one SM running on this host, then this manager is simply removed.  If
# there is only one SM, then this causes the program to exit.  If the value
# passed in is 'M', this indicates that a SM is being moved, so we will only
# kill the manager on this host, and not the engine for the department.
#
# Inputs:
#   Child value and name, user name and password
#
# Return Value(s): Returns 1 on success, 0 on failure.
#
# Global variable modifications:
#
# Example of usage:
#
#----------------------------------------------------------------------------#

sub local_managers_change
{
  my ($sm_value,$sm_index,$user_name,$password) = @_;
  my ($mesg_ref,$mc_message,$child_message);
  my ($mc_host,$mc_port) = split(':',$global::cv{$global::current_dept}->
        {'aggregator_mc'});
#
# Added for debugging by J.Rowe 7-may-97
  my @j_keys = ( keys %global::local_managers );
  $global::log->warn("Current department is $global::current_dept");
  $global::log->warn("Current local_managers are @j_keys");
  $global::log->warn("Indexed is $global::local_managers{$sm_index}");
#
# 
  unless ($global::local_managers{$sm_index})
   {
    $global::log->warn("$sm_index is not a manager on this host.");
    return 0;
   }

  # We are moving an SM, so we do not want to kill the engine. 
  if ($sm_value eq 'M')
   {
     # Remove this SM form the local list of managers.  If the list is now
     # empty, then the program will terminate.
     $global::log->warn("Removing local_manager $sm_index");
     delete $global::local_managers{$sm_index};
     my @keys = keys %global::local_managers;
     if ($#keys == -1)
      {
       # Must terminate the SM.
       reply('sdvr',[1]);
       $global::log->warn("Sent sdvr response, now will exit SM.");
       exit 1;
      }
    return 1;
   }

  # We are removing an SM.
  if ($sm_value eq '')
   {
    # If we aren't in comm mode, there is no module controller to talk to.
    return 1 unless($global::output_mode eq 'comm');
 
    my ($child,$address,$host,$port);

    $address = $global::cv{$global::current_dept}->{'aggregator_mc'};
    my($host,$port) = split(':',$address);

    my @mesg_array = ('SET','module_controller','version',
                    $global::cv{$global::current_dept}->{'parent'},
                    'mc_command', 'KILL',
                    'mod_module', 'engine',
                    'mod_version', '*',
                    'mod_department',$global::current_dept,
                    );  
 
    my $mc_message = [$host,$mc_port,'s',\@mesg_array];

    # Send and receive
    my $send_success = send_messages([$mc_message],$global::log);
    $global::log->warn("Failed in send_messages") unless $send_success;
    my($status,$replies) = receive_messages([$mc_message],$global::log);
    $global::log->warn("Failed in receive_messages") unless $status;
    my ($junk,$junk,$header,$reply_ref) = @{$replies->[0]};
    my $error = shift @$reply_ref;
  
    unless($header eq 'sr' && ($error =~ /^OK|^WARN/))
     {
      reply('shve',["Error $error"]);
      $global::log->warn("Bad reply: $header $error @$reply_ref");
      return 0;
     }
    
    # Now we tell all of our children SM's to die.
    while(($child,$address) = each %{$global::cv{$global::current_dept}
          ->{'dept_child'}})
     {
      my($host,$port) = split(' ',$address);
 
      # This tells a child dept to terminate its SM and engine.
      my $sdv_ref = [$user_name, $password,"local_managers\{$child\}",''];
      my $sm_message = [[$host,$port,'sdv',$sdv_ref]];

      my $send_status = send_messages($sm_message,$global::log);
      return (0) unless $send_status;
     } # end while
    # Remove this SM form the local list of managers.  If the list is now
    # empty, then the program will terminate. 
    $global::log->warn("Removing local_manager $sm_index");
    delete $global::local_managers{$sm_index};
    my @keys = keys %global::local_managers;
    if ($#keys == -1)
     {
      # Must terminate the SM.
      reply('sdvr',[1]);
      $global::log->warn("Sent sdvr response, now will exit SM.");
      exit 1;
     }
    return 1;
   }# end if

  return 1;

}# end local_managers
 
#============================================================================#
#============================================================================#

#			AUTHORIZATION CHECKING FUNCTIONS
		
#============================================================================#
#============================================================================#

# Function:  authorization_ok

# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = #
# Note:  This is a stub function which must be implemented.
# = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = #

# This function checks to see if the user is authorized at this particular
# department in the hierarchy.

# Inputs:
#  Actual Arguments:
#   $username - user making the request
#   $password - their password.
#  Global:
#   $global::log - Reference for log object.
 
# Return Value(s):
# 1 if they are kosher, 0 if not.
 
# Global variable modifications:
#  None
 
# Example of usage:
#  unless(&transaction_authorization_ok($messageref))
 

sub authorization_ok
{

# ACL

  my($username,$password) = @_;
  $global::log->warn("Authorization checked for $username.");
  # Note - ultimately failures should be logged here.

  return 1;
}

#============================================================================#
#============================================================================#

#				COMMUNICATION INTERFACE

# Note that several input/output modes are supported.  The one in use is
# controlled by $global::output_mode.  Possible values are

# stdio - all input and output is done on STDIN and STDOUT

# file  - input is done on STDIN, and output is done to a file with name
#	of the form host:port.  This allows us to mock up a hierarchy on
#	a single machine and figure out what happened afterwards from
#	the files.

# comm - this is the production mode.  I/O is done via the Comm.pm interface.

#============================================================================#
#============================================================================#

# Function Name: get_incoming_message

# Gets one incoming message in GrIDS Common Packet Format and the hierarchy 
# packet format. Returns it as an array of two scalars.  The first is the 
# header.  The second is a reference to an array containing the remaining 
# fields of the message.

# Inputs:
# None.

#  Global:
#   %global::output_mode - what kind of i/o we are doing

# Return Value(s):
#   $header - string header from the GCPF format
#   $body_ref - reference to list containing the body fields from the packet.

# Global variable modifications:
# $global::return_address - the caller is stored for future use in replies.

# Example of usage:
#  ($header, $body_ref) = &get_incoming_message();

sub get_incoming_message
{
  if($global::output_mode eq 'comm')
   {
    # Real I/O
    my($array) = [];
    my($host,$port,$header);

    until (defined $host)
     {
      ($host,$port,$header,@$array) = Comm::tcp_recv('blocking');
      unless($host && $port && $header)
       {
        $global::log->warn("Error in tcp_recv call in get_incoming_message");
       }     
      $global::return_address = "$host:$port";

     }
    return ($header,$array,$host,$port);
   }
  else
   {
    # File and Stdio input
    RESTART:
    my $input = <STDIN>; 
    # We may have to seek to reset the EOF error on a file after we had
    # reached the end but there is now more data for us.
    if($global::output_mode eq 'file' && !defined $input)
     {
      seek(STDIN,0,1);
      sleep 1;
      goto RESTART;
     }
    chomp $input;
      $global::log->warn("input: $input");
    my @array = split('#',$input);
    foreach (@array) {$_ = '' if(/^_$/);}
    # Strip off the return address in 'file' mode
    my($host,$port);
    if($global::output_mode eq 'file')
     {
      $global::return_address = shift @array;
      ($host,$port) = split(':',$global::return_address);
     }
    else
     {($host,$port) = ('',0);}
    my $header = shift @array;
    return ($header,\@array,$host,$port);
   }
}

#============================================================================#

# Function Name: reply

# Sends a message back to the caller who most recently addressed us.

# Inputs:
# $header - type of GCPF this is.
# $messageref - ref to either 1) a list of fields to send, or
#			      2) a hash to send as name,val,name,val.

#  Global:
#   $global::log - the log object
#   %global::output_mode - what kind of i/o we are doing
#   $global::original_dir - the directory we were called in
#   $global::return_address - the last caller


# Return Value(s):
#   None.

# Global variable modifications:
# None.

# Example of usage:
#  &reply($header, $body_ref) = ;

sub reply
{
  my($header,$messageref) = @_;
  my($output);

  if($global::output_mode eq 'comm')
   {
    # Output via Comm.pm
    my($host,$port) = split(/:/,$global::return_address);
    
    if ($host eq '')
     {
      $global::log->warn("SM completed an internal $header.");
      return;
     }
    $global::log->warn("SM about to send $header to $host:$port\n");
    if($header eq 's' && ref $messageref eq 'ARRAY')
     {
      $global::log->warn(join(' ',@$messageref)."\n");
     }
    if(ref $messageref eq 'ARRAY')
     {
      my $comm_return = Comm::tcp_send($host,$port,$header,@$messageref); 
      unless ($comm_return eq 'true')
       {
        $global::log->warn("tcp_send failed:$comm_return");
        return;
       }
     }
    elsif(ref $messageref eq 'HASH')
     {
      my $comm_return = Comm::tcp_send($host,$port,$header,%$messageref); 
      unless ($comm_return eq 'true')
       {
        $global::log->warn("tcp_send failed:$comm_return");
        return;
       }
     }
    else
     {die "Bad reference type in reply function";}
    Comm::tcp_close($host,$port)
   }
  else
   {
    # Figure out what the output should be (based on argument type).
    if(ref $messageref eq 'ARRAY')
     {
      foreach (@$messageref) {$_ = '_' if($_ eq '');} 
      $output = join('#',$header,@$messageref)."\n"; 
     }
    elsif(ref $messageref eq 'HASH')
     { 
      foreach (values %$messageref) {$_ = '_' unless $_;}
      $output = "$header\n";
      $output = join('#',$header,%$messageref)."\n" if 
		$messageref && %$messageref && keys %$messageref > 0;
     }
    else
     {die "Bad reference type in reply function";}

    # Actually spit it out.
    if($global::output_mode eq 'stdio')
     {
      print $output;
     }
    elsif($global::output_mode eq 'file')
     {
      open(OUTPUT,">>$global::original_dir/pipes/$global::return_address") ||
       $global::log->die(
        "Couldn't open $global::original_dir/pipes/$global::return_address:$!");
      $output = "$global::my_address#$output";
      $global::log->warn("output: $output");
      print OUTPUT $output;
      close OUTPUT;
     }
   }
}

#============================================================================#

# Function Name: parse_var_name

# Figure out whether a control variable is indexed or straight and split
# it into pieces if necessary.

# Inputs:
# $raw_variable - the raw control variable name.

#  Global:
#   $global::log - the log object


# Return Value(s):
#   $variable - the name of the control variable
#   $index - the index if there is one, or undef if there isn't.

# Global variable modifications:
# None.

# Example of usage:
#  ($variable,$index) = parse_var_name($raw_variable);


sub parse_var_name
{
  my($variable) = @_;

  if($variable =~ /^\w[\w\:\-\,\.]*$/)
   {
    # The variable is a straight one.
    return ($variable);
   }
  elsif($variable =~ /^(\w[\w\:\-\,\.]*)\{(\w[\w\:\-\,\.]*)}$/)
   {
    # an indexed variable
    return ($1,$2);
   }
  else
   {
    # Trouble
    $global::log->warn("Bad variable name $variable\n");
    reply('e',{});
    return undef;
   }
}

#============================================================================#
