#!/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"); }