Bug 2163

Summary: plugin support
Product: Spamassassin Reporter: Rod Adams <rod>
Component: Rules (Eval Tests)Assignee: SpamAssassin Developer Mailing List <dev>
Status: RESOLVED FIXED    
Severity: enhancement CC: kmactane
Priority: P2    
Version: unspecified   
Target Milestone: 3.0.0   
Hardware: All   
OS: All   
Whiteboard:

Description Rod Adams 2003-06-30 11:52:04 UTC
It would be nice to be able to write eval rules for my local site without
mucking up the default EvalTests.pm, which would gets overridden whenever I upgrade.

Would seem a moderately simple to do something like:

my $dir = ${wherever_local.cf_is_on_this_box};
if (-r "$dir/EvalTests.pm") {
   eval { do "$dir/EvalTests.pm"; }
}

 -- or --

add a AUTOLOAD function which will bounce unknown functions over to a
Mail::SpamAssassin::LocalEvalTests, which can be somewhere else in PERL5LIB.

 -- or --

create the second package as above, but instead of a AUTOLOAD relay, have the
user define rules with "localeval:" instead of "eval:". This is my preference of
the these three suggestions.


This seems like a nice utility for both development of new tests and for the
creations of evals that have no bussiness hitting the CVS, exploiting some local
nuance.

Downside would be I'm too new to SA to know how often and radically the code
base supporting EvalTests.pm is, and how painful it will be for users to upgrade
the local tests. But that will happen regardless, so I don't see this as a major
to not do one of these ideas.

Comments?
Comment 1 Justin Mason 2003-10-10 12:22:30 UTC
I'm pro this idea, BTW.  One thing I've been wanting to do -- but without any
time to do it ;) -- is to open up SpamAssassin a little more for 2.70.

What I'm thinking is:

    - if it were possible to drop new .pm files (let's say, objects of type
      Mail::SpamAssassin::EvalTest) into /etc/mail/spamassassin to provide
      new eval function APIs, and

    - SpamAssassin provided clean documented OO APIs for third-party
      eval-test .pm objects to use, to access the body parts, MIME
      structure, headers, and parsed body text, and

    - possibly provide a sample of what such an EvalTest object is coded like 
      (even just on a webpage)

That would be a great way to do this.

BTW, Matt talked about this in his presentation to the Spam Conference in
January: 'SA is a framework for combining spam detection techniques'.

Opening up the framework to more sophisticated code-based tests would be great,
especially since we've put so much work into reliable message parsing that's
resilient against the attackers spammers use to render "real" MIME parsers
ineffective.  This way, we can make SA a usable *platform* for spam detection,
not just a spam detector on its own.

Any volunteers? ;)
Comment 2 Kenneth Porter 2003-11-17 09:28:06 UTC
I'd like to see this ability as well, to create variations of
check_for_to_in_subject (see for instance
http://bugzilla.spamassassin.org/show_bug.cgi?id=2638). As I reported on
SA-Talk, I see a lot of spam with my name or username, a comma, and whitespace
that I'd like to add a point or two for. Rod's idea of a "localeval" tag in a
rule sounds good to me.
Comment 3 Justin Mason 2003-11-17 12:08:22 UTC
I think the current status is that we're waiting for someone to free up some
time to work on "framework-izing" SA's internals; then this will fall out more
or less for free.  That hasn't happened yet. ;)

In the meantime, I think we'd be able to apply a patch.  However, it'd have to
be done like this in order to be future-proof:

- we have a base class, Mail::SpamAssassin::EvalTest .   This provides the
public APIs that local eval test implementations can use (e.g. getting headers,
getting body text in various formats, etc.)

- all local eval tests have to be subclasses of this.

- rather than defining a new prefix "localeval", we define a new test keyword,
"loadmodule" (in honour of Apache ;). e.g.

   header MY_EVAL      eval:myevalsub(args)
   loadmodule   /path/to/module/file.pm       (for full path specification)

or

   loadmodule   file.pm 

the latter will search in the system-wide config file locations (e.g.
/usr/share/spamassassin, /etc/mail/spamassassin, etc.,  but *not* the user home
dir by default, see below.)

- when "loadmodule" is found, it searches for the module.pm file, loads it, and
calls an init() function in that module to initialize it.  (this happens at
Mail::SpamAssassin::init() time, so once at boot for spamd.)

- that init() function will be passed the Mail::SpamAssassin object.  It must
create an instance of the local-eval class, then call
$sa->register_local_eval($instance, "nameofsub") where "nameofsub" is the name
of the eval test subroutine that this local-eval class will handle.   SA will
then know to call that method on that object when an eval test refers to
"eval:nameofsub(..args..)", and we don't need an extra "localeval" keyword.

- the init() function can also do any rule-specific initialization required;
e.g. loading dictionaries, etc.

- users can make their own eval test classes *only if* a new parameter,
allow_user_eval_rules is set to 1.  It's 0 by default for security (because
this is raw perl code being executed).

