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::Filter package
#
# AAFID project, COAST Laboratory, CERIAS, 1998-1999.
#
# Susana Soriano, August 11, 1998.
# Diego Zamboni, 1999.
#
# $Id: Filter.pm,v 1.13 1999/09/06 15:23:20 zamboni Exp $
#
# NOTE: This file is in Perl's POD format. For more information, see the 
#       manual page for perlpod(1).
#

package AAFID::Filter;

use AAFID::Entity;
use AAFID::Common;
use Data::Dumper;
use Socket;
use Carp;
use strict;
use IO::File;
use Comm::Reactor;
use vars qw(
	    $waitedpid
	    $self
	    @ISA
	    $Server
	    $Client
	    $VERSION
	    %PARAMETERS
	   );

@ISA=qw(AAFID::Entity);

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

#Default parameters.
%PARAMETERS=(
	     Description		=> "Base Filter class",
	    );

=pod

This is the basic structure of a filter. In this program, there are
defined the basic subroutines. The specifications depending on the
logfile must be defined in an independent package which has to be a
subclass of this filter. The structure of a filter contains the
subroutines that open a logfile if it is required, read the pattern(s)
that the agent gives to the filter, establish a UNIX-domain socket
which connects the agent with the filter and send the information
wanted to the agent.

=head1 Joining an Agent, a Filter and a Logfile.

Sometimes the information that is required for the Agent is stored in
a logfile. In fact, this is the general case and it is implemented as
a default case. The subroutine C<Init_log> takes care of opening the log
file and storing it in the C<MyFileHandle> parameter. The file opened is
given by the C<LogFile_Name> parameter, which must be defined by the
filter if it does not provide its own C<Init_log> method.

In the case that the information is not stored in a logfile (or at
least not in a single log file), the filter must redefine the
C<Init_log> subroutine accordingly.

=cut

sub Init_log {
  my $self=checkref(shift);
  my $FILEF=$self->getParameter('LogFile_Name');
  # Filter opens the log file if it is defined.
  if (defined ($FILEF)) {
      $self->Log("debug", "Installing handler for file $FILEF.\n");
      Comm::Reactor::add_file($FILEF, sub { $self->processLine(@_); });
  }
  else {
    $self->Log("debug", "Not opening any file.\n");
  }
  return $self;
}

=pod

The connection between an Agent and a Filter is established by a
UNIX-domain socket which is a local socket to the current host.

=cut

sub newServerSocket {
  my $self=checkref(shift);
  my $class=ref($self);
  $class =~ tr/A-Z/a-z/;
  my $NAME=shift || $self->getParameter('TmpDir')."/aafid$$.".$class;
  $self->setParameter(SocketPath => $NAME);
  (-e $NAME) and do { unlink($NAME) or do { $!="Could not remove $NAME: $!";
					  return undef;
					}
		  }; 
  my $sock=IO::Socket::UNIX->new(Listen   => 10,
                                 Type           => SOCK_STREAM,
                                 Local          => $NAME,
				);
  # This is a hack so that AAFID::Comm::isServerSocket can detect
  # the type of socket automatically.
  $sock and $ {*$sock}{_AAFID_Comm_ServerSocket}=1;
  return $sock;
}

=pod

The C<stopServerSocket> subroutine removes the event handler for the server
socket, and closes it. This may be used in a filter that spawns subprocesses
by the subprocesses, since they do not need to respond to connections on
the server socket.

=cut

sub stopServerSocket {
  my $self=checkref(shift);
  my $sock=$self->getParameter('ServerSocket');
  Comm::Reactor::remove_handle($sock);
  $self->getParameter('OpenHandles')->remove($sock);
  $self->deleteParameter('ServerSocket');
  $sock->close;
}

=pod

The base Filter class does not contain an C<Init> method, so that each
filter can specify its own if needed.

=head1 Getting the patterns and Selecting the information.

