#!/usr/bin/perl
# Version 2
# /usr/local/psa/admin/sbin/mailmng --add-handler --handler-name custom_grey --handler-type global --executable /opt/psa/user_handlers/custom_greylisting --context 'greylisting' --hook before-queue --priority 10
#  perl -MCPAN -e 'install Net::CIDR' 

use DBI;
use Net::CIDR;

# settings
$db_dbi   = 'DBI:mysql:psa';
$db_user  = 'BENUTZER';
$db_pass  = 'PASSWORD';
$db_table = 'custom_greylist';

$grey_timeout   = 110;
$grey_cidr      = 24;
$grey_enable    = true;

# Beginne Script
$pattern = '(^|\n)Received: from (.+) \(([\d\.]+)\)($|\n)';

# hole E-Mail Infos und Content
$context        = shift;
$mail_from      = shift;
$mail_to        = shift;
$mail_content   = undef;
$mail_received  = undef;

while (<STDIN>) {
  $mail_content .= $_;
}

# grep ip
if ($mail_content =~ m/$pattern/) {
  $mail_received = $3;
}

# Mail durchlassen
sub pass_mail {
  print STDERR "PASS\n";
  print STDOUT "$mail_content";
  exit;
}

# Mail ablehnen (temporaer)
sub defer_mail {
  print STDERR "DEFER\n";
  exit;
}

# Mail ablehnen (fest)
sub deny_mail {
  print STDERR "REJECT\n";
  exit;
}

# Log in Maillog
sub log_mail {
  $text = shift;
  print STDERR "LOG $text\n";
}

# CIDR berechnen
sub ipcidr {
  $ip = shift;
  $cidr = shift;

  @iprange = Net::CIDR::cidr2octets($ip."/".$cidr);
  $firstip = shift(@iprange);
  return $firstip;
}

# Fehler bei Verbindung
sub err_connect {
  &log_mail("Fehler bei der Datenbank verbindung");
  &pass_mail();
}

# einfache wildcards zu perl regex
sub glob2pat {
  my $globstr = shift;
  my %patmap = (
    '*' => '.*',
    '?' => '.',
  );
  $globstr =~ s{(.)} { $patmap{$1} || "\Q$1" }ge;
  return '^' . $globstr . '$';
}

# mail alias zu haupt-name
sub alias2mail {
  $mailname = shift;
  $mailname =~ m/^(.*)@(.*)$/;
  $mailuser = $1;
  $maildomain = $2;

  $aliassql = $db_con->prepare("select mail.mail_name, domains.name, NOW() FROM mail_aliases, mail, domains LEFT JOIN domainaliases ON domains.id = domainaliases.dom_id WHERE mail_aliases.mn_id = mail.id AND mail.dom_id = domains.id AND mail_aliases.alias = ? AND (domains.name = ? OR domainaliases.name = ?)");
  $aliassql->execute($mailuser,$maildomain,$maildomain);
  @aliasres = $aliassql->fetchrow_array();
  $aliassql->finish();

  if ($aliasres[2] > 0) {
    return $aliasres[0]."@".$aliasres[1];
  }
  return $mailname;
}

# Pruefe ob Authorisiert, ohne RECEIVED-FROM oder ob grey disabled
if ($ENV['SMTP_AUTHORIZED'] eq "yes") {
  &log_mail("SMTP Authentifiziert");
  &pass_mail;
}
if ($mail_received eq undef) {
  &log_mail("Lokal - Ohne RECEIVED-FROM");
  &pass_mail;
}
if ($mail_received =~ m/^127\./ || $mail_received eq 'localhost' || $mail_received eq '::1') {
  &pass_mail;
}
if ($grey_enable ne true) {
  &log_mail("Greylist nicht aktiviert");
  &pass_mail;
}

# IP setzen und berechnen
$mail_originalip = $mail_received;
$mail_ip = &ipcidr($mail_originalip,$grey_cidr);

# Connect zur DB
$db_con = DBI->connect($db_dbi,$db_user,$db_pass) || &err_connect();

# Hole den richtigen Mailnamen
$mail_alias = &alias2mail($mail_to);

# Whitelist fuer IPs
$poplocks = $db_con->prepare("SELECT ip_address, ip_mask FROM smtp_poplocks");
$poplocks->execute();
while (@popres = $poplocks->fetchrow_array()) {
  $popip = $popres[0]."/".$popres[1];
  @list = ("$popip");
  if (Net::CIDR::cidrlookup($mail_originalip,@list)) {
    &log_mail("IP in globaler Whitelist");
    &pass_mail;
  }
}
$poplocks->finish();

# Whitelist und Blacklist fuer Mailadressen
$whitemail = $db_con->prepare("SELECT spamfilter_preferences.preference AS preforig, spamfilter_preferences.value AS valorig FROM spamfilter_preferences, spamfilter WHERE (spamfilter.username = ? OR spamfilter.username = ?) AND spamfilter.id = spamfilter_preferences.spamfilter_id AND (spamfilter_preferences.preference = ? OR spamfilter_preferences.preference = ?) AND (SELECT COUNT(*) FROM spamfilter_preferences, spamfilter WHERE preference = CONCAT('un',preforig) AND value = valorig AND spamfilter_id = id AND username = ?) = 0");
$whitemail->execute($mail_alias,'*@*','whitelist_from','blacklist_from',$mail_alias);
while (@whitemail_result = $whitemail->fetchrow_array()) {
  $whiteglob = $whitemail_result[1];
  $whitepreg = glob2pat($whiteglob);
  if ($mail_from =~ m/$whitepreg/) {
    if ($whitemail_result[0] eq 'blacklist_from') {
      &log_mail("E-Mail steht auf Blacklist ($whiteglob)");
      &deny_mail;
    } else {
      &log_mail("E-Mail steht auf Whitelist ($whiteglob)");
      &pass_mail;
    }
  }
}
$whitemail->finish();



# SQL-Qry um zu sehen ob der Datensatz schon existiert
$fq = $db_con->prepare("SELECT UNIX_TIMESTAMP(GreylistFreigabe), UNIX_TIMESTAMP() FROM ".$db_table." WHERE GreylistVon = ? AND GreylistAn = ? AND GreylistIP = ?");
$fq->execute($mail_from,$mail_to,$mail_ip);
@fr = $fq->fetchrow_array();
$fq->finish();

$now = $fr[1];
$free = $fr[0];

if ($now > 0) {
  if ($free > $now) {
    # Mail ist noch nicht freigegeben, ablehnen
    &defer_mail;
  } else {
    # Mail ist freigegeben, akzeptieren und zaehlen
    $sq = $db_con->prepare("UPDATE ".$db_table." SET GreylistUpdate = NOW(), GreylistCount = GreylistCount+1 WHERE GreylistVon = ? AND GreylistAn = ? AND GreylistIP = ?");
    $sq->execute($mail_from,$mail_to,$mail_ip);
    $sq->finish();
    &pass_mail;
  }
} else {
  # Neuer versuch, erstmal ablehnen
  $sq = $db_con->prepare("INSERT INTO ".$db_table." VALUES ( ?, ?, ?, NOW(), NOW(), FROM_UNIXTIME(UNIX_TIMESTAMP()+".$grey_timeout."), 0 )");
  $sq->execute($mail_from,$mail_to,$mail_ip);
  $sq->finish();
  &defer_mail;
}

# Beendet - falls irgendwass schief ging, erlauben
&pass_mail;
