
jm at apache
Jan 22, 2004, 11:01 PM
Post #1 of 1
(102 views)
Permalink
|
|
svn commit: rev 6250 - in incubator/spamassassin/trunk: . lib/Mail lib/Mail/SpamAssassin
|
|
Author: jm Date: Thu Jan 22 22:01:21 2004 New Revision: 6250 Added: incubator/spamassassin/trunk/lib/Mail/SpamAssassin/Plugin.pm incubator/spamassassin/trunk/lib/Mail/SpamAssassin/PluginHandler.pm Modified: incubator/spamassassin/trunk/MANIFEST incubator/spamassassin/trunk/MANIFEST.SKIP incubator/spamassassin/trunk/lib/Mail/SpamAssassin.pm incubator/spamassassin/trunk/lib/Mail/SpamAssassin/Conf.pm incubator/spamassassin/trunk/lib/Mail/SpamAssassin/PerMsgStatus.pm Log: plugin support, using new loadplugin configuration command Modified: incubator/spamassassin/trunk/MANIFEST ============================================================================== --- incubator/spamassassin/trunk/MANIFEST (original) +++ incubator/spamassassin/trunk/MANIFEST Thu Jan 22 22:01:21 2004 @@ -240,3 +240,5 @@ tools/split_corpora tools/test_extract tools/triplets.pl +lib/Mail/SpamAssassin/Plugin.pm +lib/Mail/SpamAssassin/PluginHandler.pm Modified: incubator/spamassassin/trunk/MANIFEST.SKIP ============================================================================== --- incubator/spamassassin/trunk/MANIFEST.SKIP (original) +++ incubator/spamassassin/trunk/MANIFEST.SKIP Thu Jan 22 22:01:21 2004 @@ -111,3 +111,4 @@ tasks/.* build/2.60_change_summary build/replace_license_blocks +sa-learn Modified: incubator/spamassassin/trunk/lib/Mail/SpamAssassin.pm ============================================================================== --- incubator/spamassassin/trunk/lib/Mail/SpamAssassin.pm (original) +++ incubator/spamassassin/trunk/lib/Mail/SpamAssassin.pm Thu Jan 22 22:01:21 2004 @@ -113,6 +113,7 @@ use Mail::SpamAssassin::PerMsgStatus; use Mail::SpamAssassin::MsgParser; use Mail::SpamAssassin::Bayes; +use Mail::SpamAssassin::PluginHandler; use File::Basename; use File::Path; @@ -310,6 +311,7 @@ $DEBUG->{rulesrun}=64; $self->{conf} ||= new Mail::SpamAssassin::Conf ($self); + $self->{plugins} = Mail::SpamAssassin::PluginHandler->new ($self); $self->{save_pattern_hits} ||= 0; @@ -1038,11 +1040,13 @@ my $text = join ('',<IN>); close IN; + $self->{conf}->{main} = $self; $self->{conf}->parse_scores_only ($text); if ($self->{conf}->{allow_user_rules}) { dbg("finishing parsing!"); $self->{conf}->finish_parsing(); } + delete $self->{conf}->{main}; # to allow future GC'ing } ########################################################################### @@ -1232,8 +1236,10 @@ warn "No configuration text or files found! Please check your setup.\n"; } + $self->{conf}->{main} = $self; $self->{conf}->parse_rules ($self->{config_text}); $self->{conf}->finish_parsing (); + delete $self->{conf}->{main}; # to allow future GC'ing delete $self->{config_text}; @@ -1453,6 +1459,14 @@ closedir SA_CF_DIR; return map { "$dir/$_" } sort { $a cmp $b } @cfs; # sort numerically +} + +########################################################################### + +sub call_plugins { + my $self = shift; + my $subname = shift; + return $self->{plugins}->callback ($subname, @_); } ########################################################################### Modified: incubator/spamassassin/trunk/lib/Mail/SpamAssassin/Conf.pm ============================================================================== --- incubator/spamassassin/trunk/lib/Mail/SpamAssassin/Conf.pm (original) +++ incubator/spamassassin/trunk/lib/Mail/SpamAssassin/Conf.pm Thu Jan 22 22:01:21 2004 @@ -201,6 +201,8 @@ $self->{rawbody_evals} = { }; $self->{meta_tests} = { }; + $self->{eval_plugins} = { }; + # testing stuff $self->{regression_tests} = { }; @@ -1986,7 +1988,16 @@ $self->{num_check_received} = $1+0; next; } +########################################################################### + if ($self->{main}->call_plugins ("parse_config", { + line => $_, + user_config => $scoresonly + })) + { + # a plugin dealt with it successfully. + next; + } ########################################################################### # SECURITY: no eval'd code should be loaded before this line. @@ -2643,6 +2654,20 @@ # user_scores_sql_table here. All just take \S+ and set the string of the # same name on $self. +=item loadplugin PluginModuleName /path/to/module.pm + +Load a SpamAssassin plugin module. The C<PluginModuleName> is the perl module +name, used to create the plugin object itself; C</path/to/module.pm> is the +file to load, containing the module's perl code. + +See C<Mail::SpamAssassin::Plugin> for more details on writing plugins. + +=cut + + if (/^loadplugin\s+(\S+)\s+(\S+)$/) { + $self->load_plugin ($1, $2); next; + } + ########################################################################### failed_line: @@ -2866,6 +2891,18 @@ } return 0; +} + +########################################################################### + +sub load_plugin { + my ($self, $package, $path) = @_; + $self->{main}->{plugins}->load_plugin ($package, $path); +} + +sub register_eval_rule { + my ($self, $pluginobj, $nameofsub) = @_; + $self->{eval_plugins}->{$nameofsub} = $pluginobj; } ########################################################################### Modified: incubator/spamassassin/trunk/lib/Mail/SpamAssassin/PerMsgStatus.pm ============================================================================== --- incubator/spamassassin/trunk/lib/Mail/SpamAssassin/PerMsgStatus.pm (original) +++ incubator/spamassassin/trunk/lib/Mail/SpamAssassin/PerMsgStatus.pm Thu Jan 22 22:01:21 2004 @@ -2174,6 +2174,17 @@ my ($function, @args) = @{$test}; unshift(@args, @extraevalargs); + # check to make sure the function is defined + if (!$self->can ($function)) { + my $pluginobj = $self->{conf}->{eval_plugins}->{$function}; + if ($pluginobj) { + # we have a plugin for this. eval its function + $self->register_plugin_eval_glue ($pluginobj, $function); + } else { + dbg ("no method found for eval test $function"); + } + } + eval { $result = $self->$function(@args); }; @@ -2191,6 +2202,31 @@ } else { #dbg("Ran run_eval_test rule $rulename but did not get hit", "rulesrun", 32) if $debugenabled; } + } +} + +sub register_plugin_eval_glue { + my ($self, $pluginobj, $function) = @_; + + dbg ("registering glue method for $function ($pluginobj)"); + my $evalstr = <<"ENDOFEVAL"; +{ + package Mail::SpamAssassin::PerMsgStatus; + + sub $function { + my (\$self) = shift; + my \$plugin = \$self->{conf}->{eval_plugins}->{$function}; + return \$plugin->$function (\$self, \@_); + } + + 1; +} +ENDOFEVAL + eval $evalstr; + + if ($@) { + warn "Failed to run header SpamAssassin tests, skipping some: $@\n"; + $self->{rule_errors}++; } } Added: incubator/spamassassin/trunk/lib/Mail/SpamAssassin/Plugin.pm ============================================================================== --- (empty file) +++ incubator/spamassassin/trunk/lib/Mail/SpamAssassin/Plugin.pm Thu Jan 22 22:01:21 2004 @@ -0,0 +1,309 @@ +# <@LICENSE> +# ==================================================================== +# The Apache Software License, Version 1.1 +# +# Copyright (c) 2000 The Apache Software Foundation. All rights +# reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# +# 3. The end-user documentation included with the redistribution, +# if any, must include the following acknowledgment: +# "This product includes software developed by the +# Apache Software Foundation (http://www.apache.org/)." +# Alternately, this acknowledgment may appear in the software itself, +# if and wherever such third-party acknowledgments normally appear. +# +# 4. The names "Apache" and "Apache Software Foundation" must +# not be used to endorse or promote products derived from this +# software without prior written permission. For written +# permission, please contact apache [at] apache +# +# 5. Products derived from this software may not be called "Apache", +# nor may "Apache" appear in their name, without prior written +# permission of the Apache Software Foundation. +# +# THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED +# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR +# ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# ==================================================================== +# +# This software consists of voluntary contributions made by many +# individuals on behalf of the Apache Software Foundation. For more +# information on the Apache Software Foundation, please see +# <http://www.apache.org/>. +# +# Portions of this software are based upon public domain software +# originally written at the National Center for Supercomputing Applications, +# University of Illinois, Urbana-Champaign. +# </@LICENSE> + +=head1 NAME + +Mail::SpamAssassin::Plugin - SpamAssassin plugin base class + +=head1 SYNOPSIS + + package MyPlugin; + + use Mail::SpamAssassin::Plugin; + use vars qw(@ISA); + @ISA = qw(Mail::SpamAssassin::Plugin); + + sub new { + my $class = shift; + my $mailsaobject = shift; + + # the usual perlobj boilerplate to create a subclass object + $class = ref($class) || $class; + my $self = $class->SUPER::new($mailsaobject); + bless ($self, $class); + + # then register an eval rule + $self->register_eval_rule ("check_for_foo"); + + # and return the new plugin object + return $self; + } + + ...methods... + + 1; + +=head1 DESCRIPTION + +This is the base class for SpamAssassin plugins; all plugins must be objects +that implement this class. + +This class provides no-op stub methods for all the callbacks that a plugin +can receive. It is expected that your plugin will override one or more +of these stubs to perform its actions. + +SpamAssassin implements a plugin chain; each callback event is passed to each +of the registered plugin objects in turn. Any plugin can call +C<$plugin->inhibit_further_callbacks()> to block delivery of that event to +later plugins in the chain. This is useful if the plugin has handled the +event, and there will be no need for later plugins to handle it as well. + +The following methods can be overridden by subclasses to handle events +that SpamAssassin will call back to: + +=head1 INTERFACE + +=over 4 + +=cut + +package Mail::SpamAssassin::Plugin; +use Mail::SpamAssassin; + +use strict; +use bytes; + +use vars qw{ + @ISA $VERSION +}; + +@ISA = qw(); +$VERSION = 'bogus'; + +########################################################################### + +=item $plugin = MyPluginClass->new ($mailsaobject) + +Constructor. Plugins that need to register themselves will need to +define their own; the default super-class constructor will work fine +for plugins that just override a method. + +Note that subclasses must provide the C<$mailsaobject> to the +superclass constructor, like so: + + my $self = $class->SUPER::new($mailsaobject); + +=cut + +sub new { + my $class = shift; + my $mailsaobject = shift; + $class = ref($class) || $class; + + if (!defined $mailsaobject) { + die "plugin: usage: Mail::SpamAssassin::Plugin::new(class,mailsaobject)"; + } + + my $self = { + main => $mailsaobject, + _inhibit_further_callbacks => 0 + }; + bless ($self, $class); + $self; +} + +=item $plugin->parse_config ( { options ... } ) + +Parse a configuration line that hasn't already been handled. C<options> +is a reference to a hash containing these options: + +=over 4 + +=item line + +The line of configuration text to parse. This has leading and trailing +whitespace, and comments, removed. + +=item user_config + +A boolean: C<1> if reading a user's configuration, C<0> if reading the +system-wide configuration files. + +=back + +If the configuration line was a setting that is handled by this plugin, the +method implementation should call C<$plugin->inhibit_further_callbacks()> and +return C<1>. + +If the setting is not handled by this plugin, the method should return C<0> so +that a later plugin may handle it, or so that SpamAssassin can output a warning +message to the user if no plugin understands it. + +Note that it is suggested that configuration be stored on the +C<Mail::SpamAssassin::Conf> object in use, instead of the plugin object itself. +That can be found as C<$plugin->{main}->{conf}>. + +=cut + +sub parse_config { + my ($self, $opts) = @_; + # implemented by subclasses, no-op by default + return 0; +} + +=item $plugin->finish () + +Called when the C<Mail::SpamAssassin> object is destroyed. + +=cut + +sub finish { + my ($self) = @_; + # implemented by subclasses, no-op by default +} + +########################################################################### + +=back + +=head1 HELPER APIS + +These methods provide an API for plugins to register themselves +to receive specific events, or control the callback chain behaviour. + +=over 4 + +=item $plugin->register_eval_rule ($nameofevalsub) + +Plugins that implement an eval test will need to call this, so that +SpamAssassin calls into the object when that eval test is encountered. + +For example, + + $plugin->register_eval_rule ('check_for_foo') + +will cause C<$plugin->check_for_foo()> to be called for this +SpamAssassin rule: + + header FOO_RULE eval:check_for_foo() + +Note that eval rules are passed the following arguments: + +=over 4 + +=item The plugin object itself + +=item The C<Mail::SpamAssassin::PerMsgStatus> object calling the rule + +=item any and all arguments specified in the configuration file + +=back + +In other words, the eval test method should look something like this: + + sub check_for_foo { + my ($self, $permsgstatus, ...arguments...) = @_; + ...code returning 0 or 1 + } + +Note that the headers can be accessed using the C<get()> method on the +C<Mail::SpamAssassin::PerMsgStatus> object, and the body by +C<get_decoded_stripped_body_text_array()> and other similar methods. +Similarly, the C<Mail::SpamAssassin::Conf> object holding the current +configuration may be accessed through C<$permsgstatus->{main}->{conf}>. + +The eval rule should return C<1> for a hit, or C<0> if the rule +is not hit. + +State for a single message being scanned should be stored on the C<$checker> +object, not on the C<$self> object, since C<$self> persists between scan +operations. + +=cut + +sub register_eval_rule { + my ($self, $nameofsub) = @_; + $self->{main}->{conf}->register_eval_rule ($self, $nameofsub); +} + +=item $plugin->inhibit_further_callbacks() + +Tells the plugin handler to inhibit calling into other plugins in the plugin +chain for the current callback. Frequently used when parsing configuration +settings using C<parse_config()>. + +=cut + +sub inhibit_further_callbacks { + my ($self) = @_; + $self->{_inhibit_further_callbacks} = 1; +} + +=item dbg ($message) + +Output a debugging message C<$message>, if the SpamAssassin object is running +with debugging turned on. + +=cut + +sub dbg { Mail::SpamAssassin::dbg (@_); } + +1; + +=back + +=head1 SEE ALSO + +C<Mail::SpamAssassin> + +C<Mail::SpamAssassin::PerMsgStatus> + +http://bugzilla.spamassassin.org/show_bug.cgi?id=2163 + +=cut Added: incubator/spamassassin/trunk/lib/Mail/SpamAssassin/PluginHandler.pm ============================================================================== --- (empty file) +++ incubator/spamassassin/trunk/lib/Mail/SpamAssassin/PluginHandler.pm Thu Jan 22 22:01:21 2004 @@ -0,0 +1,162 @@ +# <@LICENSE> +# ==================================================================== +# The Apache Software License, Version 1.1 +# +# Copyright (c) 2000 The Apache Software Foundation. All rights +# reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# +# 3. The end-user documentation included with the redistribution, +# if any, must include the following acknowledgment: +# "This product includes software developed by the +# Apache Software Foundation (http://www.apache.org/)." +# Alternately, this acknowledgment may appear in the software itself, +# if and wherever such third-party acknowledgments normally appear. +# +# 4. The names "Apache" and "Apache Software Foundation" must +# not be used to endorse or promote products derived from this +# software without prior written permission. For written +# permission, please contact apache [at] apache +# +# 5. Products derived from this software may not be called "Apache", +# nor may "Apache" appear in their name, without prior written +# permission of the Apache Software Foundation. +# +# THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED +# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR +# ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# ==================================================================== +# +# This software consists of voluntary contributions made by many +# individuals on behalf of the Apache Software Foundation. For more +# information on the Apache Software Foundation, please see +# <http://www.apache.org/>. +# +# Portions of this software are based upon public domain software +# originally written at the National Center for Supercomputing Applications, +# University of Illinois, Urbana-Champaign. +# </@LICENSE> + +=head1 NAME + +Mail::SpamAssassin::PluginHandler - SpamAssassin plugin handler + +=cut + +package Mail::SpamAssassin::PluginHandler; +use Mail::SpamAssassin; +use Mail::SpamAssassin::Plugin; + +use strict; +use bytes; + +use vars qw{ + @ISA $VERSION +}; + +@ISA = qw(); + +$VERSION = 'bogus'; # avoid CPAN.pm picking up version strings later + +########################################################################### + +sub new { + my $class = shift; + my $main = shift; + $class = ref($class) || $class; + my $self = { + plugins => [ ], + main => $main + }; + bless ($self, $class); + $self; +} + +########################################################################### + +sub load_plugin { + my ($self, $package, $path) = @_; + + dbg ("plugin: loading $path"); + + if (!do $path) { + if ($@) { warn "failed to parse plugin $path: $@\n"; } + elsif ($!) { warn "failed to load plugin $path: $!\n"; } + } + + my $plugin = eval $package.q{->new ($self->{main}); }; + + if ($@ || !$plugin) { warn "failed to create plugin $package: $@\n"; } + + if ($plugin) { + $self->{main}->{plugins}->register_plugin ($plugin); + } +} + +sub register_plugin { + my ($self, $plugin) = @_; + $plugin->{main} = $self->{main}; + push (@{$self->{plugins}}, $plugin); + dbg ("plugin: registered $plugin"); +} + +########################################################################### + +sub callback { + my $self = shift; + my $subname = shift; + my $ret; + + foreach my $plugin (@{$self->{plugins}}) { + $plugin->{_inhibit_further_callbacks} = 0; + + dbg ("plugin: calling $subname on $plugin"); + my $methodref = $plugin->can ($subname); + $ret = &$methodref ($plugin, @_); + + if ($plugin->{_inhibit_further_callbacks}) { + dbg ("plugin: $plugin inhibited further callbacks"); + last; + } + } + + return $ret; +} + +########################################################################### + +sub finish { + my $self = shift; + foreach my $plugin (@{$self->{plugins}}) { + $plugin->finish(); + delete $plugin->{main}; + } + delete $self->{plugins}; + delete $self->{main}; +} + +########################################################################### + +sub dbg { Mail::SpamAssassin::dbg (@_); } + +1;
|