So that's how I see it so far -- that gives us a lot of flexibility now, but is
fast, extensible for future additions, and nicely OO (which is a big problem
with our current eval test model).

Comment 4 Kenneth Porter 2003-11-17 14:19:24 UTC
Sounds good.

I would assume that the semantics of allow_user_eval_rules is that it really
means allow a module owned by *another* user, allowing an end-user to run
spamassassin and use his own rules but preventing spamd (also running as a
mortal) from using his stuff.
Comment 5 Justin Mason 2003-11-17 14:47:58 UTC
Subject: Re: [SAdev]  local eval tests 

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1


>I would assume that the semantics of allow_user_eval_rules is that it really
>means allow a module owned by *another* user, allowing an end-user to run
>spamassassin and use his own rules but preventing spamd (also running as a
>mortal) from using his stuff.

Well, it'd mean allowing "loadmodule" lines in ~/.spamassassin/user_prefs
to be acted upon.  "spamd" *does* run as the user; the problem is that
many sites don't want their users running *any* code on the mailserver,
and would keep allow_user_eval_rules set to 0.  That way only
admin-supplied local eval rules would be possible in that configuration.

- --j.
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.2 (GNU/Linux)
Comment: Exmh CVS

iD8DBQE/uVAYQTcbUG5Y7woRAtWQAJ9/7pumUSN1YhEw/Zuaus1F0K109ACgwESO
tw+WXaTtKnYjxf7isl/0XJA=
=etrL
-----END PGP SIGNATURE-----

Comment 6 Duncan Findlay 2003-11-17 17:35:20 UTC
Subject: Re: [SAdev]  local eval tests

FWIW, allowing user eval's is no more of a security hole that allowing
user rules. Both are run as the user, but can contain arbitrary
code. (Which reminds me, spamd should check that a user_prefs is not
world writable before using.)
Comment 7 Justin Mason 2003-11-17 18:09:12 UTC
> FWIW, allowing user eval's is no more of a security hole that allowing
> user rules. Both are run as the user, but can contain arbitrary
> code. 

There is a difference: user rules are unexploitable, as far as I can
tell, *from our investigations so far*.   So exploiting them would require
a bit of work, and may be limited in their usefulness.  However, user
evals are trivial to exploit; the .pm just has to contain "run { system
("/bin/sh"); }" or whatever to get shell access.

So, given this, I'd suggest that there is a difference.  If
I was installing SA on a "no shell access" machine, with a relatively
trusted group of users, I'd probably set

  allow_user_rules 1
  allow_user_eval_rules 0

due to the level of trust.

> (Which reminds me, spamd should check that a user_prefs is not
> world writable before using.)

good point...
Comment 8 Justin Mason 2003-11-17 18:39:44 UTC
oh BTW -- another reason to add a 'allow_user_eval_rules' instead of reusing
'allow_user_rules' is because an admin with 'allow_user_rules' set would
silently allow his users to run arbitrary perl code a lot more easily that
way, possibly without realising this, once they upgraded.   IMO it'd be
better to fail-safe on this one.
Comment 9 Justin Mason 2004-01-22 22:01:29 UTC
OK, plugin support is now in CVS.

allow_user_eval_rules is not yet implemented, BTW; only the system-wide rules
files can currently contain plugins, not user configs.

Here's a sample plugin class:

package MyPlugin;

use Mail::SpamAssassin::Plugin;
use vars qw(@ISA);
@ISA = qw(Mail::SpamAssassin::Plugin);

# constructor: register the eval rule
sub new {
  my $class = shift;
  my $mailsaobject = shift;

  # some boilerplate...
  $class = ref($class) || $class;
  my $self = $class->SUPER::new($mailsaobject);
  bless ($self, $class);

  # the important bit!
  $self->register_eval_rule ("check_for_foo");

  warn "JMD registered MyPlugin $self";
  return $self;
}

# and the eval rule itself
sub check_for_foo {
  my ($self, $permsgstatus) = @_;
  warn "JMD MyPlugin eval test called on: $self";
  # ... hard work goes here...
  return 1;
}

1;


To try this out, save this as e.g. /home/jm/ftp/spamassassin/plugintest.pm,
and write these lines to /etc/mail/spamassassin/plugintest.cf:

  loadplugin     MyPlugin /home/jm/ftp/spamassassin/plugintest.pm
  header         MY_PLUGIN_FOO eval:check_for_foo()


Note that that's just for eval rules; however, plugins can receive settings
from Conf.pm, and the design is such that they can receive other arbitrary
events from other parts of SA.  So it should be quite usable for other stuff,
given more instrumentation points in key functions...
Comment 10 Theo Van Dinter 2004-02-15 17:55:48 UTC
justin: any reason that since plugin support is now in HEAD, the bug wasn't 
closed?
Comment 11 Justin Mason 2004-02-17 10:33:19 UTC
nope. ;)

Just lost the bug in the swamp that is bugzilla...