The agent sends patterns to the filter in order to select specific information.
The filter split the lines into fields to be more careful about what 
information is choosen. In this way, the filter looks for the line on the
log which matches with the pattern and reconstructs it to the original
format. Once this process is done, the filter send the information in the
 original format to the agent.

The agent uses the SETPATTERN command to tell the filter the pattern that
it is looking for. The arguments to this command must be the field names
of the data that the filter handles, with each one containing a regular
expression that is to be matched against that particular field. Only
log records that match on all the given fields are returned to the
agent.

For example, the following command:

   SETPATTERN Daemon => 'telnet', From => '.*\.cs\.purdue\.edu$ '

will cause the filter to return only records whose Daemon field contains
"telnet" B<and> whose From field ends with ".cs.purdue.edu".

=cut

sub command_SETPATTERN {
  my $self= shift;
  my $msg= shift;
  my %param=@_;
  my $result=$self->getParameter('OpenHandleIDs');
  $result->{$msg->{FROM}}->{Pattern}=\%param;
  $self->Log("debug", "Pattern for ".$msg->{FROM}." set to ".Dumper(\%param). "\n");
  return undef;
}

=pod

In order to give the filter some semantic knowledge about the data it
is processing, all the records are internally split in fields, and
processed as such. This allows the specification of per-field patterns
for matching, as is done by the SETPATTERN command. However, how to
split data into fields is completely dependent on the specific data
that is being processed, and thus is a functionality that needs to be
specified by the filter author. The C<makefield> subroutine takes as
argument a string, and returns a hash reference where each key is the
field name, and each element contains the corresponding field.

The filter author can provide her own C<makefield> subroutine that is
customized to the data that is being processed. However, for the most
low-denominator case, a default behavior is provided. The base
C<makefield> subroutine works as follows:

=over 4

=item *

If the C<DataFields> parameter is defined, it must contain an
array reference where each element corresponds to a field name, in the
order they appear in the string. The string is split into as many
fields as elements that array contains, considering the fields to be
separated by blank space, and the last field to "swallow" the rest of
the string.

=item *

If the C<DataFields> parameter is not defined, the string is
split into blank-space-separated fields, named "Field0", "Field1",
etc.

=back 4

=cut

sub makefield {
  my $self=checkref(shift);
  my $line=shift;
  my $fields=$self->getParameter('DataFields');
  if ($fields) {
    my @fields=@$fields;
    # Use the fields from the DataFields parameter.
    my $nfields=@fields;
    my @data=split(" ", $line, $nfields);
    my $data;
    my %res=map { $data=shift @data;
		  $data=defined($data)?$data:"-";
		  $_ => $data ;
		} @fields;
    return \%res;
  }
  else {
    # Use generic named fields.
    my $nfield=0;
    my $field;
    my %res=map { $field="Field" . $nfield++; 
		  $field => $_ 
		} split(" ", $line);
    return \%res;
  }
}

=pod

In order to send the data back to the agent, it needs to be
reconstructed from the field representation to a string
representation. This is what the C<makeline> subroutine does. It takes
a hash reference as an argument that contains the data fields, and
returns a string.

In its default form, C<makefield> concatenates (separated by spaces)
the fields in the order given by the C<DataFields> parameter, or
in numeric order if that parameter is not defined. Of course, the
filter author can override this subroutine to provide adequate
behavior if the default behavior is not correct.

If a second argument is given to the default form of C<makeline>, it is
used as a field separator instead of a space.

=cut

sub makeline {
  my $self=checkref(shift);
  my $data=shift;
  my $sep=shift || " ";
  my $fields=$self->getParameter('DataFields');
  my @fields;
  if ($fields) {
    # Join in order dictated by DataFields
    @fields=@$fields;
  }
  else {
    # Join in numeric order.
    @fields=sort keys %$data;
  }
  return join $sep, map { defined($data->{$_})?$data->{$_}:"-" } @fields;
}

=pod

