#!/usr/local/bin/perl -w
###########################################################################
#
#      greylist.pl
#
# Copyright (c) 2003 Roman Festchook <roma@polesye.net>
# http://noc.polesye.net/greylist/
#
# Really quick and dirty perl script to implement Greylist antispam tech 
# (http://projects.puremagic.com/greylisting/) to the Exim.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License Version 2 from
# June 1991 as published by the Free Software Foundation.
#
# 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, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
###########################################################################
# ChangeLog:
#
# 26/06/03 first release
#
# 30.10.2003 v.0.01 by Victor Ustugov aka corvax
#
# реализована передача $sender_host_address, $sender_address, $local_part в виде параметров функции
# откорректированы сообщения об ошибках в функции
# откорректирована логика функции при обработке разного рода ошибок
# реализована передача в виде параметров критериев фильтров при вызове execute
# реализован вывод сообщений об ошибках в текстовый лог, syslog, лог файм exim'а или STDERROR
# добавлена обработка ситуации, когда record_expires уже настал, а запись не вычищена из таблицы
#
###########################################################################

use strict;

sub greylist() {
    use DBI;

    my($host, $sender_addr, $lp) = @_;

    my $block_period		= 55;		# minites
    my $record_lifetime_min	= 240;		# minites
    my $record_lifetime		= 36;		# days

    my $log_to		= 'exim_log';	# text_log | syslog | exim_log | stderr
    my $syslog_facility	= 'local4';

    # put your mysql connect options here
    my $myhost		= "myhost.com";
    my $mydb		= "mydb";
    my $myuser		= "myuser";
    my $mypasswd	= "mypass";

    my $return = 'fail';
    my $to_log;
    my $LOGFILE = "/var/log/isp";

    unless (my $isp= DBI->connect("DBI:mysql:$mydb:$myhost", $myuser, $mypasswd)) {
	$to_log = "Couldn't connect to host '$myhost'\n";
    } else {
	unless (my $sth = $isp->prepare(qq{
select
    id,
    UNIX_TIMESTAMP(block_expires)-UNIX_TIMESTAMP(),
    UNIX_TIMESTAMP(record_expires)-UNIX_TIMESTAMP()
from relaytofrom
where
	rcpt_to		= ?
    and mail_from	= ?
    and relay_ip	= ?
})) {
	    $to_log = "Couldn't prepare retrieving dates\n";
	} else {
	    unless ($sth->execute($lp, $sender_addr, $host)) {
	        $to_log = "Couldn't execute retrieving dates\n";
	    } else {
		my @status_array = $sth->fetchrow_array;
	        $sth->finish;

		my $id			= $status_array[$[];
		my $block_expires	= $status_array[$[+1];
		my $record_expires	= $status_array[$[+2];

		unless (@status_array) {
		# не было записи в БД
		    unless ($sth = $isp->prepare(qq{
insert into relaytofrom
(
    relay_ip, mail_from, rcpt_to,
    block_expires, record_expires,
    blocked_count, passed_count, aborted_count,
    origin_type,
    create_time, last_update
)
values (
    ?,
    ?,
    ?,
    DATE_ADD(NOW()+0, INTERVAL $block_period MINUTE),
    DATE_ADD(NOW()+0, INTERVAL $record_lifetime_min MINUTE),
    1, 0, 0,
    'AUTO',
    NOW()+0, NOW()+0
)
})) {
			$to_log = "Couldn't prepare inserting triplet\n";
		    } else {
			unless ($sth->execute($host, $sender_addr, $lp)) {
			    $to_log = "Couldn't execute inserting triplet [".$sth->errstr()."]\n";
			} else {
			    $return = 'yes';
			}
		    }
		} elsif ($record_expires<0) {
		# запись в БД существует, но record_expires уже просрочен
		    unless ($sth = $isp->prepare(qq{
update relaytofrom
set
    block_expires	= DATE_ADD(NOW()+0, INTERVAL $block_period MINUTE),
    record_expires	= DATE_ADD(NOW()+0, INTERVAL $record_lifetime_min MINUTE),
    blocked_count	= 1,
    passed_count	= 0,
    aborted_count	= 0,
    create_time		= NOW()+0,
    last_update		= NULL
where
    id			= ?
})) {
		        $to_log = "Couldn't prepare updating blocked_count\n";
		    } else {
		        unless ($sth->execute($id)) {
		    	    $to_log = "Couldn't execute updating blocked_count\n";
			} else {
			    $return = 'yes';
			}
		    }
		} elsif ($block_expires>0) {
		# запись в БД существует, но block_expires еще не настал
		    unless ($sth = $isp->prepare(qq{
update relaytofrom
set
    blocked_count	= blocked_count+1,
    last_update		= NOW()+0
where
    id			= ?
})) {
		        $to_log = "Couldn't prepare updating blocked_count\n";
		    } else {
		        unless ($sth->execute($id)) {
		    	    $to_log = "Couldn't execute updating blocked_count\n";
			} else {
			    $return = 'yes';
			}
		    }
	    	} else {
		# запись в БД существует, block_expires настал
		    unless ($sth = $isp->prepare(qq{
update relaytofrom
set
    passed_count	= passed_count+1,
    record_expires	= DATE_ADD(NOW()+0, INTERVAL $record_lifetime DAY),
    last_update		= NOW()+0
where
    id			= ?
})) {
		        $to_log = "Couldn't prepare updating passed_count\n";
		    } else {
		        unless ($sth->execute($id)) {
		    	    $to_log = "Couldn't execute updating passed_count\n";
			} else {
			    $return = 'no';
			}
		    }
		}
	    }
	}
	$isp->disconnect;
    }

    if ($to_log) {
        if ($log_to eq 'text_log') {
	    if (open(FILE, ">>$LOGFILE")) {
	        print FILE scalar(localtime).' '.$to_log;
    		close(FILE);
	    }
        } elsif ($log_to eq 'exim_log') {
	    die($to_log);
        } elsif ($log_to eq 'stderr') {
	    print(STDERR scalar(localtime).' '.$to_log);
        } elsif ($log_to eq 'syslog') {
	    use Sys::Syslog qw(:DEFAULT setlogsock);
	    openlog('exim_greylist', 'cons,pid', $syslog_facility);
	    syslog('notice', $to_log);
	    closelog();
	}
    }
    die("Temporary local problem - please try later\n") if ($return eq 'fail');
    return $return;
}
