#!/usr/bin/perl

# Author: tomekk
# e-mail:  tomekk/@/tomekk/./org
# home page: http://tomekk.org/
#
# Version 0.4
#
# This file is Copyrighted under the GNU Public License.
# http://www.gnu.org/copyleft/gpl.html
##########################################################

# fixed: couple code fixes
# changed: move action removed, now we have copy action
# changed: script logic and table structure
# added: nosave action
# removed: external config file - we don't need to parse config file for couple static vars (nonsense?)

# some uses
use DBI;
use utf8;
use strict;
use File::Copy;
use Mail::Message;
use String::Random;
use Encode qw(decode);
use Mail::Mailer qw(sendmail);

# config
# user/domain data comes directly from postfix to the ssqllda.pl script
# $USER - this macro expands to the username part of a recipient address [...]
# $DOMAIN - this macro expands to the domain portion of the recipient address [...]
# $ORIGINAL -  this macro expands to the complete recipient address before any address rewriting or aliasing [...]
# for more information: 'man 8 pipe' (postfix pipe command)

# example
# new e-mail for yoda@starwars.com
# VUSER_MAILDIR main dir: /postfix/vusers/$DOMAIN/$USER
# full path will be: /postfix/vusers/starwars.com/yoda/new/email.file (email.file = 'timestamp.random_string')
my $maildir = '/postfix/vusers/$DOMAIN/$USER';

# database name
my $sql_db = 'postfix_lda';

# ssqllda table
my $sql_table = 'main';

# database user
my $sql_user = 'myuser';

# database user pass
my $sql_pass = 'mypass';

# database host
my $sql_host = 'localhost';

# database port
my $sql_port = 3306;