The C<processLine> subroutine gets a line from a log file, and processes
it appropriately. It is called as a callback when data is received from
a file handle, therefore its arguments are the file handle itself and the
line that was read, including the end-of-line at the end. In a sense, this
routine is the heart of the filter, because it is the one that checks which
lines have to be sent to which agents, and sends them.

=cut 

sub processLine {
  my $self=checkref(shift);
  my ($fh, $line)=@_;
  my $info_log;
  my $info_usr;
  my $field;
  my $flag;
  my $i;
  my $socket;
  chomp $line;
  $self->Log("debug", "Got line: $line\n");
  $info_log=$self->makefield($line);
  # Skip if makefield tells us it's invalid.
  return unless $info_log;
  my $ohids=$self->getParameter('OpenHandleIDs');
  if (!$ohids) {
    croak "Panic: I don't have my OpenHandleIDs parameter!";
  }
  foreach $i (keys(%$ohids)) {
    $self->Log("debug", "Checking for $i\n");
    $socket=$ohids->{$i}->{Handle};
    $flag=0;
    if (defined($ohids->{$i}->{Pattern}) &&
	%{$ohids->{$i}->{Pattern}}) {
      $info_usr=$ohids->{$i}->{Pattern};
      foreach $field (keys(%$info_usr)) {
	if ($field =~ "All") {
	  # The presence of an "All" field makes it match, no matter what
	  # the other fields of the pattern are.
	  $flag=0;
	  last;
	}
	else {
	  if (defined($info_log->{$field})) {
	    if (_matches($info_log->{$field}, $info_usr->{$field})) {
	      $flag+=0;
	    }
	    else {
	      $flag+=1;
	    }
	  }
	  else {
	    $flag=1;
	  }
	}
      }
      if (!$flag) {
	# A match.
	$self->Log("debug", "Got a match, sending line.\n");
	my $linemsg=$self->makeline($info_log);
	if ($linemsg) {
	  AAFID::Comm::sendStringTo($linemsg, $socket, 
				    $self->getParameter('_standalone'));
	}
	else {
	  $self->Log("errors", 
	  "Got a match, but now got an error when reconstructing the line.\n");
	}
      }
      else {
	$self->Log("debug", "No match.\n");
      }
    }
    else {
      # We fall here is no pattern has been given, or an empty
      # pattern has been given. So an empty pattern means
      # "no matches", which is what we want.
      $self->Log("debug", "Empty pattern, no match\n");
    }
  }
}

=pod

The C<_matches> subroutine interprets the pattern accordingly (it has
to be negated if the pattern starts with a '!') and returns whether the
string matches or not.

=cut 

sub _matches {
  my ($str, $pat)=@_;
  my $rev=0;
  if ($pat =~ /^!(.*)/) {
    $pat=$1;
    $rev=1;
  }
  if (!$rev) {
    return ($str =~ $pat);
  }
  else {
    return ($str !~ $pat);
  }
}

=pod 

The C<run> subroutine is run after all the setup occurs, so it only does
some final setup and calls the event loop.

=cut

sub run {
  my $self=checkref(shift);
  my $NAME;

  # Do some setup
  $self->setupDefaultSignalHandlers;
  # Set up the standard STDIN callback.
  $self->setupSTDINcallback;

  # Initialize the source of logging info.
  unless ($self->Init_log) {
    my $errormsg=newErrorMsg({Error => 
			  "Error in Init_log. Aborting filter."});
    $self->sendReport($errormsg);
    $self->Log('errors', "Error in Init_log. Aborting filter.\n");
    return undef;
  }

  # Set up the UNIX-domain socket on which we provide the service.
  $Server=$self->newServerSocket;
  $NAME=$self->getParameter('SocketPath');
  if ($Server) {
    $self->Log("processes", "Filter started on $NAME\n");
    # Add server socket to the select object.
    $self->getParameter('OpenHandles')->add($Server);
    # Store it
    $self->setParameter(ServerSocket => $Server);
  }
  else {
    $self->Log("errors", "Error starting filter server socket: $!\n");
    return undef;
  }

  # Create an acceptor for the server socket.
  Comm::Reactor::add_acceptor($self->getParameter('ServerSocket'),
			      sub { $self->_getNewConnection(@_); });

  # Send a CONNECT message
  $self->sendConnectMessage;
  # Enter event loop
  $self->eventLoop();
}

