eval 'exec perl -x $0 ${1+"$@"}' # -*-perl-*-
  if 0;
#!perl -w
#
# ======================================================================
# This file is Copyright 1998,1999 by the Purdue Research Foundation and
# may only be used under license.  For terms of the license, see the
# file named COPYRIGHT included with this software release.
# AAFID is a trademark of the Purdue Research Foundation.
# All rights reserved.
# ======================================================================
#
# AAFID::Agent package
#
# AAFID project, COAST Laboratory, CERIAS, 1998-1999.
# 
# Diego Zamboni, Jan 27, 1998.
#
# $Id: Agent.pm,v 1.28 1999/09/03 17:08:52 zamboni Exp $
#
# NOTE: This file is in Perl's POD format. For more information, see the 
#       manual page for perlpod(1).
#

package AAFID::Agent;

# The following keeps up with the RCS version number. The self-assignment
# keeps the -w switch from complaining (because $VERSION may not be used
# here, but it is used in our base class).
$VERSION = do { my @r = (q$Revision: 1.28 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; $VERSION = $VERSION;

use vars qw(
	    @ISA
	    %PARAMETERS
	    $VERSION
	    @EXPORT
	   );

use strict;
use AAFID::Log;
use AAFID::Entity;
use AAFID::Comm;
use AAFID::Message;
use AAFID::Common;
use AAFID::Config;
use Comm::Reactor;
use Carp;
use Exporter();

@ISA=qw(AAFID::Entity
	AAFID::Log
	Exporter
       );

@EXPORT=qw(_EndOfEntity);

=pod

This is the base class for all the agents in the AAFID system. By
inheriting from this class, and agent writer can concentrate only on
performing the tasks that the agent should really do, without worrying
about how to communicate with the transceiver, how to send or receiver
messages, etc.

=head1 How to write agents

Of course, this is the first section because everybody is impatiently
wanting to start writing agents, and will not read through the rest of
the documentation to learn in more detail how agents work and how they
are implemented. However, if you have time, please go through it, it
may be interesting.

=head2 Quick guide

=over 4

=item 1.

The following is the super-essential template for an agent. You can
find a more complete one (which is the one you should use, in fact) in
the F<AAFID/Agents/Template.pm> file.

  package YourAgent;

  $VERSION = do { my @r = (q$Revision: 1.28 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r };
  $VERSION = $VERSION; # Keeps -w from complaining.

  %PARAMETERS=(Description => "Your agent description",
	       CheckPeriod => 10, # or whatever you want
	      );

  use AAFID::Agent;
  use AAFID::Common;
  @ISA=qw(AAFID::Agent);

  sub Init {
    my $self=checkref(shift);
    # Your initialization code here. Optional.
  }

  sub Check {
    my $self=checkref(shift);
    # Your check code here. Mandatory.
    return ($status, $message);
  }

  sub Cleanup {
    my $self=checkref(shift);
    # Your cleanup code here. Optional.
  }

  # Important, to make your agent standalone.
  _EndOfEntity;

=item 2.

Put the appropriate values in the C<%PARAMETERS> hash.

=item 3.

Put the appropriate code in the C<Init>, C<Cleanup> and C<Check>
routines. When writing C<Check>, keep in mind the following:

=over 4

=item *

You can do whatever is doable with Perl.

=item *

Do not take too long. Specifically, do not take more than
C<CheckPeriod>, or you will mess up the timing of your checks (if your
check takes more than that, then increase C<CheckPeriod>). Also, do
not take exactly C<CheckPeriod>, you have to leave some time to allow
for processing of any requests or commands that the agent receives.

=item *

Return a two-element list of the form C<($status, $message)>, where
C<$status> is an integer from 0 to 10, with 0 meaning "no problem" and
10 meaning "extremely severe problem", and C<$message> is a textual
description of the situation.

=item *

See the sections below for the main facilities you have access to.

=back 4

=item 4.

End your file with and C<_EndOfEntity;> line. This will ensure that
your agent is both loadable by other entities (for example, a
transceiver) and runnable as a standalone program.

=back 4

=head2 Testing and debugging

There are two characteristics of all AAFID entities that make them
easier to test and debug:

=over 4

=item 1.

They are all also standalone programs.

=item 2.

They receive commands from standard input, and produce results to
standard output. In a running system, these handles may be associated
with pipes, TCP connections, or whatever, but if you run your agent
from the command line, it allows you to interact with it directly.

=back 4

Thus, the recommended steps for debugging an agent are the following:

=over 4

=item 1.

Start your program with C<#/p/perl/perl -w>, it will catch a lot of
errors and make your life easier.

=item 2.

Put a C<use strict;> near the beginning of the agent. It will also
help in catching some errors, and in general force you to make your
code cleaner and easier to understand. Be aware that when you use
the C<strict> module, you have to explicitly declare all global
variables, and that includes the standard global variables, so you
have to also put this:

  use vars qw($VERSION %PARAMETERS @ISA);

=item 3.

Liberally sprinkle C<$self-E<gt>Log> commands in your code. In particular,
create a "debug" category. This category is special in that messages sent
to it also include the file name and line number from where they were
generated. This helps in tracing the execution of the program. What I do
is send the logging to a fail, and to a C<tail -f> on that file while
the program executes.

=item 4.

Once you have checked the syntax of your program, run it from the command
line. Once it starts, it should produce a message of the following form:

  CONNECT CHILD <id> - <time> <class> <description>

where I<id> is the agent ID, I<time> is a numeric representation of the
current time, I<class> is the class name of the agent, and I<description>
is the description you provided.

Immediately afterwards, your agent should produce at least one message
of the following form:

  STATUS_UPDATE NOTYPE <id> - <time> Status => <st>, Message => <msg>

where I<st> and I<msg> are the status and message returned in the
first invocation of C<Check>. From then on, the C<Check> method will
be invoked automatically every C<CheckPeriod> seconds. Whenever the
status value or the message returned changes, a new STATUS_UPDATE
message will be generated. Most of the time, unless things are
changing a lot, the agent should be silent.

=item 5.

Try triggering the condition your agent is looking for, and see if it
produces the correct output.

=item 6.

To give commands to your agent, you can use the following template:

  COMMAND <cmdname> me - - <parameters>

where I<cmdname> is the command name, and I<parameters> is the
parameters that the command receives, in C<name =E<gt> value>
notation, separated by commas. The C<me> string will be interpreted by
the agent as the name of the entity that is sending the command
(you can use anything else you want). The first "C<->" corresponds to
the TO field of the message, which can be empty since there is no
possible confusion about who the message is for. The second "C<->"
is the time, and when a dash is provided, the recipient of the 
message will set it to the time at which the message was received.

For the parameters, remember that they will be evaluated as Perl
code, so you have to follow the Perl syntax rules. In particular,
quoting is important. However, it also means that you can specify
any Perl expressions, and they will be evaluted in the context of
the agent.

=item 6.

The standard commands EVAL, SET_PARAMS and DUMP_YOURSELF can be useful
for examining and controlling your agent. Remember that EVAL allows
you to execute arbitrary Perl code in the context of the agent. The
output of DUMP_YOURSELF contains a field called C<Me> that itself 
contains a dump of the agent object as generated by the standard
B<Data::Dumper> class. Internally, the object is represented by a 
hash containing name/value pairs for all the parameters, and so by
using DUMP_YOURSELF you can examine the whole state of the object.

For example, to set the parameter "Foo" to the value 8, "Bar" to "Hi"
and "MyTime" to the current date and time string, you can use the
following:

  COMMAND SET_PARAMS me - - Foo=>8, Bar=>"Hi", "MyTime"=>scalar localtime

you can then use the following to check that the new parameters were
indeed set:

  COMMAND DUMP_YOURSELF

note in this example that only the first two fields of the message
are specified. The others take on default values automatically.

You can also use the standard GET_PARAMS command to check the new values
of the parameters:

  COMMAND GET_PARAMS me - - Params => 'Foo, Bar, MyTime'

=item 7.

If you have defined new commands, try them out.

=item 8.

When you want to quit, you can either give a STOP message (simply
type STOP, without any other fields) or press CTRL-D. Either way,
the agent should produce a message of the following form:

  DISCONNECT CHILD <id> - <time> <message>

where I<message> will be "STOP command or message" or "Close on STDIN",
depending on the method you used.

=back 4

=head2 Tutorial

In this section, we will go through the construction of an agent for
checking whether any of a group of users has an F<.rhosts> file in his
or her home directory that have incorrect permissions.

Here are some of the questions that we have to ask ourselves when
writing a new agent, and their answers (you may have reached different
answers).

=over 4

=item What do we want to detect?

Incorrect permissions on user F<.rhosts> files. In this case, we will
define "incorrect permissions" as "having any permission at all for
anybody else than the owner". Under this definition, permissions
400, 200, 100 or any combination of them is acceptable, but any others
(in particular, any permission on the "group" and "other" fields) are
not.

=item How do we want to report it?

Our agent has to generate a status level and a message. The status has
to be a number between 0 and 10, inclusive. We want the number to
convey a sense of the danger created by the problems detected. So, for
example, a world read permission has to be flagged, but with a "less
dangerous" level than a world write permission. We will use the
following criteria to compute the status:

=over 4

=item *

If nobody has incorrect permissions of any kind, the status is 0 (zero).

=item *

For each user that has group or world read permission in his
F<.rhosts> file, we will add 0.5 to the status, rounding up (so that a
minimum status of 1 will be generated if one user has read permissions
in the file).

=item *

For each user that has group or world write permission in his F<.rhosts>
file, we will add 3 to the status.

If the user name happens to be "root", the status will immediately
jump to 10.

=item *

If the home directory of the user is not readable (and thus we cannot
check permissions on the F<.rhosts> file) or the F<.rhosts> file does
not exist, that user is considered safe.

=item *

If the sum goes over 10, it is set to 10.

=back 4

The message will be a string with the following format:

  Read:user,...:Write:user,...

where every user that has wrong read permissions will be listed in the
"Read" field, and every user that has wrong write permissions will be
listed in the "Write" field. Thus, a user may appear on both of them.

=item How do we want to interact with it?

The essential functionality that we will need to control and monitor
this agent is the capability to specify which users we want to monitor,
and to request the list of users that are currently being monitored.
Thus, we will define three new commands:

=over 4

=item C<ADDUSERS Users =E<gt> 'user1[,user2,...]'>

Adds the users listed in the C<Users> parameter to the list of users
to be monitored.

=item C<DELUSERS Users =E<gt> 'user1[,user2,...]'>

Takes the named users off the monitoring list.

=item C<LISTUSERS>

Returns a field called "Users" that contains the list of users currently
being monitored.

=back 4

=back 4

Ok, now we have the functionality we want sketched out, so we can
start writing our agent. We start by specifying the package name,
version number, and standard parameters.

  #!/p/perl/perl -w

  package CheckRhosts;
  $VERSION = do { my @r = (q$Revision: 1.28 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r };
  $VERSION = $VERSION;
  %PARAMETERS=(
	     Description	=> "Check permissions on .rhosts files",
	     CheckPeriod	=> 10,  # Seconds
	    );

Next, we include the packages, declare global variables, and set 
inheritance relationships.

  use strict;
  use AAFID::Agent;
  use AAFID::Log;
  use AAFID::Common;
  use vars qw(@ISA $VERSION %PARAMETERS);
  @ISA=qw(AAFID::Agent AAFID::Log);

Next, we should include a POD section called "Description", which contains
more or less the discussion above about how the agent works, how the
status is computed, and such. I will not repeat it here for the sake
of space.

  =head1 Description

  The description of what the agent does goes here.

Now we have to start with the actual implementation. The agent needs to
keep a list of users that is has to monitor, and thus has to use a
parameter to do that. In our case, the parameter could be called
"MyUsers", and could contain a reference to a hash, whose keys are
the users that we have to monitor. Storing the users in the keys
of the hash (instead of storing them in a list, for example) makes
it very simple to eliminate repeated entries in the list.

Every agent (in fact, every AAFID entity) by default registers the
following log categories: "debug", "messages", "errors", "I/O" and
"signals", although only "messages" and "errors" are activated
by default. All categories by default log to a file called "AAFID.log".

In our C<Init> method, we will activate the "debug" log category, and
we will initialize the "Myusers" parameter to a reference to an empty
hash.

  sub Init {
    my $self=checkref(shift);
    $self->Log_activate("debug");
    $self->setParameters(MyUsers => {});
  }

The C<Check> method is the one that actually does the work. It goes
through the list, and for each user, gets the permissions in his
F<.rhosts> file, and computes a status according to the rules we
set previously.

  sub Check {
    my $self=checkref(shift);
    my @users=keys(%{$self->getParameter('MyUsers')});
    my @readusers=();
    my @writeusers=();
    my $readstatus=0;
    my $writestatus=0;
    my $status;
    my $message;
    my $u;
    $self->Log("debug", "Entering Check routine.\n");
    foreach $u (@users) {
      $self->Log("debug", "Checking user $u.\n");
      my $home=(getpwnam $u)[7];
      if (!$home) {
	$self->Log("errors", "Null home directory for user $u.\n");
	return;
      }
      # If the stat fails (because no permission, or file doesn't exit)
      # we default the mode to zero.
      my $mode=(stat "$home/.rhosts")[2] || 0;
      my $omode=sprintf("%o", $mode);
      if (($mode & oct('022'))>0) { # Write permission.
	$self->Log("debug", "$u has incorrect write permissions $omode.\n");
	$writestatus+=3;
	push @writeusers, $u;
	$writestatus=10 if $u eq 'root';
      }
      if (($mode & oct('044'))>0) { # Read permission.
	$self->Log("debug", "$u has incorrect read permissions $omode.\n");
	$readstatus+=0.5;
	push @readusers, $u;
      }
    }
    $status=$writestatus+int($readstatus+0.5); # Round $readstatus up.
    $status=10 if $status>10;
    $message="Read:".join(",", @readusers).":Write:".join(",", @writeusers);
    $self->Log("debug", "Returning ($status, '$message')\n");
    return ($status, $message);
  }

We use simple bit tests for checking the permissions. As you can see,
there is no dark science here, just Perl code (are they the same?).

We are almost done. Our agent does not need anything special done at
termination, so we do not define a custom C<Cleanup> method. The thing
left to do is to define our new commands.

First, the ADDUSERS command receives a C<Users> argument that
contains a command-and/or-space-separated list of user names, and
adds them to the C<MyUsers> parameter.

  sub command_ADDUSERS {
    my $self=checkref(shift);
    my ($msg, %params)=@_;
    if ($params{Users}) {
      # splitList is part of the AAFID::Common package
      my @users=splitList($params{Users});
      my $usersref=$self->getParameter('MyUsers');
      my $u;
      $self->Log("debug", "Adding to list of monitored users: @users.\n");
      foreach $u (@users) {
	$usersref->{$u}=1;
      }
    }
    else {
      $self->Log("errors", "No Users argument provided.\n");
    }
    return undef;
  }

The command only does something if the expected parameter C<Users> is 
provided. It also makes us of the C<splitList> routine, which comes
from the B<AAFID::Common> package, and which simply splits a string 
into comma-and/or-space-separated elements, and returns them in a list.
Then, they are all simply used as keys into the C<MyUsers> parameter to
add them to the list.

The DELUSERS command does the opposite: it deletes the provided users
from the C<MyUsers> parameter.

  sub command_DELUSERS {
    my $self=checkref(shift);
    my ($msg, %params)=@_;
    if ($params{Users}) {
      my @users=splitList($params{Users});
      my $usersref=$self->getParameter('MyUsers');
      my $u;
      $self->Log("debug", "Removing from list of monitored users: @users.\n");
      foreach $u (@users) {
	delete $usersref->{$u};
      }
    }
    else {
      $self->Log("errors", "No Users argument provided.\n");
    }
    return undef;
  }

Finally, the LISTUSERS command returns the users currently in the list.

  sub command_LISTUSERS {
    my $self=checkref(shift);
    $self->Log("debug", "Reporting current list of users.\n");
    return {Users => join(",", keys %$self->getParameter('MyUsers'))};
  }

Notice how we have to dereference the value that C<getParameter> returns
before passing it to C<keys>. Also, notice that we are returning a 
hash B<reference>, not a hash itself. This is denoted by the use of
braces instead of parenthesis in the C<return> statement.

Now we are finished. We simply have to provide the standard end of
entity marker:

  _EndOfEntity;

And we can go on and play with our agent all that we want.

(missing here: a tutorial on running and testing the agent).

=head2 Standard facilities

=head3 Parameters

All entities in the AAFID system (and that includes agents, of course)
have the ability to store internal parameters that can be used to keep
any form of state. In fact, these parameters are used to store, among
other things, the Description and CheckPeriod that you specify.

Your agent can use this parameters facility to keep state between
different invocations of C<Check>. No provision for keeping state
between different invocations of the agent is provided at this point.

It is recommended that you use a unique prefix for the names of any
parameters you define in your agent. For example, you could prefix all
parameter names with the class name (for example, use the
'CheckRhosts' prefix if your agent is called CheckRhosts). Another
trick is the following: there is no clash among parameter name spaces
for different agents (even different instances of the same agent
type), so you can use the same prefix for all your agents. It is
guaranteed that no internal parameters start with the prefix 'My', so
you can use that for your agent parameters, as in the examples below.

It is important to remember that all the subroutines for accessing
parameters, as most other subroutines in the AAFID system, have to be
invoked as instance methods, this is through the C<$self> variable (or
its equivalent, your naming may vary).

The two main points of entry to the parameters system are the
following: 

=over 4

=item C<$self-E<gt>setParameters(Param1 => value[, Param2 => value ...])>

Sets the specified parameters to the desired values. The parameter
names can be arbitrary strings, and you can leave them unquoted (or
quote them, if that makes you feel better). The values can be any
valid Perl value, such as scalars, arrays, references, etc.

=item C<$self-E<gt>getParameter('Param')>

Returns the current value of the named parameter. In this case, it is
recommended that you quote the parameter name, to avoid "may clash
with a future reserved word" warnings from Perl, or compilation errors
if you do C<use strict;> (highly recommended). An C<undef> value is
returned if the parameter is not defined.

Please remember that this function returns a B<copy> of the value of
the parameter, so you cannot modify the parameter by using
C<getParameter>, assigning the returned value to a variable, and then
assigning to it. Of course, if the parameter is a reference, you can
dereference it and modify the value to which it points.

=back 4

There is a much wider repertoire of functions for accessing
entity parameters. These are:

=over 4

=item C<$self-E<gt>setParameter(Param1 => value)>

Exactly the same as C<setParameters> (you can even give it multiple
parameters to set), but more intuitive when setting only one 
parameter. Pure, distilled laziness.

=item C<$self-E<gt>getParameters(Param1[, Param2 ...])>

Returns a hash with the named parameters as keys to their
corresponding values. Nonexistent parameters are returned associated
to an C<undef> value.

=item C<$self-E<gt>getParametersList(Param1[, Param2 ...])>

Returns a list containing only the values of the parameters requested,
in the order in which they were requested, and with C<undef> in place
of undefined parameters.

=item C<$self-E<gt>getParameterRef(Param)>

Returns a B<reference> to the value of the named parameter. Thus, this
can be used to later directly modify the parameter without going
through the C<setParameters> routine. See the examples below.

=item C<$self-E<gt>getParametersRef(Param1[, Param2 ...])>

Same as C<getParameters>, but the values in the hash are references to
the parameters.

=item C<$self-E<gt>getParametersListRef(Param1[, Param2 ...])>

Same as C<getParametersList>, but the values returned as references to
the parameters.

=back 4

Some examples of using entity parameters follow:

  $self->setParameters(MyLastStatus => 0, MyLastMessage => "");
  my $statref=$self->getParameterRef('MyLastStatus');
  $$statref=3;  # This actually modifies the parameter through the ref.
  $self->setParameter(MyOpenFiles => []); # Reference to a list
  $self->setParameter(MyUserHomes => {}); # Reference to a hash
  my @params=$self->getParametersList('MyOpenFiles', 'MyUserHomes');
      # Get the references, but them in the @params list.
  my $homes=@params[1]; # Get the reference
  $homes->{zamboni}="/homes/zamboni";  
       # Dereference, create an element in the parameter.
  $ {$self->getParameterRef('MyLastMessage')}="New message";
       # Ugh, convoluted, ugly, but shows the usage
  @params[0]->[3]="file3";  # Modify the parameter through the list ref.

=head3 Logging

All AAFID entities have access to a standard logging facility that
supports categorization of log messages, and individual
per-entity-per-category control of the logging. To use it, you have to
do the following:

=over 4

=item 1.

Make your agent a subclass of B<AAFID::Log> (apart from
B<AAFID::Agent>), as follows:

  use AAFID::Agent;
  use AAFID::Log;
  @ISA=qw(AAFID::Agent AAFID::Log);

=item 2.

In your C<Init> method, register the categories you want to log to by
using the C<Log_register> method, as follows:

  $self->Log_register("category"[, "file"|handle ]);

The second parameter specifies a filename or file handle to which the
messages for that category will be sent. If it is not specified, the
messages by default are sent to a file called "ClassName.log", where
ClassName is the name of your agent class.

=item 3.

Activate log categories you want activated by default by using the
C<Log_activate> method, which takes as arguments a list of categories
to activate.

  $self->Log_activate("category"[, "category2" ...]);

=item 4.

When you want to generate a log message, use the C<Log> method:

  $self->Log("category", "Message\n");

The message has to include a newline at the end if you want one
generated.

=item 5.

Categories are by default deactivated when they are registered. If you
want to deactivate a category you have already activated, you can use
the C<Log_deactivate> method.

  $self->Log_deactivate("category");

=back 4

=head3 Utility routines

The B<AAFID::Common> package defines a number of subroutines that perform
useful and often repeated actions.

(description of the routines in AAFID::Common missing here).

=head3 Standard entity commands

All AAFID entities have the capability of receiving messages and
commands. For a full description of the message format, see the
documentation for B<AAFID::Message> and B<AAFID::Entity>. Different
message types are usually used internally for communication and
synchronization between entities. A command is simply a message of
type COMMAND, whose subtype indicates the command to execute. A
command receives arguments in the form of named parameters, which are
specified in the DATA field of the message. If a command wishes to
return something to the entity that requested its execution, it must
return a hash reference containing any field name/value pairs that it
wishes to return.

The standard commands to which all agents automatically respond are:

=over 4

=item STOP

Stop the agent, first calling the C<Cleanup> routine.

=item EVAL

Evaluate the code provided in the C<Code> parameter, using the
standard Perl C<eval> statement. Any errors are returned to the
caller, and the return value of the expression is ignored. Mostly for
testing purposes, we would never leave something like this on a
production system.

=item SET_PARAMS

Sets all the parameter name/value pairs passes as arguments.

=item GET_PARAMS

Returns the values of the parameters named in its "Params" argument.

=item DUMP_YOURSELF

Returns a textual description of the data structure that represents
the agent. Also for debugging and testing purposes.

=item REPORT_STATUS

Returns fields C<Status> and C<Message> to the caller, containing the
values returned by the last call to C<Check>.

=back 4

=head3 New commands

Defining new commands to which an agent can respond is extremely
easy. To define a new command called B<XYZ>, you have to define a
method called C<command_XYZ> (the commands themselves are case
insensitive, but the method has to be declared with XYZ in
uppercase). This method will be invoked when an XYZ command is
received, with the following arguments:

=over 4

=item *

A reference to the message that requested the command. This is, a
reference to an object of type C<AAFID::Message>.

=item *

A hash containing the parameter name/value pairs provided to the
command. This is, most likely, what most commands will use, ignoring
the message passed in the first argument.

=back 4

If the method returns an C<undef> value, it means (contrary to the
common rule, but it makes sense in our case) that everything went ok,
and the command did not produce any output. If the method wishes to
return anything (either an error message, a return value, or whatever)
to the caller, all it has to do is return a hash reference containing
any parameter name/value pairs it wants to return. These will be
automatically packed in a message of type COMMAND and subtype RESULT
(normally referred to as a "COMMAND RESULT message") and send them to
whoever requested the command.

That is it. Easy, right?


=head1 Agent architecture

=head2 Overview

Under the architecture of the AAFID system, an agent should check for
some specific condition to occur. The standard architecture of an
agent involves having a function that does the checking, and returns
certain values depending on whether an abnormal situation has been
detected.

This checking function, called C<Check>, is invoked at a specific
interval, which can be specified by the agent itself using the
C<CheckPeriod> parameter. Thus, some agents may want to check things
every second, while others may need to do it every minute, every 30
minutes, or even longer intervals.

The C<Check> function should return a list of the form C<($status,
$message)>, where C<$status> has a value from 0 (normal) to 10 (severe
problem), and C<$message> is a descriptive message for the current
situation. The main loop will call the C<Check> function at the
specified interval. Whenever the value of either C<$status> or
C<$message> is different than the previous one (this includes the
first time C<Check> is run, so at least one message is generated at
startup), a STATUS_UPDATE message is sent up, which has the following
format:

  STATUS_UPDATE NOTYPE <agentID> - <time> Status=>$status, Message=>$message

=head2 Agent identification

Each agent class is identified by its class name. For example, a class
that inherits from B<AAFID::Agent> to implement
B<AAFID::CheckWritableRhosts> has "CheckWritableRhosts" as its class
identifier.  This is stored in the C<AgentClassID> parameter.

Each agent must also have a textual description which will be used in
producing human-readable output. This is set by the C<Description>
parameter (which is the same one used by all AAFID entities to keep
their descriptions).

Furthermore, there may be several agents of the same class running
simultaneously in a host, and thus each agent instance has itself a
numeric identifier that serves for distinguishing between different
instances of the same class.

The instance ID of an Agent is its C<EntityID>, as defined by the
B<AAFID::Entity> class. It is copied to the C<AgentID> parameter.

=head2 What the agent writer has to provide

The idea is to make writing an agent as easy as possible. With the
provided framework, the agent writer only has to provide the following
components:

=over 4

=item *

The C<Check> function.

=item *

The check interval (parameter C<CheckPeriod>).

=item *

The agent description (parameter C<Description>).

=item *

If necessary, an agent-specific C<Init> function.

=back 4

=head1 Implementation

=head2 Creating and initializing the agent

At startup time, an agent needs to perform the following tasks:

=over 4

=item 1

Set the C<CheckPeriod> and C<Description> parameters.

=item 2

Set the Signal handlers for handling the signals as required by
B<AAFID::Entity>, if required (standard handlers are provided by
C<AAFID::Entity>).

=item 3

Do any initialization specific to the agent (C<Init> subroutine).

=item 4

Send a C<CONNECT> message to its parent entity (usually a transceiver,
but the message is sent to standard output).

=item 5

Enter the main monitoring loop

=back 4

The agent writer has to provide steps (1) and (2), plus the C<Check>
function used in step (5). Other that that, everything is done
automatically by the framework. If no special signal handling is
needed, step (2) is also done automatically.

The agent-specific initialization is done in the C<Init> function, of
which a standard form is provided by B<AAFID::Entity>, but which has
to be overriden by the agent writer if necessary. This function also
has to set the signal handlers if others than the default ones are
desired.

C<AAFID::Agent> inherits the constructor from C<AAFID::Entity>. It
also extends the internal C<_init> subroutine to check that the agent
writer has provided a C<%PARAMETERS> hash containing the
C<CheckPeriod> and C<Description> parameters.

B<Important:> Agent writers should B<not> override the C<new> or
C<_init> methods. Instead, they should override the C<Init> method,
if necessary.

=cut

# These are provided so that AAFID::Agent can run by itself, even if
# it does nothing.'
# By default, a message is generated if either the status value or the
# message changes, but that can be controlled by the ReportIfMessageChanges
# parameter.
%PARAMETERS=(
	     Description	=> "Template Agent class",
	     ReportIfMessageChanges => 1,
	    );


sub _init {
  my $self=checkref(shift);
  my $class=ref($self);

  # Now check that the necessary parameters were provided
  my %params=$self->getParameters('Description');
  if (!$params{Description}) {
    croak "$class: Description not provided";
  }
  # Set some standard agent parameters
  $self->setParameters(AgentID		=> $self->ID,
		       AgentClassID	=> $class,
		      );

  $self->Log("debug", "Initializing agent ".$self->ID."\n");
  $self->Init;
}

=head2 Running the agent

The C<run> method of B<AAFID::Agent>, which overrides the
corresponding method from B<AAFID::Entity>, does the main loop of the
operation of the agent. It invokes the user-provided C<Check> method,
and sends a STATUS_UPDATE message to the transceiver if the status or
the message returned is different from the previous invocation.

=cut

sub run {
  my $self=checkref(shift);
  $self->Log("debug", $self->ID . ": Running\n");
  # Do runtime initialization
  unless ($self->runtimeInit) {
    $self->Log("errors", "Error in run time initialization\n");
    return;
  }
  # Contact the filters I have requested, if any.
  $self->connectFilters;
  # If the agent provides values for the Status and Message parameters,
  # they are used as the initial values. Otherwise, initialize them
  # to the defaults.
  my ($s,$m)=($self->getParameter('Status', undef), 
	      $self->getParameter('Message', ""));
  $self->setupSTDINcallback;
  # Send the CONNECT message up.
  $self->sendConnectMessage;
  # Set up the callback for running Check only if the CheckPeriod
  # parameter is defined, because each agent may define other means
  # of calling its routines (such as acting on a file event).
  # The callback is stored in an internal parameter to be able to
  # remove it later.
  my $period;
  if ($period=$self->getParameter('CheckPeriod')) {
    my $func=sub {$self->_do_Check; };
    $self->setParameter(_CheckFunction => $func);
    $self->addRepeatingEvent($period, $func);
    # Do the first check immediately. This will also send an initial
    # status update.
    $self->_do_Check;
  }
  else {
    # Send initial status update
    $self->_sendInitialStatusUpdate;
  }
  # Event loop;
  $self->eventLoop;
}

sub _do_Check {
  my $self=shift;
  $self->_do_Generic_Check('Check', @_);
}

sub _do_Generic_Check {
  my $self=checkref(shift);
  my $subname=shift;
  my ($status, $message)=$self->getParametersListRef('Status', 'Message');
  # Get current values for comparison.
  my ($oldstatus, $oldmessage)=($$status||0, $$message||"");
  # Update values.
  $self->Log("debug", $self->ID . ": Calling $subname as a check routine\n");
  ($$status, $$message)=$self->$subname(@_);
  $self->Log("debug", $self->ID . ": Got ($$status, '$$message'); ".
	     "old data was ($oldstatus, '$oldmessage')\n");
  if(($$status ne $oldstatus) or 
     ((uc($$message) ne uc($oldmessage)) && 
      $self->getParameter('ReportIfMessageChanges'))) {
    my $msg=$self->newMsg(
	  TYPE  => "STATUS_UPDATE",
	  DATA  => "Status => $$status, Message => '$$message'");
    $self->Log("debug", $self->ID . 
	       ": Sending status update: ".$msg->toString."\n");
    $self->sendReport($msg);
  }
}

=pod 

For agents, callbacks for filters are set in a different manner, because
they have to be called through C<_do_Generic_Check>. So we override
C<_setFilterCallback>.

=cut

sub _setFilterCallback {
  my ($self, $sock, $fname, $cbname)=@_;
  my $cb=eval "sub { \$self->_do_Generic_Check('$cbname', '$fname', \@_); }";

  if ($@) {
    $self->Log("errors", 
	       "Error defining callback for filter $fname: $@\n");
    return undef;
  }
  $self->Log("debug", "Setting callback $cbname for filter $fname.\n");
  Comm::Reactor::add_handle($sock, $cb);
  return 1;
}

=pod

The C<_sendInitialStatusUpdate> causes a STATUS_UPDATE message to be
sent immediately when the agent starts running. If the agent sets
initial values for the B<Status> and B<Message> parameters, those
are the values sent. Otherwise, the default values (zero and empty
string) are used.

=cut

sub _sendInitialStatusUpdate {
  my $self=checkref(shift);
  my $status=$self->getParameter('Status')||0;
  my $message=$self->getParameter('Message');
  $message="" unless defined($message);
  my $msg=$self->newMsg(
			TYPE  => "STATUS_UPDATE",
			DATA  => "Status => $status, Message => '$message'");
  $self->Log("debug", $self->ID . 
	     ": Sending initial status update: ".$msg->toString."\n");
  $self->sendReport($msg);
}

=pod

B<AAFID::Agent> provides an empty C<Check> subroutine that simply
returns ever changing messages, to check the communication. This
should always be overriden by agent writers.

=cut

sub Check {
  my $self=checkref(shift);
  my $status=$self->getParameter('Status');
  $status=0 if ++$status>10;
  #
  # Do your checking here.
  #
  return ($status, "My dummy status is $status");
}

=head2 Additional standard agent commands

The C<RESET_STATUS> command is overriden to call its original definition,
but also to automatically generate a B<STATUS_UPDATE> message after
the reset, if the status and/or message changed.

=cut

sub command_RESET_STATUS {
  my $self=checkref(shift);
  my ($oldstatus, $oldmsg)=$self->getParametersList('Status', 'Message');
  $self->SUPER::command_RESET_STATUS();
  my ($status, $msg)=$self->getParametersList('Status', 'Message');
  if(($status ne $oldstatus) or 
     ((uc($msg) ne uc($oldmsg)) && 
      $self->getParameter('ReportIfMessageChanges'))) {
    my $updmsg=$self->newMsg(
	  TYPE  => "STATUS_UPDATE",
	  DATA  => "Status => $status, Message => '$msg'");
    $self->Log("debug", $self->ID . 
	       ": Sending status update: ".$updmsg->toString."\n");
    $self->sendReport($updmsg);
  }
}

=pod

The C<SET_PERIOD> command assigns a new value to the C<CheckPeriod>
parameter. This involves changing the parameter value, and also
rescheduling the periodic execution of the C<Check> subroutine using
the new value. If the C<CheckPeriod> parameter did not have a value
assigned before, then calling this command make the agent start to
automatically execute the C<Check> subroutine periodically. The
C<Check> subroutine is executed immediately after assigning the new
value, and every C<CheckPeriod> seconds thereafter.

This command takes one argument called B<Period>, which contains the
new value.

=cut

sub command_SET_PERIOD {
  my $self=checkref(shift);
  my ($msg, %params)=@_;
  my $period;
  my $func;
  if ($params{Period}) {
    if ($period=$self->getParameter('CheckPeriod')) {
      # If it had a value assigned, remove the periodic execution.
      $func=$self->getParameter('_CheckFunction');
      $self->removeRepeatingEvent($period, $func);
    }
    # Now add the new one.
    $func=sub { $self->_do_Check; };
    $self->setParameter(CheckPeriod => $params{Period});
    $self->setParameter(_CheckFunction => $func);
    $self->addRepeatingEvent($params{Period}, $func);
    # Do the first one immediately.
    $self->_do_Check;
  }
  return undef;
}

=head1 Standard agent parameters

The currently defined parameters for agents are the following.

=over 4

=item Description

(string) A textual description of the agent, for display purposes. This
same parameter is used by all entities to keep their descriptions. It
should be set by the agent writer using the global C<%PARAMETERS> hash.

=item CheckPeriod

(numeric) The interval, in seconds, at which the C<Check> function of
the agent must be invoked. It may be fractionary (e.g., 0.1). It must
also be defined in C<%PARAMETERS> by the agent writer.

=item AgentID

(string) The Id of the agent. It is the same as the EntityID of the
Agent, and thus is computed automatically.

=item AgentClassID

(string) The class name of the agent.

=item _CheckFunction

(function reference) Internal parameter used to store the function that
is called periodically when B<CheckPeriod> is set. It is used to be able
to change the period through the SET_PERIOD command.

=back 4

=cut

_EndOfEntity;

# $Log: Agent.pm,v $
# Revision 1.28  1999/09/03 17:08:52  zamboni
# Changed the start line to something that is path-independent, and
# updated the copyright notice.
#
# Revision 1.27  1999/08/31 22:40:06  zamboni
# Fixed a bug in _do_Generic_Check ($oldstatus and $oldmessage were not
# being initialized correctly)
#
# Revision 1.26  1999/08/31 07:32:56  zamboni
# - Added the SET_PERIOD command.
# - Added documentation for the _CheckFunction parameter.
#
# Revision 1.25  1999/08/08 00:20:36  zamboni
# - Removed the REPORT_STATUS command, moved it to Entity.pm
# - Added the RESET_STATUS command.
#
# Revision 1.24  1999/06/28 21:22:02  zamboni
# Merged with a07-port-to-linux
#
# Revision 1.23.2.1  1999/06/28 18:21:26  zamboni
# - Added support for the "Status" and "Message" parameters, now these
#   parameters, if they exist, provide the initial values for the agent's
#   status and message.
# - Made it send an initial status update even if 'CheckPeriod' is not
#   defined.
# - Added the _sendInitialStatusUpdate function (see previous item)
#
# Revision 1.23  1999/06/11 21:50:05  zamboni
# - Moved the definition of runtimeInit() to Entity.pm
# - Modified run() to call $self->eventLoop instead of Comm::Reactor::loop
#
# Revision 1.22  1999/06/08 05:01:51  zamboni
# Merged branch a06-raw-data-collection into main trunk
#
# Revision 1.21.2.1  1999/06/06 01:12:52  zamboni
# - Added runtimeInit to perform runtime initialization activities.
# - Moved sendConnectMessage to after all the initialization.
#
# Revision 1.21  1999/03/31 07:21:23  zamboni
# - Added a new parameter ReportIfMessageChanges that controls whether a
#   STATUS_UPDATE is produced if the message changes but the status value
#   remains the same. It is by default on, but it may be disabled by the
#   agent if needed.
# - Made the CheckPeriod parameter optional. If it exists, then a periodic
#   event (every 'CheckPeriod' seconds) is set that calls Check. But the
#   agent writer may decide to monitor things in some other way. Removed
#   the CheckPeriod parameter from Agent.pm so that it is not inherited
#   by default to subclasses.
# - Made _do_Check pass to Check any arguments it receives.
# - Created a new subroutine _do_Generic_Check that receives as argument
#   the name of a subroutine and calls that subroutine as a "check"
#   subroutine, this is, it interprets its output as (status, message)
#   and sends a STATUS_UPDATE message if the status has changed.
#   Any additional arguments it receives are passed to the subroutine.
#   _do_Generic_Check has most of the code that was previously in _do_Check,
#   but instead of always calling Check it calls the provided subroutine.
# - Converted _do_Check into a call to _do_Generic_Check.
# - Added a custom _setFilterCallback that calls the callback given
#   by the user through _do_Generic_Check (overrides the one from Entity.pm).
#
# Revision 1.20  1999/03/29 22:33:21  zamboni
# Merged branch a05-new-comm-module, which updates it to make use of the new event-based communication mechanism.
#
# Revision 1.19.4.2  1999/03/29 15:57:04  zamboni
# - Changed the initialization message.
# - Changed call to setUpCommunicationCallbacks to setupSTDINcallback.
#
# Revision 1.19.4.1  1999/03/19 17:08:27  zamboni
# - Moved Log tag to end of file.
# - Modified run() to work with the new event-based mechanisms.
#
# Revision 1.19  1998/09/07 17:35:17  zamboni
# Added support for filters!
#
# * Monitor.pm: Replaced all uses of AAFID::Message->new by uses
#   of $self->newMsg.
#
# * Filter.pm: Cleaned up the code and the comments, and added some
#   extra features, such as default versions of makefield and
#   makeline that make it easier to define new filters if they read
#   data where the fields are space-separated. Also, added some
#   error checks here and there.
#
# * Entity.pm: Added filter support
#   - Added descriptions for FiltersNeeded and FilterPaths parameters.
#   - Modified getParameter so that if a second argument is given, it is
#     used to initialize the parameter in case it is undefined.
#   - Added subroutines connectFilters, newFilterClientSocket,
#     setFilterPattern
#   - Added subroutine newMsg.
#
# * ControllerEntity.pm: Added filter support.
#   - Some general cleanup (removing stray commented code, etc.)
#   - Rewrote some sections for clarity (in invoke, _invoke,
#     _instantiate_and_run)
#   - Modularized loadmodule. Created new subroutines:
#       _loadmodule_error
#       _loadmodule_success
#   - Modularized _instantiate_and_run, creating new subroutine
#       _fork_and_run.
#   - Added the setupFilters subroutine, which takes care of loading
#     all the filters required by an entity. It is complemented by
#     an augmented message_CONNECT, which detects when a filter is
#     connecting and does the necessary updating.
#
# * Comm.pm: Removed an annoying debug message from bufferInput.
#
# * Agent.pm: Added code to contact the filters.
#
# Revision 1.18  1998/06/29 20:11:23  zamboni
# Added copyright message
#
# Revision 1.17  1998/06/26 21:07:16  zamboni
# - Eliminated the processing of the %PARAMETERS hash that was done in the
#   _init method. Now that is done automatically by the new recursive
#   mechanism in the constructor of AAFID::Entity.
#
# Revision 1.16  1998/06/25 21:03:37  zamboni
# - Added "use AAFID::Config" so that it finds the methods.
#
# Revision 1.15  1998/04/27 15:07:47  zamboni
# - Added a section for "Testing and debugging".
# - Added a tutorial section, where we go over the creation of a new
#   and improved version of CheckRhosts.
# - Made run() also generate a STATUS_UPDATE if the message has changed
#   from the previous invocation of Check(). Case is ignored when
#   comparing.
#
# Revision 1.13  1998/03/17 06:39:21  zamboni
# - (this actually happened in the previous checking, but forgot to
#   document it) Added a whole new section of documentation at the
#   beginning, teaching how to write agents.
#
# Revision 1.12  1998/03/17 06:38:18  zamboni
# - Added some documentation, restructured some other, and cleaned up a
#   little of code.
# - Replaced all manual checks of $self with calls to
#   AAFID::Common::checkref().
#
# Revision 1.11  1998/03/14 05:53:35  zamboni
# - Made AAFID::Agent export _EndOfEntity by default, so that agents only have to
#   import AAFID::Agent in order to have all the functionality available. After
#   playing with it, I don't know if it's really necessary, but I don't think
#   it hurts.
#
# Revision 1.10  1998/03/13 16:56:12  zamboni
# - Changed the AgentDescription parameter to Description, to be consistent
#   with all other entities. Also, removed the insertion of %PARAMETERS and
#   $VERSION into the object from the _init method, because that is done
#   automatically in the AAFID::Entity->new method.
# - Readded the insertion of %PARAMETERS and $VERSION into _init. The reason
#   for this is that otherwise, if the Agent writer does not provide
#   %PARAMETERS, the agents would inherits the default ones from AAFID::Entity
#   and not from AAFID::Agent, which is what we want. Oh well, the beauties
#   of Perl's inheritance mechanism.
#
# Revision 1.9  1998/03/13 01:46:08  zamboni
# Made a change to make $VERSION keep up with the RCS revision number.
#
# Revision 1.8  1998/03/06 07:11:28  zamboni
# - Added "use strict"
#
# Revision 1.7  1998/03/04 06:29:24  zamboni
# Did general debugging and cleaning up. Seems to work correctly when started
# from a ControllerEntity, and the tests performed with Agents/CheckRhosts
# seem satisfactory.
#
# One problem that kept me busy for some time was that the first status
# update message sent after the connect was not seen by the
# ControllerEntity (i.e. the connect message was seen, but the message
# immediately after it was not) until a later message came by. This was
# solved for now by inserting a 1-second delay after sending the connect
# message and before entering the check loop. However, I don't think this
# is satisfactory. For some reason the Select object does not seem to
# catch up on the second message because it is being sent immediately after
# the first one. But there must be a way of doing this. I have to further
# investigate.
#
# Revision 1.6  1998/02/26 05:30:59  zamboni
# Disabled debugging output by default.
#
# Revision 1.5  1998/02/26 04:02:32  zamboni
# Removed the call to $self->SUPER::_init from _init, because it resulted in
# a duplicated call to $self->Init.
#
# Revision 1.4  1998/02/25 19:29:10  zamboni
# Added a call to $self->sendConnectMessage to the run() method, due to
# a similar change in AAFID::Entity.
#
# Revision 1.3  1998/02/25 07:25:29  zamboni
# Almost all-new version of Agent.pm.
# - Uses the new architecture of AAFID::Entity and builds on it.
# - Removed a lot of functionality that was previously here but that now
#   belongs to AAFID::Entity (such as parameter handling) or simply
#   disappeared (such as explicitly communicating with a transceiver).
# - _init function that overrides AAFID::Entity::_init (although it calls it
#   at the end), to check that the user provided valid CheckPeriod and
#   AgentDescription parameters, as well as to set up logging.
# - Modified run method to automatically update the agent parameters Status
#   and Message when the Check function returns.
# - Modified run method to make use of the new timeout facility of
#   AAFID::Entity::processInput to be able to respond immediately to
#   messages. A side effect of this is that if a message arrives before
#   the timeout occurs, then it will be processes and the next check will
#   be done immediately, thus resulting in that not all checks will be
#   separated by CheckPeriod seconds. Have to look more into this.
# - Dummy Check subroutine that simply spews out bogus messages.
# - New command REPORT_STATUS that sends the current Status and Message
#   parameters "up".
# - Added section on standard Agent parameters.
#
# Revision 1.2  1998/02/17 05:09:46  zamboni
# Latest "old" version.
#
# Revision 1.1  1998/01/28 21:57:48  zamboni
# Initial revision
