#!/usr/bin/perl

# policyd-lemat
# version 1.1
# author: lemat at lemat.priv.pl
#
#    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 2 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.
#

# By default posfix is able to use maps where action is determined by one of the parameters: client, sender, recipient (and few other).
# This policyd returns defined action taking all of above into account. (map is based on 3 parameters)

# database preparation:

#CREATE TABLE IF NOT EXISTS `user_policy` (
#  `client` varchar(50) NOT NULL,
#  `sender` varchar(50) NOT NULL,
#  `recipient` varchar(50) NOT NULL,
#  `code` enum('OK','DUNNO','REJECT','BCC','DISCARD','HOLD','REDIRECT') NOT NULL,
#  `message` tinytext NOT NULL,
#  KEY `client` (`client`),
#  KEY `sender` (`sender`),
#  KEY `recipient` (`recipient`)
#) ENGINE=MyISAM DEFAULT CHARSET=utf8;

#INSERT INTO `user_policy` (`client`, `sender`, `recipient`, `code`, `message`) VALUES
#('*', 'tiscali.it', '*', 'REJECT', 'spam detected'),
#('*', 'freshmail.pl', '*', 'REJECT', 'freshmail to spamerzy'),
#('freshmail.pl', '*', '*', 'REJECT', 'freshmail to spamerzy'),
#('tiscali.it', 'tiscali.it', '*', 'DUNNO', ''),
#('yahoo.com', 'gmail.com', '*', 'REJECT', 'nigerian scam detected'),
#('*', 'yahoo.com.tw', '*', 'REJECT', 'please use yahoo.com mailservers'),
#('yahoo.com', 'yahoo.com', '*', 'DUNNO', ''),
#('yahoo.com', '*', '*', 'REJECT', 'nigerian scam detected'),
#('yahoo.com', 'yahoo.fr', '*', 'DUNNO', ''),
#('yahoo.com', 'yahoo.nl', '*', 'DUNNO', ''),
#('*', 'yahoo.com', '*', 'REJECT', 'nigerian scam detected'),
#('yahoo.com', 'yahoo.co.uk', '*', 'DUNNO', ''),
#('yahoo.com', 'yahoo.co.jp', '*', 'DUNNO', ''),
#('yahoo.com', 'yahoo.de', '*', 'DUNNO', ''),
#('yahoo.com', 'hotmail.com', '*', 'REJECT', 'nigerian scam detected'),
#('google.com', 'gmail.com', '*', 'DUNNO', ''),
#('*', 'gmail.com', '*', 'REJECT', 'please use google mailserver'),
#('*', 'yahoo.de', '*', 'REJECT', 'please use yahoo mailserver'),
#('*', 'yahoo.co.jp', '*', 'REJECT', 'please use yahoo mailserver'),
#('*', 'yahoo.co.uk', '*', 'REJECT', 'please use yahoo mailserver'),
#('*', 'yahoo.nl', '*', 'REJECT', 'please use yahoo mailserver'),
#('*', 'yahoo.fr', '*', 'REJECT', 'please use yahoo mailserver'),
#('yahoo.com', 'yahoo.com.tw', '*', 'DUNNO', ''),
#('booking.com', 'booking.com', '*', 'DUNNO', ''),
#('*', 'booking.com', '*', 'REJECT', 'please use booking.com mailservers'),
#('*', 'yahoo.com.hk', '*', 'REJECT', 'please use yahoo mailserver'),
#('yahoo.com', 'yahoo.com.hk', '*', 'DUNNO', ''),
#('*', 'yahoo.co.in', '*', 'REJECT', 'nigerian scam detected'),
#('yahoo.com', 'yahoo.co.in', '*', 'DUNNO', ''),
#('*', 'yahoo.ca', '*', 'REJECT', 'nigerian scam detected'),
#('yahoo.com', 'yahoo.pl', '*', 'DUNNO', ''),
#('yahoo.com', 'yahoo.ca', '*', 'DUNNO', ''),
#('mail.buildingboxsupport.com', 'visa.pl', '*', 'DUNNO', ''),
#('*', 'visa.pl', '*', 'REJECT', 'phishing'),
#('ukr.net', 'ukr.net', '*', 'DUNNO', ''),
#('*', 'ukr.net', '*', 'REJECT', 'please use ukr.net mailservers'),
#('yahoo.com', 'yahoo.cn', '*', 'DUNNO', ''),
#('*', 'yahoo.cn', '*', 'REJECT', 'please use yahoo.com mailservers'),
#('*', 'yahoo.pl', '*', 'REJECT', 'please use yahoo.com mailservers'),
#('aol.com', 'aol.com', '*', 'DUNNO', ''),
#('*', 'aol.com', '*', 'REJECT', 'nigerian scam detected'),
#('*', 'alice.it', '*', 'REJECT', 'please use alice.it mailservers'),
#('alice.it', 'alice.it', '*', 'DUNNO', ''),
#('vodafone.cz', 'vodafone.cz', '*', 'DUNNO', ''),
#('*', 'vodafone.cz', '*', 'REJECT', 'please use vodafone.cz mailservers'),
#('google.com', 'google.com', '*', 'DUNNO', ''),
#('*', 'google.com', '*', 'REJECT', 'please use google mailserver'),
#('t-mobile.pl', 't-mobile.pl', '*', 'DUNNO', ''),
#('*', 't-mobile.pl', '*', 'REJECT', 'please use t-mobile.pl mailservers'),
#('vodafone.de', 'vodafone.de', '*', 'DUNNO', ''),
#('*', 'vodafone.de', '*', 'REJECT', 'please use vodafone.de mailservers'),
#('*', 'yahoo.co.kr', '*', 'REJECT', 'please use yahoo mailserver'),
#('yahoo.com', 'yahoo.co.kr', '*', 'DUNNO', ''),
#('aexp.com', 'aexp.com', '*', 'DUNNO', ''),
#('*', 'aexp.com', '*', 'REJECT', 'please use aexp.com mailservers'),
#('yahoo.com', 'yahoo.it', '*', 'DUNNO', ''),
#('*', 'yahoo.it', '*', 'REJECT', 'please use yahoo mailserver'),
#('yahoo.com', 'yahoo.es', '*', 'DUNNO', ''),