=pod

The C<_getNewConnection> subroutine is called whenever a connection comes
on the server socket. The subroutine is called with the server socket
as its argument, and can be overriden by a subclass to implement some
specific functionality when a new connection is received. 

C<_getNewConnection> calls C<_handleNewConnection>, which is supposed
to do something with the socket for the new connection. By default,
it sets up a handler for it, which calls C<_processMessageFromAgent>.

C<_processMessageFromAgent> is the subroutine that is called whenever a
message arrives from an agent that has already established a connection
to the filter. This may also be overriden, although most of the time
it should not be necessary.

C<_getNewConnection> returns both the server socket and the new socket.
This is to make it easier to extend its functionality by having the 
subclass call the original function with C<$self->SUPER::_getNewConnection>,
and then be able to operate on the sockets.

=cut

sub _getNewConnection {
  my $self=checkref(shift);
  my $fh=shift;
  my $new=$fh->accept;
  if (!$new) {
    my $errormsg=newErrorMsg({Error => 
			  "Error accepting connection on server socket: $!"});
    $self->sendReport($errormsg);
    return undef;
  }
  if ($fh->sockdomain == AF_UNIX) {
    # This is a hack to make it run on Linux. NEED TO CORRECT - TODO
    # For some reason hostpath gives an error on Linux.
    #	  $self->Log("debug", "Got new connection on server socket, at ".
    #		     $new->hostpath . "\n");
  }
  else {
    $self->Log("errors", "Got connection on an unknown socket domain: ". 
	       $fh->sockdomain . "\n");
  }
  $new->autoflush(1);
  # Do something with the new connection.
  $self->_handleNewConnection($fh, $new);
  return ($fh, $new);
}

sub _handleNewConnection {
  my $self=checkref(shift);
  my $server=shift;
  my $new=shift;
  Comm::Reactor::add_handle($new, sub { $self->_processMessageFromAgent(@_)});
}

sub _processMessageFromAgent {
  my $self=checkref(shift);
  my ($fh, $msg)=@_;
  $self->processInput($fh, AAFID::Comm::nextMsg($fh,$msg));
}

=pod

We use a modified C<sendConnectMessage> subroutine, which sends the
message with the subtype FILTER instead of CHILD, and the socket path
as the second element in the DATA field.

=cut

sub sendConnectMessage {
  my $self=checkref(shift);
  my $to=shift;
  my $data=$self->getParameter("ClassID") . " " .
    $self->getParameter("SocketPath") . " " .
      $self->getParameter("Description");
  my $msg=$self->newMsg(TYPE	=> "CONNECT",
			SUBTYPE	=> ($to?"PARENT":"FILTER"),
			DATA	=> $data);
  if ($to) {
    $self->Log("I/O", "Sending CONNECT message to $to: ".$msg->toString."\n");
    $self->sendMsgTo($msg, $to)
  }
  else {
    $self->Log("I/O", "Sending CONNECT message up: ".$msg->toString."\n");
    $self->sendReport($msg);
  }
  return $self;
}

=head1 More facilities for writing filters

The C<addCommandDataSource> method allows obtaining data from the
execution of a command. The first parameter is the command to be
executed (including options), the second parameter specifies the delay
between successive executions of the command (if it is 0 it is
repeated immediately, if it is C<undef> or negative it is executed
only once), and the third parameter is a subroutine reference that
will be executed for each line read from the command. The subroutine
will be passed the file handle from which the data was read and the
line itself (without the end of line marker). If the third parameter
to C<addCommandDataSource> is C<undef>, the standard subroutine
C<processLine> is called immediately (this subroutine should be called
from the provided code anyway, but some preprocessing may be wanted).

