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

Reply via email to