Author: vetinari
Date: Sat Nov 24 03:15:29 2007
New Revision: 819
Added:
contrib/vetinari/hook_skip/
contrib/vetinari/hook_skip/README.pod
contrib/vetinari/hook_skip/hook_skip-0.42rc1-r818.diff
contrib/vetinari/hook_skip/skip_plugins
Log:
* pluggable skipping of selected plugins
- plugins to skip can be added / removed on the fly by other plugins
- not committed to core, it may slow down, we'll see how much
Added: contrib/vetinari/hook_skip/README.pod
==============================================================================
--- (empty file)
+++ contrib/vetinari/hook_skip/README.pod Sat Nov 24 03:15:29 2007
@@ -0,0 +1,50 @@
+
+=head1 hook_skip
+
+This hook is run before a hooked subroutine for a plugin is called. If this
+hook returns B<DENY>, the hook for a plugin is not executed and the next
+plugin (if any left) for the same hook is called. The C<logging> and
+the C<skip> hooks cannot be skipped.
+
+Arguments:
+ my ($self, $transaction, $plugin, $hook) = @_;
+The I<$plugin> is the encoded plugin name, i.e. the string which
+C<$self-E<gt>plugin_name> returns.
+
+Allowed return values are
+
+=over 4
+
+=item DENY
+
+Skip this plugins hook and continue with the next plugin.
+
+=back
+
+Any other return value continues as usual.
+
+Example plugin is F<skip_plugins>:
+
+ sub hook_skip {
+ my ($self, $txn, $plugin, $hook) = @_;
+ my $cfg = $self->qp->connection->notes("_skip_plugins") || {};
+ return (DECLINED)
+ unless $cfg->{$plugin};
+ return (DENY, "Plugin $plugin skipped");
+ }
+
+If you like to have no F<spamassassin> plugin for all authed clients use
+a plugin like this together with the F<skip_plugins> plugin:
+
+ sub hook_mail {
+ my ($self, $transaction, $sender, %args) = @_;
+ my $authed = $self->qp->authenticated; # FIXME: is this correct?
+ if ($authed and $authed == OK) {
+ my $skip = $self->qp->connection->notes("_skip_plugins") || {};
+ $skip->{"spamassassin"} = 1;
+ $self->qp->connection->notes("_skip_plugins", $skip);
+ }
+ return (DECLINED;
+ }
+
+=cut
Added: contrib/vetinari/hook_skip/hook_skip-0.42rc1-r818.diff
==============================================================================
--- (empty file)
+++ contrib/vetinari/hook_skip/hook_skip-0.42rc1-r818.diff Sat Nov 24
03:15:29 2007
@@ -0,0 +1,31 @@
+Index: lib/Qpsmtpd.pm
+===================================================================
+--- lib/Qpsmtpd.pm (revision 818)
++++ lib/Qpsmtpd.pm (working copy)
+@@ -372,6 +372,13 @@
+ $@ and warn("FATAL LOGGING PLUGIN ERROR: ", $@) and next;
+ }
+ else {
++ if ($hook ne "skip") {
++ @r = $self->run_hooks("skip", $code->{name}, $hook);
++ if (defined $r[0] && $r[0] == DENY) {
++ $self->log(LOGDEBUG, "Skipping plugin ".$code->{name});
++ next;
++ }
++ }
+ $self->varlog(LOGDEBUG, $hook, $code->{name});
+ eval { (@r) = $code->{code}->($self, $self->transaction, @$args); };
+ $@ and $self->log(LOGCRIT, "FATAL PLUGIN ERROR: ", $@) and next;
+Index: lib/Qpsmtpd/Plugin.pm
+===================================================================
+--- lib/Qpsmtpd/Plugin.pm (revision 818)
++++ lib/Qpsmtpd/Plugin.pm (working copy)
+@@ -4,7 +4,7 @@
+
+ # more or less in the order they will fire
+ our @hooks = qw(
+- logging config pre-connection connect ehlo_parse ehlo
++ logging config pre-connection skip connect ehlo_parse ehlo
+ helo_parse helo auth_parse auth auth-plain auth-login auth-cram-md5
+ rcpt_parse rcpt_pre rcpt mail_parse mail mail_pre
+ data data_post queue_pre queue queue_post
Added: contrib/vetinari/hook_skip/skip_plugins
==============================================================================
--- (empty file)
+++ contrib/vetinari/hook_skip/skip_plugins Sat Nov 24 03:15:29 2007
@@ -0,0 +1,106 @@
+
+=head1 NAME
+
+skip_plugins - don't run selected plugins for some hosts
+
+=head1 DESCRIPTION
+
+The B<skip_plugins> plugin allows you to skip selected plugins for some
+clients. This is similar to some whitelist plugins, without the need to
+modify any plugin.
+
+This plugin should be run before any other plugins hooking to the
+I<hook_connect>. The config allows to run all plugins for one host in a
+subnet and skip some for all other hosts in this network.
+
+=head1 CONFIG
+
+The config file I<skip_plugins> contains lines with two or three items per
+line. The first field is a network/mask pair (or just a single IP address).
+An action is set in the second field: currently B<continue> or B<skip> are
+valid actions.
+
+If a host matches a B<continue> line, the parsing is stopped and all
+plugins are run for this host. A B<skip> action tells qpsmtpd to skip
+the plugins listed in the third field for this connection.
+
+The plugin list in the third field must be separated by "," without any spaces.
+
+=head1 EXAMPLE
+
+ 10.7.7.2 continue
+ 10.7.7.0/24 skip spamassassin,check_earlytalker
+
+=head1 NOTES
+
+The list of plugins to be skipped is set in the connection note
+'_skip_plugins'. This is a ref to a hash. Disabling and re-enabling a
+plugin can be done on the fly from any other plugin.
+
+The plugin name B<must> be the C<encoded> version, i.e. the string
+ $self->plugin_name
+returns: e.g. C<virus::clamdscan> for the C<virus/clamdscan> plugin.
+
+This this would re-enable the C<spamassassin> plugin for this connection:
+
+ my $skip = $self->qp->connection->notes('_skip_plugins') || {};
+ $skip->{'spamassassin'} = 0;
+ $self->qp->connection->notes('_skip_plugins', $skip);
+
+Setting I<$skip-E<gt>{'spamassassin'} = 1;> in the above example would
+disable the C<spamassassin> plugin on the fly for this connection.
+
+=cut
+
+use Socket;
+
+sub hook_connect {
+ my ($self,$transaction) = @_;
+
+ my %skip = ();
+ my $remote = $self->qp->connection->remote_ip;
+
+ foreach ($self->qp->config("skip_plugins")) {
+ s/^\s*//;
+ my ($ipmask, $action, $plugins) = split /\s+/, $_, 3;
+ next unless defined $action;
+ $action = lc $action;
+ unless (defined $plugins) {
+ $plugins = "";
+ }
+
+ my ($net,$mask) = split '/', $ipmask, 2;
+ if (!defined $mask) {
+ $mask = 32;
+ }
+ $mask = pack "B32", "1"x($mask)."0"x(32-$mask);
+
+ if (join(".", unpack("C4", inet_aton($remote) & $mask)) eq $net) {
+ if ($action eq 'continue') {
+ %skip = ();
+ }
+ elsif ($action eq 'skip') {
+ %skip = map { ($_, 1) } split /,/, $plugins;
+ $self->log(LOGDEBUG, "skipping plugins ".join(",", keys
%skip));
+ }
+ else {
+ %skip = ();
+ $self->log(LOGDEBUG, "unknown action '$action' for $ipmask");
+ }
+ $self->qp->connection->notes("_skip_plugins", \%skip);
+ last;
+ }
+ }
+ return (DECLINED);
+}
+
+sub hook_skip {
+ my ($self, $txn, $plugin, $hook) = @_;
+ ## $self->log(LOGDEBUG, "SKIP: Plugin $plugin, Hook $hook");
+ my $skip = $self->qp->connection->notes("_skip_plugins") || {};
+ return (DECLINED)
+ unless $skip->{$plugin};
+ return (DENY, "Hook $hook of plugin $plugin skipped");
+}
+
+# vim: sw=4 ts=4 expandtab syn=perl