The subroutine returns C<$self> if successful, or C<undef> if an
error occurs.

The command is executed immediately, and then may be executed again
depending on the value of the second argument given.

=cut

sub addCommandDataSource {
  my $self=checkref(shift);
  my ($command, $delay, $cb)=@_;
  return undef unless $command;
  $cb=sub { $self->processLine(@_); } unless $cb;
  # Open a pipe from the command
  my $fh=IO::File->new("$command |")
    or return undef;
  $self->addHandleEvent($fh,
	sub {
	  my $_fh=shift;
	  my $msg=<$_fh>;
	  if (!defined($msg)) {
	    $self->removeHandleEvent($_fh);
	    $_fh->close;
	    # Check if we have to reawake and schedule it.
	    if (defined($delay) && $delay>=0) {
	      $self->addTimeEvent(time()+$delay,
		sub { $self->addCommandDataSource($command, $delay, $cb) });
	    }
	  }
	  else {
	    # Process the line
	    chomp $msg;
	    &{$cb}($_fh, $msg);
	  }
	});
  return $self;
}

=pod

The C<addHandleDataSource> method allows obtaining data from a file handle
(such as an already opened pipe, or a socket). The first parameter is the
file handle. The second parameter is a subroutine reference that will be
executed whenever there is data available for reading from the file handle.
This subroutine will be passed the file handle as an argument. It is
responsibility of the subroutine to perform the read from the handle and
to eventually call C<processLine>. If the second argument is not provided,
a line will be automatically read from the handle and passed to
C<processLine>.

The subroutine returns C<$self> if successful, or C<undef> if an error
occurs.

=cut

sub addHandleDataSource {
  my $self=checkref(shift);
  my ($handle, $cb)=@_;
  return undef unless $handle;
  if (!$cb) {
    $cb=sub {
          my $fh=shift;
	  my $msg=<$fh>;
	  if (defined($msg)) {
	    chomp $msg;
	    $self->processLine($fh, $msg);
	  }
	  else {
	    # If the handle closes, remove it as a source
	    $self->removeHandleEvent($fh);
	    $fh->close;
	    # TODO: Have a callback for when the handle is closed.
	  }
        }
  }
  $self->addHandleEvent($handle, $cb);
  return $self;
}

=pod

The C<addTextFileDataSource> allows to specify obtention of data from a
regular file. The first argument is the file name to monitor, and the
second argument is the subroutine to call when data is read from the
file. The subroutine will be called with the file handle and the line
read as arguments. If the second argument is not given or is C<undef>,
the lines read will be passed directly to the C<processLine> method.

There may be additional arguments, as allowed by the C<addTextFileMonitor>
method in B<AAFID::Entity>.

=cut

sub addTextFileDataSource {
  my $self=checkref(shift);
  my ($filename, $cb, @other_cbs)=@_;
  @other_cbs=() unless defined(@other_cbs);
  return undef unless $filename;
  if (!$cb) {
    $cb=sub {
      $self->processLine(@_);
    }
  }
  my $res=$self->addTextFileMonitor($filename, $cb, @other_cbs);
  if ( defined($res) && ($res == -1) ) {
    return undef;
  }
  else {
    return $self;
  }
}

_EndOfEntity;

