1

I would like postfix to consult a simple (bash/python) script in order to find out (virtual_alias_maps - style) before-queue whether or not a recipient address it legitimate. E.g. i'd like to use mail addresses like

$USER-lottery.com-$EXIPRYDATE-$CHECKSUM@example.com

to either get accepted for $USER if $EXIPRYDATE has not yet passed and if $CHECKSUM passes, or to get rejected otherwise.


If it simplifies the solution, i'd be ok with doing this for a single, constant user, so postfix would only need to observe True/False from some address check script.


I am not interested in solutions that require postfix to accept a mail first and then bounce later if the recipient address is found invalid ("after-queue"). I need postfix to reject during the initial SMTP session ("before-queue").

masegaloeh
  • 18,498
Nils Toedtmann
  • 3,572
  • 5
  • 30
  • 36

1 Answers1

2

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;
masegaloeh
  • 18,498