On Tue, 31 Oct 2006 19:56:11 -0800
Ask Bjørn Hansen <[EMAIL PROTECTED]> wrote:
> On Oct 31, 2006, at 6:38 PM, John Peacock wrote:
> 
> > The issue is that this plugin requires a patch to the core
> > to actually implement the skip part, which might be fine, but there 
> > might also
> > be a more elegant way to handle this that I can't see right now.
> 
> I'm not sure what it would look like; but it'd be nice to have an API 
> for querying and changing the list of configured plugins.  The API  
> should let you change them for the connection (changing them globally 
> would be hard or impossible, so that's less important).
This diff now does that, see perldoc lib/Qpsmtpd/PlugOut.pm for what you
can do :)
The skip_plugins plugin now uses this new API.

Oh, even if it was done for r669, it can be applied to r672 without
problems

        Hanno
diff -Nurx .svn -x config ../0.3x/config.sample/plugins ./config.sample/plugins
--- ../0.3x/config.sample/plugins       2006-04-07 22:21:23.000000000 +0200
+++ ./config.sample/plugins     2006-11-04 17:36:09.000000000 +0100
@@ -12,6 +12,9 @@
 # from one IP!
 hosts_allow
 
+# skip selected plugins for some clients:
+skip_plugins
+
 # enable to accept MAIL FROM:/RCPT TO: addresses without surrounding <>
 dont_require_anglebrackets
 
diff -Nurx .svn -x config ../0.3x/lib/Qpsmtpd/PlugOut.pm 
./lib/Qpsmtpd/PlugOut.pm
--- ../0.3x/lib/Qpsmtpd/PlugOut.pm      1970-01-01 01:00:00.000000000 +0100
+++ ./lib/Qpsmtpd/PlugOut.pm    2006-11-05 12:11:41.000000000 +0100
@@ -0,0 +1,177 @@
+#
+# PlugOut - disable or re-enable loaded plugins 
+#
+package Qpsmtpd::PlugOut;
+
+=head1 NAME
+
+Qpsmtpd::PlugOut - disable or re-enable loaded plugins for a connection
+
+=head1 DESCRIPTION
+
+The B<Qpsmtpd::PlugOut> module lets you disable (or reenable) plugins
+for the current connection. The functions are inside the plugins name space
+and have to be called like 
+  $self->loaded_plugins;
+
+=head1 API
+
+=over 6
+
+=item loaded_plugins( )
+
+This returns a hash. Keys are (escaped, see below) plugin names. The value
+tells you if the plugin is active (1) or disabled (0).
+
+=cut
+
+sub loaded_plugins {
+    my $self = shift;
+    # all plugins are in their own class "below" Qpsmtpd::Plugin,
+    # so we start searching the symbol table at this point
+    my @loaded = $self->_loaded("Qpsmtpd::Plugin");
+    foreach (@loaded) {
+        s/^Qpsmtpd::Plugin:://;
+    }
+    my %plugins = map { ($_, 1) } @loaded;
+    foreach ($self->disabled_plugins) {
+        $plugins{$_} = 0;
+    }
+    return %plugins;
+}
+
+sub _loaded {
+    my $self   = shift;
+    my $base   = shift;
+    my @loaded = ();
+    my (@sub, $symbol);
+    # let's see what's in this name space
+    local (*symbols) = *{"${base}::"};
+    foreach my $name (values %symbols) { 
+        # $name is read only while walking the stash
+
+        # not a class name? ok, next 
+        ($symbol = $name) =~ s/^\*(.*)::$/$1/ || next; 
+
+        # in qpsmtpd we have no way of loading a plugin with the same
+        # name as a sub directory inside the ./plugins dir, so we can safely
+        # use either the list of sub classes or the class itself we're 
+        # looking at (unlike perl, e.g. Qpsmtpd.pm <-> Qpsmtpd/Plugin.pm).
+        # ... this restricts qpsmtpd: No module named 
+        # Qpsmtpd::Plugin::Something must exist, or it will be returned
+        # as a loaded plugin...
+        @sub = $self->_loaded($symbol);
+        push @loaded, @sub ? @sub : $symbol;
+    }
+    return @loaded;
+}
+
+=item escape_plugin( $plugin_name )
+
+Turns a plugin filename into the way it is used inside qpsmtpd. This needs to
+be done before you B<plugin_disable()> or B<plugin_enable()> a plugin. To 
+see if a plugin is loaded, use something like
+
+ my %loaded = $self->loaded_plugins;
+ my $wanted = $self->escape_plugin("virus/clamav");
+ if (exists $loaded{$wanted}) {
+   ...
+ }
+... or shorter:
+
+ if ($self->plugin_is_loaded($self->escape_plugin("virus/clamav"))) {
+   ...
+ }
+
+=cut
+
+sub escape_plugin {
+    my $self = shift;
+    my $plugin_name = shift;
+    # "stolen" from Qpsmtpd.pm
+    # Escape everything into valid perl identifiers
+    $plugin_name =~ s/([^A-Za-z0-9_\/])/sprintf("_%2x",unpack("C",$1))/eg;
+
+    # second pass cares for slashes and words starting with a digit
+    $plugin_name =~ s{
+            (/+)       # directory
+            (\d?)      # package's first character
+           }[
+             "::" . (length $2 ? sprintf("_%2x",unpack("C",$2)) : "")
+            ]egx;
+    return $plugin_name;
+}
+
+=item disabled_plugins( )
+
+This returns a list of all plugins which are disabled for the current 
+connection. 
+
+=cut
+
+sub disabled_plugins {
+    my $self = shift;
+    my @skipped = ();
+    my $skip = $self->qp->connection->notes('_skip_plugins') || {};
+    foreach my $s (keys %{$skip}) {
+        push @skipped, $s
+            if $skip->{$s};
+    }
+    return @skipped;
+}
+
+=item plugin_disable( $plugin )
+
+B<plugin_disable()> disables a (loaded) plugin, it requires the plugin name
+to be escaped by B<escape_plugin()>. It returns true, if the given plugin
+name is a loaded plugin (and disables it of course).
+
+=cut
+
+sub plugin_disable {
+    my ($self,$plugin) = @_;
+    # do a basic check if the supplied plugin name is really a plugin
+    return 0 
+      unless $self->plugin_is_loaded($plugin);
+    my $skip = $self->qp->connection->notes('_skip_plugins') || {};
+    $skip->{$plugin} = 1;
+    $self->qp->connection->notes('_skip_plugins', $skip);
+    return 1;
+}
+
+=item plugin_enable( $plugin )
+
+B<plugin_enable()> re-enables a (loaded) plugin, it requires the plugin name
+to be escaped by B<escape_plugin()>. It returns "0", if the given plugin
+name is not a loaded plugin. Else it returns "1" after enabling.
+
+=cut
+
+sub plugin_enable {
+    my ($self,$plugin) = @_;
+    return 0 
+      unless $self->plugin_is_loaded($plugin);
+    my $skip = $self->qp->connection->notes('_skip_plugins') || {};
+    $skip->{$plugin} = 0;
+    $self->qp->connection->notes('_skip_plugins', $skip);
+    return 1;
+}
+
+=item plugin_is_loaded( $plugin )
+
+Returns true, if the given (escaped) plugin name is a loaded plugin
+
+=cut
+
+sub plugin_is_loaded {
+    my ($self,$plugin) = @_;
+    # each plugin has a sub called "plugin_name()";
+    return defined &{"Qpsmtpd::Plugin::${plugin}::plugin_name"}; 
+}
+
+=back
+
+=cut
+
+1;
+# the end :)
diff -Nurx .svn -x config ../0.3x/lib/Qpsmtpd/Plugin.pm ./lib/Qpsmtpd/Plugin.pm
--- ../0.3x/lib/Qpsmtpd/Plugin.pm       2006-07-22 19:45:37.000000000 +0200
+++ ./lib/Qpsmtpd/Plugin.pm     2006-11-05 09:52:47.000000000 +0100
@@ -153,9 +153,10 @@
                    "package $package;",
                    'use Qpsmtpd::Constants;',
                    "require Qpsmtpd::Plugin;",