#
# $Log: Filter.pm,v $
# Revision 1.13  1999/09/06 15:23:20  zamboni
# Fixed a bug in stopServerSocket. Was calling a nonexistent function.
#
# Revision 1.12  1999/09/03 17:08:53  zamboni
# Changed the start line to something that is path-independent, and
# updated the copyright notice.
#
# Revision 1.11  1999/09/02 23:25:44  zamboni
# - Made run() abort if Init_log returns undef, so that Init_log can flag
#   errors and abort the execution of the filter.
# - Added a default "return $self" to the default Init_log to comply with
#   the previous point.
# - Removed a large commented-out chunk.
# - Added stopServerSocket(), which closes the filter's server socket and
#   removes it from the tables.
# - Splitted the code that sets up the callback for the server socket into
#   three subroutines:
#   - _getNewConnection is the default handler for accepting a new
#     connection on the server socket. This is set by the run() method,
#     and it may be overriden by subclasses. By default, _getNewConnection
#     accepts the connection and calls _handleNewConnection.
#   - _handleNewConnection does something with the socket for the new
#     connection after it has been accepted. By default it sets up a
#     callback for it that calls _processMessageFromAgent.
#   - _processMessageFromAgent receives a message from an agent and does
#     something with it. By default it calls the standard processInput().
#   Any of these subroutines may be overriden by a subclass.
#
# Revision 1.10  1999/09/01 22:13:46  zamboni
# - Fixed a bug in addCommandDataSource (a default callback was not being
#   supplied).
# - Added addHandleDataSource and addTextFileDataSource
#
# Revision 1.9  1999/06/28 21:22:05  zamboni
# Merged with a07-port-to-linux
#
# Revision 1.8  1999/06/28 20:44:00  zamboni
# Changed Comm::Reactor::loop() to $self->eventLoop() in run().
#
# Revision 1.7.2.1  1999/06/28 18:35:48  zamboni
# - Removed a call to IO::Socket::UNIX::hostpath() (from a Log
#   statement) because it doesn't work under Linux for some weird
#   reason.
# - Changed Comm::Reactor::loop to $self->eventLoop() in run().
#
# Revision 1.7  1999/06/11 21:50:48  zamboni
# - Removed some old commented-out code
# - Commented out the openfile method, which I think is obsolete.
# - Made the path of the socket be constructed using the new TmpDir standard
#   parameter (see AAFID::Config and AAFID::System), and have the process ID
#   in it.
# - Added addCommandDataSource to set a command to be executed.
#
# Revision 1.6  1999/06/08 05:01:55  zamboni
# Merged branch a06-raw-data-collection into main trunk
#
# Revision 1.5.2.1  1999/06/07 16:44:48  zamboni
# - Fixed some bugs with empty fields (made empty fields become "-")
# - Added a _matches() function that performs the field matching. Added
#   the capability that if the pattern starts with "!", the match is
#   negated.
#
# Revision 1.5  1999/04/01 02:39:11  zamboni
# - Added some error checking in processLine, and replaced a "next" that
#   had to be a "return".
#
# Revision 1.4  1999/03/29 22:33:26  zamboni
# Merged branch a05-new-comm-module, which updates it to make use of the new event-based communication mechanism.
#
# Revision 1.3.4.2  1999/03/29 22:04:49  zamboni
# - Removed the Init subroutine (so that each specific filter subclass
#   can specify it if it needs to).
# - Removed spurious global variables $NAME and $FILEF.
#
# Revision 1.3.4.1  1999/03/29 16:34:11  zamboni
# - Added standard AAFID comment header block.
# - Modified to make use of the new event mechanism. Now, instead of polling
#   the log file periodically, the filter adds an event handler on the file.
#   Whenever a line appears, it is processed and sent to the appropriate
#   agents.
# - Added the feature that if the LogFile_Name parameter starts with a '+',
#   the rest of the string is considered the file name, and upon opening
#   it, the pointer is automatically moved to the end. If the '+' is not
#   given, the file is read from the beginning.
# - Removed the whole getline function, no longer needed with the event
#   mechanism.
# - Moved the call to Init_log and the setup of the server socket from
#   Init (which is called at creation time) to run, so that they are
#   executed only when the filter is actually invoked. This prevents
#   the creation of spurious file handles in the parent process when a
#   filter is invoked.
# - Added processLine(), which does the actual processing of each line
#   received. This subroutine is the default callback for the log file.
# - Fixed behavior of the All flag in the pattern for an agent.
#
# Revision 1.3  1998/09/09 09:05:37  zamboni
# * Filter.pm: General debugging.
#
# Revision 1.2  1998/09/07 17:35:20  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.1  1998/09/02 15:58:29  zamboni
# Base class for filters.
#
