=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 gavin@openfusion.com.au, 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 . =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 $user\@$domain - skipping"); return DECLINED; } my @config = (); unless (open CF, "<$configfile") { $self->log(1, "could not open configfile '$configfile': $!"); return DECLINED; } @config = ; close CF; chomp @config; @config = grep { $_ and $_ !~ m/^\s*#/ and $_ =~ m/\S/} @config; $self->log(6, "returning per_recipient_config '$config' ($configfile) for user '$user\@$domain':\n", Data::Dumper->Dump([\@config], [qw(config)])); return (OK, @config) if @config; return DECLINED; }