#!/usr/bin/perl
#############################################################################
# receivegrey 080519vl1@2008 ohtan #
# This program is free software; you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation; either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see . #
#############################################################################
use Fcntl;
use Sys::Syslog qw(:DEFAULT setlogsock);
use DB_File;
use strict;
my $SYSLOG_SOCKTYPE = 'unix';
my $SYSLOG_FACILITY = 'mail';
my $SYSLOG_OPTIONS = 'pid';
my $SYSLOG_IDENT = 'postfix/policy-service';
my $RESULT_NG = '450 In order to receive mail, please try again later';
my $RESULT_OK = 'DUNNO';
my %OPTION;
$OPTION{DEBUG} = 0;
$OPTION{TYPE} = 1;
$OPTION{COUNT} = 2;
$OPTION{BEGIN} = 5;
$OPTION{END} = 120;
$OPTION{DBFILE} = '/tmp/receivegrey.db';
foreach (@ARGV) {
if (/^--([^\-]+)=([^\=]+)$/) {
$OPTION{$1} = $2;
}
}
my %ATTR = ();
setlogsock $SYSLOG_SOCKTYPE;
openlog($SYSLOG_IDENT,$SYSLOG_OPTIONS,$SYSLOG_FACILITY);
select((select(STDOUT), $| = 1)[0]);
while () {
if (/([^=]+)=([^\n]*)/) {
$ATTR{$1} = $2;
} elsif ($_ eq "\n") {
if ($OPTION{DEBUG}) {
for (keys %ATTR) {
syslog("info","attribute: %s=%s",$_,$ATTR{$_});
}
}
if ($ATTR{"request"} ne "smtpd_access_policy") {
fatal_exit("unrecognized request type: $ATTR{request}");
}
my $ACTION = smtpd_access_policy();
syslog("info","action: %s",$ACTION) if $OPTION{DEBUG};
print STDOUT "action=$ACTION\n\n";
%ATTR = ();
} else {
chomp;
syslog("warning","ignoring garbage: %.100s", $_);
}
}
exit(0);
sub smtpd_access_policy
{
my $CLIENT = $ATTR{"client_address"};
my $FROM = $ATTR{"sender"};
my $RCPT = $ATTR{"recipient"};
my $RESULT = $RESULT_NG;
my $SEARCH = $CLIENT . '[]' . $FROM . '[]' . $RCPT;
syslog("info","search key: %s",$SEARCH) if $OPTION{DEBUG};
$DB_BTREE->{'flags'} = R_DUP;
my $X = tie(my %HASH,"DB_File","$OPTION{DBFILE}",O_RDWR|O_CREAT,0644,$DB_BTREE)
or fatal_exit("can't open $OPTION{DBFILE}");
my $KEY;
my $VAL;
for (my $STATUS = $X->seq($KEY,$VAL,R_FIRST); $STATUS == 0; $STATUS = $X->seq($KEY,$VAL,R_NEXT)) {
if ($VAL < time() - (60 * $OPTION{END})) {
$X->del_dup($KEY,$VAL);
syslog("info","delete: %s=%s",$KEY,$VAL) if $OPTION{DEBUG};
}
}
my @MATCH = $X->get_dup($SEARCH);
my $KYNUM = @MATCH;
syslog("info","count key: %s",$KYNUM) if $OPTION{DEBUG};
if (!$KYNUM) {
$HASH{$SEARCH} = time();
$RESULT = $RESULT_NG;
} else {
my $SUB_RES = 0;
foreach (@MATCH) {
if ($_ < time() - (60 * $OPTION{BEGIN})) {
$SUB_RES++;
last;
}
}
if ($SUB_RES) {
if ($KYNUM < $OPTION{COUNT}) {
$HASH{$SEARCH} = time();
$RESULT = $RESULT_NG;
} else {
delete $HASH{$SEARCH} if $OPTION{TYPE};
$RESULT = $RESULT_OK;
}
}
}
untie %HASH;
syslog("info","client:%s from:%s to:%s result:%s",$CLIENT,$FROM,$RCPT,$RESULT) if $OPTION{DEBUG};
return $RESULT;
}
sub fatal_exit
{
my $ERR = shift;
syslog("err","fatal: %s",$ERR);
die("fatal: $ERR\n");
}