Hi all,

Following up the per-user config discussions from a couple of weeks ago,
attached is a proof-of-concept plugin (per_recipient_config) implementing 
the simple form of per-domain and per-user configs I suggested then - 
using domain and username subdirectories under the qpsmtpd/config 
directory to hold per-domain and per-user configs. e.g.

  To create per-user configs, create username subdirectories within
  domain subdirectories in qpsmtpd/config, and create individual config
  files there e.g.

     config/
       spamassassin          [0]
       openfusion.com.au/
         spamassassin        [1]
         gavin/
           spamassassin      [2]

  This allows the spamassassin config file [2] to be used for mail to
  [EMAIL PROTECTED], while all other openfusion.com.au recipients
  use the spamassassin config file [1]. All other domains use the global
  spamassassin config file [0].

  Symlinks can also be used at both the directory and file levels to 
  allow aliasing.


The plugin requires a tiny patch (attached) to be applied to Qpsmtpd.pm,
allowing config plugins to be passed additional arbitrary parameters - 
the problem being that the recipient is not available within the 
transaction at the time the config hook gets called, and so must be 
passed explicitly e.g. in a rcpt hook somewhere:

  my $arg = { rcpt => $rcpt };
  my @spamassassin = $self->qp->config('spamassassin', $arg);
  my @whitelistsenders = $self->qp->config('whitelistsenders', $arg);

within a rcpt hook somewhere. Ask/Matt, any chance of getting this 
patch applied, or any better suggestions?


If there's interest, I'll add support for cdb/map configs and caching,
but it works fine here with standard text config files. Only lightly
tested, so far.

Comments/brickbats welcome.

Cheers,
Gavin

-- 
Open Fusion P/L - Open Source Business Solutions [ Linux - Perl - Apache ]
ph:  02 9875 5032                        fax: 02 9875 4317
web: http://www.openfusion.com.au        mob: 0403 171712
- Fashion is a variable, but style is a constant - Programming Perl
=head1 NAME

per_recipient_config

=head1 DESCRIPTION

A simple approach for loading per_recipient config data within the rcpt 
hook. Returns DECLINED for excluded config files, if no recipient can be 
identified, or if the specified per-recipient config file cannot be found.

=head1 CONFIG 

To create per-domain configs, create domain subdirectories within the
qpsmtpd config directory and create config files within those 
subdirectories. e.g.

  config/
    openfusion.com.au/
      dnsbl_zones
      spamassassin
      whitelistsenders
      whitelistrcpt
    someotherdomain.com/
      dnsbl_zones
      rhsbl_zones
      spamassassin
      whitelisthosts

To create per-user configs, create username subdirectories within
domain subdirectories in qpsmtpd/config, and create individual config
files there instead. e.g.

   config/
     spamassassin          [0]
     openfusion.com.au/
       spamassassin        [1]
       gavin/
         spamassassin      [2]

This allows the spamassassin config file [2] to be used for mail to
[EMAIL PROTECTED], while all other openfusion.com.au recipients
use the spamassassin config file [1]. All other domains use the global
spamassassin config file [0].

Symlinks can be used at both the directory and file levels to allow
aliasing.

=head1 BUGS/LIMITATIONS

per_recipient_config only works if passed a 'rcpt' argument, which
means really only in the rcpt hook (since it's only then that
individual recipients are identifiable). Non-rcpt-hook plugins that 
want to allow per_recipient configs therefore have to be specially 
adapted to support this (and ideally should take a 'per_recipient' 
plugin argument to turn this functionality on).

Per recipient configs *replace* the corresponding global config. 
Sometimes it's presumably better to merge?

No caching of results is done yet. Does not support CDB (map) config 
files yet.

=head1 AUTHOR

Written by Gavin Carr <[EMAIL PROTECTED]>.

=cut

my %EXCLUDE = map { $_ => 1 } qw(me timeout);
my $VERSION = 0.01;

sub register {
  my ($self, $qp, @args) = @_;
  $self->register_hook("config", "per_recipient_config");
}

sub per_recipient_config {
  my ($self, $transaction, $config, $arg) = @_; 

  # Ignore excluded config files
  return DECLINED if $EXCLUDE{$config};
  if ($arg->{type} and $arg->{type} eq 'map') {
    $self->log(1, "no support for map config files yet");
    return DECLINED;
  }

  # Get recipient domain and username
  return DECLINED unless $arg->{rcpt} && ref $arg->{rcpt};
  my $domain = lc $arg->{rcpt}->host;
  my $user = lc $arg->{rcpt}->user;

  my ($qphome) = ($0 =~ m!(.*?)/([^/]+)$!);
  unless (-d "$qphome/config/$domain") {
    $self->log(6, "no config/$domain directory found - skipping");
    return DECLINED;
  }

  # Check for per-user file
  my $configfile = '';
  $configfile = "$qphome/config/$domain/$user/$config"
    if -f "$qphome/config/$domain/$user/$config";
  $configfile ||= "$qphome/config/$domain/$config"
    if -f "$qphome/config/$domain/$config";
  unless ($configfile) {
    $self->log(6, "no '$config' configfile found for user [EMAIL PROTECTED] - 
skipping");
    return DECLINED;
  }
  
  my @config = ();
  unless (open CF, "<$configfile") {
    $self->log(1, "could not open configfile '$configfile': $!");
    return DECLINED;
  }
  @config = <CF>;
  close CF;
  chomp @config;
  @config = grep { $_ and $_ !~ m/^\s*#/ and $_ =~ m/\S/} @config;
  $self->log(6, "returning per_recipient_config '$config' ($configfile) for user 
'[EMAIL PROTECTED]':\n", Data::Dumper->Dump([EMAIL PROTECTED], [qw(config)]));
  return (OK, @config) if @config;
  return DECLINED;
}

--- lib/Qpsmtpd.pm.dist 2003-12-23 07:31:32.000000000 +1100
+++ lib/Qpsmtpd.pm      2004-01-12 07:55:56.000000000 +1100
@@ -24,7 +24,8 @@
 # database or whatever.
 #
 sub config {
-  my ($self, $c, $type) = @_;
+  my ($self, $c, $arg) = @_;
+  my $type = ref $arg ? $arg->{type} : $arg;
 
   #warn "SELF->config($c) ", ref $self;
 
@@ -33,7 +34,7 @@
                  timeout => 1200,
                  );
 
-  my ($rc, @config) = $self->run_hooks("config", $c);
+  my ($rc, @config) = $self->run_hooks("config", $c, $arg);
   @config = () unless $rc == OK;
 
   if (wantarray) {

Reply via email to