# ALTER TABLE `user_policy` ADD `id` INT UNSIGNED NOT NULL AUTO_INCREMENT FIRST, ADD PRIMARY KEY (`id`);

# ALTER TABLE `user_policy` ADD UNIQUE `triple` (`client`, `sender`, `recipient`);

# master.cf

# policyd-lemat3 unix - n n - - spawn user=nobody argv=/usr/local/bin/policyd-lemat3

# main.cf

# smtpd_recipient_restrictions =
#        ...
#        reject_unauth_destination,
#        ...
#        check_policy_service unix:private/policyd-lemat3,
#        ...

use version; our $VERSION = qv('1.0');

use strict;

use IO::Handle;
use Sys::Syslog qw(:DEFAULT setlogsock);
use DBI;

my $VERBOSE = 1;

my $DEFAULT_RESPONSE = 'DUNNO default';
my $ERROR_RESPONSE = 'DEFER_IF_PERMIT server configuration error';

#
# Syslogging options for verbose mode and for fatal errors.
# NOTE: comment out the $syslog_socktype line if syslogging does not
# work on your system.
#

my $syslog_socktype = 'unix'; # inet, unix, stream, console
my $syslog_facility = 'mail';
my $syslog_options  = 'pid';
my $syslog_ident    = 'postfix/policyd-lemat';

# ----------------------------------------------------------
#                      initialization
# ----------------------------------------------------------

#
# Log an error and abort.
#
sub fatal_exit {
    syslog(err     => "fatal_exit: @_");
    syslog(warning => "fatal_exit: @_");
    syslog(info    => "fatal_exit: @_");
    die("fatal: @_");
}

#
# Unbuffer standard output.
#
STDOUT->autoflush(1);

#
# This process runs as a daemon, so it can't log to a terminal. Use
# syslog so that people can actually see our messages.
#
setlogsock($syslog_socktype);
openlog($syslog_ident, $syslog_options, $syslog_facility);

my $dbh = DBI->connect('DBI:mysql:postfix', 'postfix', 'postfix', { PrintError => 0 } );
if (!$dbh) {
        syslog(err => "Could not connect to database: $DBI::errstr");
        STDOUT->print("action=$ERROR_RESPONSE\n\n");
        }
else {
        my %attr;
        while (<STDIN>) {
            chomp;
    
            if (/=/) {
                my ($key, $value) =split (/=/, $_, 2);
                $attr{$key} = $value;
                next;
                }
            elsif (length) {
                syslog(warning => sprintf("warning: ignoring garbage: %.100s", $_));
                next;
                }
    
            if ($VERBOSE) {
                for (sort keys %attr) {
                        syslog(debug => "Attribute: %s=%s", $_, $attr{$_});
                        }
                }
    
            my $action = do_query(%attr);
        
            syslog(info => "%s: Policy action=%s", $attr{queue_id}, $action);
    
            STDOUT->print("action=$action\n\n");
            %attr = ();
        }

        $dbh->disconnect();
        }


sub do_query {

my %attr = @_;
my $sender=$attr{'sender'};
my $recipient=$attr{'recipient'};
my $client=$attr{'client_name'};
my($user,$sdomain)=split (/@/, $sender, 2);
my($user,$rdomain)=split (/@/, $recipient, 2);

#foreach (keys %attr) {
#    print $_ . " : " . $attr{$_} . "\n";
#    }
               
my $sth = $dbh->prepare("SELECT code,message FROM user_policy
    WHERE (sender=? OR sender=? OR ? LIKE CONCAT('%.',sender) OR ? LIKE CONCAT('%\@',sender) OR sender='*')
    AND   (recipient=? OR recipient=? OR ? LIKE CONCAT('%.',recipient) OR ? LIKE CONCAT('%\@',recipient) OR recipient='*')
    AND   (client=? OR ? LIKE CONCAT('%.',client) OR client='*')
    ORDER BY LENGTH(recipient) DESC,LENGTH(sender) DESC,LENGTH(client) DESC LIMIT 1");
if (!$sth->execute($sender, $sdomain, $sdomain, $sender, $recipient, $rdomain, $rdomain, $recipient, $client, $client)) {
        syslog(err => "Couldn't execute statement: " . $sth->errstr);
        return $ERROR_RESPONSE;
        }

if ( my $result = $sth->fetchrow_hashref() ) {
        if (length($result->{message})>0 ) {
                $result->{message}=' '.$result->{message};
                }
        return $result->{code}.$result->{message};
        }

# Attention! "lemat_limit" is from policyd-lemat2, if you don't have this remove lines from here ->
my $sth = $dbh->prepare("SELECT 'OK' as message FROM lemat_limit WHERE recipient=? AND username=? LIMIT 1");
if (!$sth->execute($sender, $recipient)) {
        syslog(err => "Couldn't execute statementx: " . $sth->errstr);
        return $ERROR_RESPONSE;
        }
if ( my $result = $sth->fetchrow_hashref() ) {
#       syslog(info => "%s: Policy action=%s %s %s", $attr{queue_id}, $action, $sender, $recipient);
        return $result->{message};
        }
# <- up to there

return $DEFAULT_RESPONSE;
}
