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) {