I have similar problem with yours. Pre-queue requirement left us to three option
Because we are only checking recipient, then SMTP access policy delegation would be suffice. Milter and SMTPD Proxy was used if we need access to header and body too.
My checker script was provided by developer. The program usage was only calling ./myscript recipient_address
which recipient_address by postfix. To do that, I use modified version of perl script. This perl script allows me to read stdin, split string per line and split the line with equal =
sign.
Then I declare spawn daemon in master.cf
policy unix - n n - - spawn user=mydedicateduser argv=/path/to/perl/script.pl
Then define it in main.cf
smtpd_recipient_restrictions =
...
reject_unauth_destination
check_policy_service unix:private/policy
...
This script below can be substituted by another programming language. Here the pseudo-code to explain how it works.
while True:
get the stdin
if stdin = '' # ---> the end of parameter
call the external script with recipient parameter
get the return code
if recipient valid
print "dunno"
else if recipient not valid
print "reject"
else # ---> command error
print "defer if permit"
break
else
validate the stdin, the proper format is 'parameter=value'
if valid
put it in array
For python code, you can modify code from this snippet. No need to set spawn daemon.
For the completeness, here the full source code of /path/to/perl/script.pl
#!/usr/bin/perl -w
use strict;
use Sys::Syslog qw(:DEFAULT setlogsock);
use Getopt::Long;
#
# Syslogging options for verbose mode and for fatal errors.
# NOTE: comment out the $syslog_socktype line if syslogging does not
# work on your system.
#
my $syslog_socktype = 'unix'; # inet, unix, stream, console
my $syslog_facility = 'mail';
my $syslog_options = 'pid';
my $syslog_priority = 'info';
# Configuration
my $executable_path = '/my/execute/script.sh';
#
# Procedures
#
#
# Log an error and abort.
#
sub fatal_exit {
my $first = shift @_;
syslog "err", "fatal: $first", @_;
exit 1;
}
# SMTPD access policy routine. The result is an action just like
# it would be specified on the right-hand side of a Postfix access
# table. Request attributes are available via the %attr hash.
sub smtpd_access_policy() {
system($executable_path, $attr{'recipient'});
# -1 command error
# 0 user isn't valid
# else user valud
if ( $? == -1 ) {
# command error
return "defer_if_permit Something error"
}
if ( $? == 0 ) {
return "reject user not found";
}
return "dunno";
}
#
# Main program
#
#
# This process runs as a daemon, so it can't log to a terminal. Use
# syslog so that people can actually see our messages.
#
setlogsock $syslog_socktype;
openlog $0, $syslog_options, $syslog_facility;
unless(GetOptions('v:+' => \$verbose)) {
syslog $syslog_priority, "Invalid option. Usage: %s [-v] [-v] ...", $0;
exit 1;
}
#
# Unbuffer standard output.
#
select((select(STDOUT), $| = 1)[0]);
#
# Receive a bunch of attributes, evaluate the policy, send the result.
#
while (<STDIN>) {
chomp;
if (/^([^=]+)=(.*)$/) {
$attr{substr($1, 0, 512)} = substr($2, 0, 512);
} elsif ($_ eq '') {
if ($verbose>2) {
for (keys %attr) {
syslog $syslog_priority, "Attribute: %s=%s", $_, $attr{$_};
}
}
fatal_exit "unrecognized request type: '%s'", $attr{'request'}
unless $attr{'request'} eq 'smtpd_access_policy';
my $action = smtpd_access_policy();
syslog $syslog_priority, "Action: %s", $action if $verbose>1;
print STDOUT "action=$action\n\n";
%attr = ();
} else {
syslog $syslog_priority, "warning: ignoring garbage: %.100s", $_;
}
}
exit 0;