+                   "require Qpsmtpd::PlugOut;",
                    'use vars qw(@ISA);',
                     'use strict;',
-                   '@ISA = qw(Qpsmtpd::Plugin);',
+                   '@ISA = qw(Qpsmtpd::Plugin Qpsmtpd::PlugOut);',
                    ($test_mode ? 'use Test::More;' : ''),
                    "sub plugin_name { qq[$plugin] }",
                    $line,
diff -Nurx .svn -x config ../0.3x/lib/Qpsmtpd.pm ./lib/Qpsmtpd.pm
--- ../0.3x/lib/Qpsmtpd.pm      2006-07-22 19:45:37.000000000 +0200
+++ ./lib/Qpsmtpd.pm    2006-11-02 22:16:20.000000000 +0100
@@ -327,6 +327,11 @@
         $@ and warn("FATAL LOGGING PLUGIN ERROR: ", $@) and next;
       }
       else {
+        my $skip = $self->connection->notes('_skip_plugins');
+        if (exists $skip->{$code->{name}} and $skip->{$code->{name}}) {
+            $self->log(LOGDEBUG, "skipping plugin ".$code->{name});
+            next;
+        }
         $self->varlog(LOGINFO, $hook, $code->{name});
         eval { (@r) = $code->{code}->($self, $self->transaction, @_); };
         $@ and $self->log(LOGCRIT, "FATAL PLUGIN ERROR: ", $@) and next;
diff -Nurx .svn -x config ../0.3x/plugins/skip_plugins ./plugins/skip_plugins
--- ../0.3x/plugins/skip_plugins        1970-01-01 01:00:00.000000000 +0100
+++ ./plugins/skip_plugins      2006-11-05 11:49:50.000000000 +0100
@@ -0,0 +1,90 @@
+
+=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
+
+To disable a plugin for all clients except for one subnet:
+
+ 10.1.0.0/16    continue
+ 0.0.0.0/0      skip    virus/clamdscan
+
+=head1 NOTES 
+
+see perldoc Qpsmtpd::PlugOut for more about disabling / reenabling plugins
+
+=cut 
+
+use Socket;
+
+sub hook_connect {
+    my ($self,$transaction) = @_;
+
+    my %skip = ();
+    my $remote = $self->qp->connection->remote_ip;
+    foreach ($self->qp->config("skip_plugins")) {
+        chomp;
+        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 'skip') {
+                foreach my $plugin (split /,/, $plugins) {
+                    $self->plugin_disable($self->escape_plugin($plugin)) 
+                      or $self->log(LOGWARN, "tried to disable a plugin "
+                                            ."which was not loaded: $plugin");
+                }
+                $self->log(LOGDEBUG, "skipping plugins "
+                                    .join(",", $self->disabled_plugins));
+            }
+            elsif ($action eq 'continue') {
+                $self->log(LOGDEBUG, "ok, doing nothing with the plugins");
+            }
+            else {
+                $self->log(LOGDEBUG, "unknown action '$action' for $ipmask");
+            }
+            last;
+        }
+    }
+    return (DECLINED);
+}
+
+# vim: sw=4 ts=4 expandtab syn=perl

Reply via email to