📜 ⬆️ ⬇️

External rules of access to Postfix on the example of the front-end to GLD

It so happened that in the organization where I work, Postfix is ​​used as the mail server. In conjunction with it, tools are used to filter spam and viruses Spamassassin, Amavisd-new and ClamAV. In addition to all this, greylisting is implemented using GLD . The latter is easy to set up and is “easy” to work with, but one drawback follows from this - it is not flexible enough. In order to overcome this, I turned my attention to an interesting feature of Postfix - Postfix SMTP Access Policy Delegation . Information on the great and mighty on this topic is not enough. Who cares how to add their own checks before transferring data to GLD, or how to implement their “external” rules in Postfix using favorite or just familiar languages ​​and means - I ask for cat.


Introductory


It all started with the fact that some of the users began to complain more strongly about the severity of the anti-spam filter. They demanded that all checks be turned off, arguing that they are ready to receive all spam just to not lose a single letter. Our admin case, it is necessary so necessary. And here it turned out that in amavisd-new this is implemented simply, but in GLD there is no such possibility. There is a whitelist, but it is based on the sender’s data, I didn’t understand how it works with the recipient’s addresses, because I still wanted to keep one whitelist in one place and not support two at once. And the tasks can change and for the future I still needed some kind of layer between Postfix and GLD so that I could realize everything I wanted in it.

Theory


Remembering how GLD works (via a network socket, listening on port 2525), I studied the check_policy_service mechanism and this is what happens. Postfix transfers SMTP session data to the address specified in the directive. If you don’t want to write your own daemon listening socket, Postfix has a tool - the spawn daemon that works like inetd. He listens to the necessary sockets and transfers everything he receives to the specified transport.
')
The data has the format name = value one per line, the end of the data packet is indicated by an empty string. The answer must consist of a single line of the form action = value followed by an empty line. action can accept both standard for Postfix-ovsky lists OK or REJECT , and DUNNO which means to continue checking with other filters, this work is finished or DEFER_IF_PERMIT Some text ... which will lead to the rejection of the letter with code 450 and the answer Some text ...
According to official documentation, the data received by STDIN transport are as follows:

Postfix version 2.1 and later: request=smtpd_access_policy protocol_state=RCPT protocol_name=SMTP helo_name=some.domain.tld queue_id=8045F2AB23 sender=foo@bar.tld recipient=bar@foo.tld recipient_count=0 client_address=1.2.3.4 client_name=another.domain.tld reverse_client_name=another.domain.tld instance=123.456.7 Postfix version 2.2 and later: sasl_method=plain sasl_username=you sasl_sender= size=12345 ccert_subject=solaris9.porcupine.org ccert_issuer=Wietse+20Venema ccert_fingerprint=C2:9D:F4:87:71:73:73:D9:18:E7:C2:F3:C1:DA:6E:04 Postfix version 2.3 and later: encryption_protocol=TLSv1/SSLv3 encryption_cipher=DHE-RSA-AES256-SHA encryption_keysize=256 etrn_domain= Postfix version 2.5 and later: stress= Postfix version 2.9 and later: ccert_pubkey_fingerprint=68:B3:29:DA:98:93:E3:40:99:C7:D8:AD:5C:B9:C9:40 [empty line] 


To business


So, we arm with the tool. I personally like Perl. If you like and know how to read other people's perl sorts, you can close the tab with this article and proceed to the study of greylist.pl from the list of examples supplied with Postfix. Moreover, he, as befits a competent example, is written clearly, with indents and comments. We are going to configure Postfix.

Postfix

Everything is done according to the covenants of. documentation. First, edit master.cf by adding our new transport to its end:

 # Greylist policy daemon filter gld unix - nn - 0 spawn user=nobody argv=/home/bender/scripts/gld.pl 

This is just a description, in order to enable this transport we write the following in main.cf:

 smtpd_recipient_restrictions = ... reject_unauth_destination, check_policy_service unix:private/gld gld_time_limit = 3600 

Please note that reject_unauth_destination should be BEFORE your check_policy_service .

Perl

First, I will give the skeleton of the script, based on it you, having read this far, can do everything you need.

 #!/usr/bin/perl $dump = ''; $defaultAction = 'DUNNO'; #   . select((select(STDOUT), $| = 1)[0]); ##################################################################### #   ##################################################################### while (<STDIN>) { if ($_ eq "\n") { #  ,  if (meetSomeReq($dump)) { #  ,  DUNNO print STDOUT "action=$defaultAction\n\n"; } else { #   print STDOUT "action=DEFER_IF_PERMIT Service temporary unavailable\n\n"; } $dump = ''; } else { #    $dump .= $_; } } ##################################################################### #  ##################################################################### sub meetSomeReq { my $dump = shift(); my $line = ''; my %param = (); my $result = 1; #  ,   Postfix   foreach $line (split(/\n/, $dump)) { chomp($line); my ($key, $val) = split(/=/, $line); $param{$key} = $val; } #  -       $result   return $result; } 


Result


Now for those who are not interested in the topic in general, but it is my case (mediation between Postfix and GLD or the like) that I give the full text of the resulting (and really working) script:

 #!/usr/bin/perl use IO::Socket; use DBI; my $dbh = DBI->connect("DBI:mysql:host=localhost;database=amavisd", "amavisadmin", "amavisadminpw") or die "Couldn't connect to server !$ \n"; $dump = ''; $defaultAction = 'DUNNO'; # Unbuffer standard output. select((select(STDOUT), $| = 1)[0]); ##################################################################### # Main loop ##################################################################### while (<STDIN>) { if ($_ eq "\n") { if (inWhiteList($dump)) { print STDOUT "action=$defaultAction\n\n"; } else { print STDOUT passToGLD($dump); } $dump = ''; } else { $dump .= $_; } } $dbh->disconnect(); ##################################################################### # # Subs # ##################################################################### sub passToGLD { my $dump = shift(); $dump .= "\n\n"; my $sock = new IO::Socket::INET( PeerAddr => '127.0.0.1', PeerPort => '2525', Proto => 'tcp', ); die "Could not create socket: $!\n" unless $sock; print $sock $dump; $resp = <$sock>; close($sock); return $resp."\n"; } sub inWhiteList { my $dump = shift(); my $line = ''; my %param = (); my $result = 1; my $maxSize = 65536; # # Convert text dump to hash # foreach $line (split(/\n/, $dump)) { chomp($line); my ($key, $val) = split(/=/, $line); $param{$key} = $val; } # # Check user's policy # if ($param{'size'} < $maxSize) # Pass large mails without check { my ($user, $domain) = split(/@/, $param{'recipient'}); my $qry = "SELECT count(email) FROM users WHERE policy_id='3' AND (email=? OR email=?)"; my $sth = $dbh->prepare($qry); $sth->execute($param{'recipient'}, '@'.$domain); my @row = $sth->fetchrow_array(); $sth->finish(); $result = $row[0]; } return $result; } 


Conclusion


Only in the process of writing this article did I start thinking about giving up GLD and developing the script as his deputy. Although GLD is written in C, it still uses MySQL to store data, so the performance gain is not that big. Well, the latest update in May 2006 suggests that the project is not quite alive. Is anyone besides me using it?

In general, waiting for comments. And may those who dislike long texts forgive me! I myself am so.

Source: https://habr.com/ru/post/153463/


All Articles