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
  • 17,978
  • 9
  • 56
  • 104
Nils Toedtmann
  • 3,202
  • 5
  • 25
  • 36
  • I've never used a script. But you can do a lot with the reg-exp (pcre) version of the db tables. e.g. `/^(?:prvs=[\da-f]{10}=)?abuse@foobarbaz.net$/ OK` That example is for a back-scatter preventor that prefixed outgoing addresses with a MD5 hex pattern. Obviously it doesn't check the actual value of the hex-code (in this case the machine was 2ndary smtp). Would that help? See also: http://serverfault.com/questions/258469/how-to-configure-postfix-to-pipe-all-incoming-email-to-a-script – ericx Aug 08 '14 at 14:48
  • Thx erics, but it doesn't help my use case. I do need to actually check not just format but expiry date and checksum. I know that i could pipe incoming mails to a script, but that would mean to accept incoming mails first and bounce later when found illegitimate, potentially producing backscatter. My aim is to let postfix reject straightaway if the address test fails. – Nils Toedtmann Aug 08 '14 at 15:19
  • Not sure about "that would mean to accept incoming mails first and bounce later when found illegitimate". `amavisd` for example, is attached as an in-line on incoming `smtp`: `smtp inet n - n - - smtpd -o content_filter=smtp-amavis:localhost:10024 ` It is clever enough to hold the connection open. If it has a problem, `postfix smtp` blocks incoming. – ericx Aug 08 '14 at 15:29
  • 1
    You just need to use the before-queue milter, http://www.postfix.org/MILTER_README.html – NickW Aug 08 '14 at 15:30
  • @ericx I hoped to get away with something significantly simpler than writing a Amavis plugin or a SMTP-daemon. (BTW "content_filter" is after-queue, one needs "smtpd_proxy_filter" to do it before-queue) – Nils Toedtmann Aug 08 '14 at 15:42
  • @NickW Thanks i will look into milter. I hope it's not too hard to plug-in a tiny shell script. – Nils Toedtmann Aug 08 '14 at 15:44
  • 3
    Maybe the simplest solution would be to use 'check_policy_service' and to implement a little policy service (like the ones used for greylisting or SPF). – Nils Toedtmann Aug 08 '14 at 15:51

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
  • 17,978
  • 9
  • 56
  • 104