##########################################################
# check number of input args
if (($#ARGV + 1) < 4)
{
        die "more arguments neeed, check your master.cf";
}

# data from postfix
# trim, lower input args
$ARGV[0] = trim(lc($ARGV[0])); # ${user}
$ARGV[1] = trim(lc($ARGV[1])); # ${domain}
$ARGV[2] = trim(lc($ARGV[2])); # ${sender}
$ARGV[3] = trim(lc($ARGV[3])); # ${original_recipient}

# farm of vars
my ($ts, $tn, $rstr, $mail_file, $match);
my ($dbh, $select, $table, $result, $move_to_default, $header_mark, $temp_header);
my ($mail, $mail_headers, $mail_obj, $mail_obj_subject);
my ($sub_to, $sub_from, $sub_subject, $sub_message);

# get some data for the new file name
$ts = time;
$rstr = new String::Random;
$tn = $rstr->randpattern("CcCcCcCnCnCnCnCnCcCcCnCcCcC");

# assign proper data to the string
$maildir =~ s/\$USER/$ARGV[0]/g;
$maildir =~ s/\$DOMAIN/$ARGV[1]/g;
$maildir =~ s/\$ORIGINAL/$ARGV[3]/g;

# make random file name
$mail_file = $ts . "." . $tn;

# read STDIN and write to tmp file and catch the header on-the-fly
$temp_header = "";
$header_mark = 1;
open (MAIL,">" . $maildir . "/tmp/" . $mail_file) or die "cannot create temp mail file - maybe wrong file path?";
while (<STDIN>)
{
	# look for first 'empty' line
	if ($_ =~ m/^\s*$/)
	{
		$header_mark = 0;
	}
	
	# if still header part
	if ($header_mark)
	{
		$temp_header .= $_;
	}

	# anyway, put data into tmp file
	print MAIL $_;
}
close (MAIL);

# connect to DB
$dbh = DBI->connect("DBI:mysql:database=" . $sql_db . ";host=" . $sql_host . ";port=" . $sql_port, $sql_user, $sql_pass, {'PrintError' => 0}) or die "cannot select database: $DBI::errstr";

# prepare and execute SQL statements
# force 'names' charset
$dbh->do("SET NAMES UTF8");

# select records
$select = $dbh->prepare("SELECT `type`, `header`, `pattern`, `action`, `message`, `subject`, `maildir` FROM `" . $sql_table . "` WHERE `email`='" . $ARGV[0] . "@" . $ARGV[1] . "' ORDER BY `order` ASC") or die "cannot prepare sql statement";
$table = $select->execute();

# move mail to the default dir fe. when no match, no email or proper action is found
$move_to_default = 1;

# check if data with filter rules for the given user@domain exists
if($table)
{
	# prepare mail header obj
	$mail_obj = Mail::Message->read($temp_header);                                                                                                                     
	$mail_obj_subject = lc(decode("MIME-Header", $mail_obj->head->get("Subject")));

	# if exists, try to get all records
	while ($result = $select->fetchrow_hashref())
	{
		# defaults
		$match = 0;

		# trim and lower SQL data
		$result->{'type'} = trim(lc($result->{'type'}));
		$result->{'header'} = trim(lc($result->{'header'}));
		$result->{'pattern'} = trim(lc($result->{'pattern'}));
		$result->{'action'} = trim(lc($result->{'action'}));
		$result->{'message'} = trim($result->{'message'});
		$result->{'subject'} = trim($result->{'subject'});
		$result->{'maildir'} = trim($result->{'maildir'});
		
		# check if mail matches filter rule
		if ($result->{'type'} eq "sender")
		{
			if ($ARGV[2] =~ m/$result->{'pattern'}/)
			{
				$match = 1;
			}
		} elsif ($result->{'type'} eq "subject") {
			
			if (utf8::is_utf8($mail_obj_subject))
			{
				utf8::encode($mail_obj_subject);
			}

			if ($mail_obj_subject =~ m/$result->{'pattern'}/)
			{
				$match = 1;
			} 
		} elsif ($result->{'type'} eq "header") {
			if (lc($mail_obj->head->get($result->{'header'})) =~ m/$result->{'pattern'}/)
			{
				$match = 1;
			}
		}

		# we have match
		if ($match)
		{
			# copy action
			if ($result->{'action'} eq "copy")
			{	
				# if 'maildir' is not empty
				if ($result->{'maildir'})
				{
					# do copy
					copy ($maildir . "/tmp/" . $mail_file, $result->{'maildir'} . "/" . $mail_file) or die "tmp mail file cannot be copied: $!";
				}
			}
	
			# respond action
			if ($result->{'action'} eq "respond")
			{
				# if pattern matches, send autorespond from the ${original_recipient} address
				send_msg ($ARGV[2], $ARGV[3], $result->{'message'}, $result->{'subject'});
			}
	
			# do not save email in default maildir
			if ($result->{'action'} eq "nosave")
			{
				$move_to_default = 0;
			}
		}
	}
}

# move it, move it! - to default maildir
if ($move_to_default)
{
	move ($maildir . "/tmp/" . $mail_file, $maildir . "/new/" . $mail_file) or die "tmp mail file cannot be moved: $!";
}

# finish the SQL exe
$select->finish();

# end DB connection
$dbh->disconnect();

# we need to remove tmp file (for example when tmp file was copied not moved)
# don't need to check if file exists before unlink
unlink ($maildir . "/tmp/" . $mail_file);

# sent msg via sendmail
sub send_msg 
{
	($sub_to, $sub_from, $sub_message, $sub_subject) = @_;

	$mail = Mail::Mailer->new('sendmail');
	$mail_headers = { 'Content-Type' => 'text/plain; charset=UTF-8', To => $sub_to, From => $sub_from, Subject => $sub_subject };
	
	$mail->open($mail_headers);
	$mail->print($sub_message);
	$mail->close();
}

# c'mon PERL, trim!
sub trim
{
	my $trim = shift;
	$trim =~ s/^\s+//;
	$trim =~ s/\s+$//;
	return $trim;
}
