#!/usr/bin/perl
#
#  LMS version 1.11.7 Bastet
#
#  Copyright (C) 2001-2009 LMS Developers
#
#  Please, see the doc/AUTHORS for more information about authors!
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License Version 2 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.
#
#  $Id: lms-rtparser,v 1.80 2009/01/13 07:45:28 alec Exp $

use strict;
use DBI;
use Config::IniFiles;
use Getopt::Long;
use vars qw($configfile $help $version $queue $debug);
use POSIX qw(strftime);
use File::Copy;
use MIME::Parser;
use MIME::Words qw(:all);
use MIME::QuotedPrint;
use Sys::Hostname;
use Text::Iconv;
use Mail::Sender;

$ENV{'PATH'}='/sbin:/usr/sbin:/usr/local/sbin:/bin:/usr/bin:/usr/local/bin';

sub basename($) { my @tmp = split '/',$_[0]; return pop @tmp }

my $_version = '1.11.7 Bastet';

my %options = (
	"--config-file|C=s"     =>      \$configfile,
	"--help|h"              =>      \$help,
	"--version|v"           =>      \$version,
	"--debug|d"             =>      \$debug,
	"--queue|q=s"		=>	\$queue
);

Getopt::Long::config("no_ignore_case");
GetOptions(%options);

if($help)
{
	print STDERR <<EOF;
lms-rtparser, version $_version
(C) 2001-2009 LMS Developers

-C, --config-file=/etc/lms/lms.ini
		alternate config file (default: /etc/lms/lms.ini);
-h, --help	print this help and exit;
-v, --version	print version info and exit;
-d, --debug	print out debug information, do not log any messages into
		system;
-q, --queue	queue ID (it means, QUEUE ID, numeric! NOT NAME! also
		it's required to run!)
				
EOF
	exit 0;
}							

if($version)
{
	print STDERR <<EOF;
lms-rtparser, version $_version
(C) 2001-2009 LMS Developers

EOF
	exit 0;
}

if(!$configfile)
{
	$configfile = "/etc/lms/lms.ini";
}

my $hostname = hostname || 'example.com';

my $ini = new Config::IniFiles -file => $configfile;
print @Config::IniFiles::errors;

my $dbtype = $ini->val('database', 'type') || 'mysql';
my $dbhost = $ini->val('database', 'host') || 'localhost';
my $dbuser = $ini->val('database', 'user') || 'root';
my $dbpasswd = $ini->val('database', 'password') || '';
my $dbname = $ini->val('database', 'database') || 'lms';

my $smtpserver = $ini->val('rt', 'smtp_server') || 'localhost'; # backward compatibility
my $smtp_host = $ini->val('rt', 'smtp_host') || $smtpserver;
my $smtp_user = $ini->val('rt', 'smtp_user') || '';
my $smtp_pass = $ini->val('rt', 'smtp_pass') || '';
my $smtp_auth = $ini->val('rt', 'smtp_auth') || ''; # 'LOGIN', 'PLAIN', 'CRAM-MD5', 'NTLM'

my $save_path = $ini->val('rt', 'mail_dir') || '';
my $queue = $queue || $ini->val('rt', 'default_queue');
my $auto_open = $ini->val('rt', 'auto_open') || 0;
my $tmp_dir = $ini->val('rt', 'tmp_dir') || '';
my $notify = $ini->val('rt', 'newticket_notify') || 0;
my $customerinfo = $ini->val('rt', 'include_customerinfo') || 1;
my $lms_url = $ini->val('rt', 'lms_url') || 'http://localhost/lms/';
my $autoreply_from = $ini->val('rt', 'mail_from') || '';
my $autoreply_name = $ini->val('rt', 'mail_from_name') || '';
my $autoreply_subject = $ini->val('rt', 'autoreply_subject') || "[RT#%tid] Receipt of request '%subject'";
my $autoreply_body = $ini->val('rt', 'autoreply_body') || '';
my $autoreply = $ini->val('rt', 'autoreply') || 1;

if($smtp_auth && $smtp_auth !~ /LOGIN|PLAIN|CRAM-MD5|NTLM/i)
{
	print STDERR "Fatal error: smtp_auth setting not supported! Can't continue, exiting.\n";
	exit 1;
}

if(! $autoreply_body)
{
	$autoreply_body = "Your request was registered in our system.\n".
		"To this request was assigned ticket identifier: RT#%tid\n\n".
		"Please, place string [RT#%tid] in subject field of any\n".
		"mail relating to this request.\n"
}
$autoreply_body =~ s/\\n/\n/g;

# very ugly code, but we really need tempdir..
# TODO - FIXME - better handling of temporary dir - we should use sth simillar to mktemp();
my $tmpdir = $tmp_dir || ((defined $ENV{'TMP'} && ! ($ENV{'TMP'} eq '')) ? $ENV{'TMP'} : '/tmp');

my $parser = new MIME::Parser;

$parser->decode_headers(1);
$parser->extract_uuencode(1);
$parser->output_dir($tmpdir);

my $entity = $parser->parse(\*STDIN);

my $headers = $entity->head;
my $mh_from = $headers->get('From');
my $mh_to = $headers->get('To');
my $mh_cc = $headers->get('Cc');
my $mh_msgid = $headers->get('Message-ID');
my $mh_replyto = $headers->get('Reply-To') || '';
my $mh_subject = $headers->get('Subject');
my $mh_references = $headers->get('References');
my $mailheaders = $headers->as_string;
my $body;
my $mailbody;
my @attachments;

# Taken from pl.comp.lang.perl FAQ - http://www.kt.agh.edu.pl/other/perl/faq/5.html#6

if($entity->mime_type =~ m|multipart/|i) # if we deal with multipart message...
{
	for(my $partnum = 0; $partnum < $entity->parts; $partnum++)
	{
		$body = $entity->parts($partnum);
		
		if($body->mime_type =~ m/text/i && $mailbody eq '') 
		{
			# text message
			my $tmp_body = $body->as_string;
			$mailbody = $body->bodyhandle->as_string;
			if($tmp_body =~ m|Content-Transfer-Encoding: quoted-printable|i)
			{
				$mailbody = decode_qp($mailbody);
			}
		}
		elsif(($body->mime_type =~ m/multipart\/alternative/i) && $mailbody eq '')
		{
			# Outlook's nasty multipart/alternative message
			my $newbody = join " ", @{$body->body};
			my @newbody = split /------=_NextPart_\S*\n/, $newbody;
			$newbody = $newbody[1]; # text message part
			my $tmp_body = $newbody;
			# now we need to remove leading headers from body
			$newbody =~ s/Content-Type.*Content-Transfer-Encoding.*?\n+//is; 
			$mailbody = $newbody;
			if($tmp_body =~ m|Content-Transfer-Encoding: quoted-printable|i)
			{
				$mailbody = decode_qp($mailbody);
			}
		}
		else
		{
			# push entity index into table with attachements
			push @attachments, $partnum;
		}
	}
}
else
{
	$body = $entity->bodyhandle;
	$mailbody = $body->as_string;
}

chomp $mh_from;
chomp $mh_to;
chomp $mh_cc;
chomp $mh_subject;
chomp $mh_msgid;
chomp $mailbody;
chomp $mailheaders;
chomp $mh_replyto;
chomp $mh_references;

if($mh_subject eq '')
{
	$mh_subject = '(no subject)';
}

my $mail_mh_subject = $mh_subject;
my $orig_mailbody = $mailbody;

my $dbase;
my $utsfmt;
my $custname;

if($dbtype =~ /mysql/)
{
	$dbase = DBI->connect("DBI:mysql:database=$dbname;host=$dbhost","$dbuser","$dbpasswd", { RaiseError => 1 });
	$dbase->do("SET NAMES utf8");
	$utsfmt = "UNIX_TIMESTAMP()";
	$custname = "CONCAT(UPPER(lastname), ' ', name) AS customername";
}
elsif($dbtype eq "postgres")
{
	$dbase = DBI->connect("DBI:Pg:dbname=$dbname;host=$dbhost","$dbuser","$dbpasswd", { RaiseError => 1 });
	$utsfmt = "EXTRACT(EPOCH FROM CURRENT_TIMESTAMP(0))";
	$custname = "UPPER(lastname) || ' ' || name AS customername";
}
else
{
	print STDERR "Fatal error: unsupported database type: $dbtype, exiting.\n";
	exit 1;
}

# for security, fetch timestamp from database.
my $dbq = $dbase->prepare("SELECT $utsfmt AS timestamp");
$dbq->execute();
my $row = $dbq->fetchrow_hashref();
my $timestamp = $row->{'timestamp'};

# before we create new ticket try to find references...
# no because: somebody would like to make new request while replying 
# with new subject (without ticketdid)

my $prev_tid = 0;
my $inreplytoid = 0;
my @reftab = split(' ', $mh_references);
my $lastref = $reftab[scalar(@reftab)-1];

# check 'References'
if($lastref)
{
	$dbq = $dbase->prepare("SELECT id, ticketid FROM rtmessages WHERE messageid = ?");
	$dbq->execute($lastref);
	if($row = $dbq->fetchrow_hashref())
	{
		$prev_tid = $row->{'ticketid'};
		$inreplytoid = $row->{'id'};
	}
}

# check email subject
if( !$prev_tid && ($mh_subject =~ /RT#[0-9]{6,}/) )
{
	$prev_tid = $mh_subject;
	$prev_tid =~ s/.*RT#([0-9]{6,}).*/\1/;
	$prev_tid = sprintf('%d',$prev_tid);
	
	$dbq = $dbase->prepare("SELECT id FROM rttickets WHERE id = $prev_tid");
	$dbq->execute();
	if(!($row = $dbq->fetchrow_hashref()))
	{
		$prev_tid = 0;
	}
}

# let's convert message to proper encoding (if needed)
if(my $tmp_body = $entity->parts(0)) #if multipart message, read encoding from first part, not from headers
{
	$_ = $tmp_body->as_string;
} else {
	$_ = $mailheaders;
}
if(/charset\=\"([a-zA-Z0-9-]*)\"/i || /charset\=([a-zA-Z0-9-]*)/i )
{
	my $charset = uc($1);

	if($charset ne 'UTF-8')
	{
		my $converter = Text::Iconv->new($charset, 'UTF-8');
		$mailbody = $converter->convert($mailbody);
		$orig_mailbody = $converter->convert($orig_mailbody);
		$mailheaders = $converter->convert($mailheaders);
		$mh_subject = $converter->convert($mh_subject);
		$mh_from = $converter->convert($mh_from);
		$mh_replyto = $converter->convert($mh_replyto);
		$mail_mh_subject = $converter->convert($mail_mh_subject);
	}
}

my $ticket_id;
my $ticketid;
my $rt_msgid;
my $reqcustid = 0;
my $requserid = 0;
my $replytoname = $mh_replyto;
$replytoname =~ s/^(.*)<.+\@.+>$/\1/g;
my $replytoemail = $mh_replyto;
$replytoemail =~ s/^.*<(.+\@.+)>$/\1/g;
my $fromname = $mh_from;
$fromname =~ s/^(.*)<.+\@.+>$/\1/g;
my $fromemail = $mh_from;
$fromemail =~ s/^.*<(.+\@.+)>$/\1/g;
my $toemail = $mh_to;
$toemail =~ s/^.*<(.+\@.+)>$/\1/g;
my $toemail2 = $mh_cc;
$toemail2 =~ s/^.*<(.+\@.+)>$/\1/g;

# find queue ID if not specified
if(!$queue)
{
	$dbq = $dbase->prepare("SELECT id FROM rtqueues WHERE email = ? OR email = ?");
	$dbq->execute($toemail, $toemail2);
	if(my $row = $dbq->fetchrow_hashref())
	{
		$queue = $row->{'id'};
	}
}

if(!$queue)
{
	print STDERR "Fatal error: Queue ID not found, exiting.\n";
	exit 1;
}

# find customerid
$dbq = $dbase->prepare("SELECT id FROM customers WHERE email = ?");
$dbq->execute($fromemail);
if(my $row = $dbq->fetchrow_hashref())
{
	$reqcustid = $row->{'id'};
}

# get sender e-mail if not specified
if(!$autoreply_from)
{
	$dbq = $dbase->prepare("SELECT email, name FROM rtqueues WHERE id = $queue");
	$dbq->execute();
	if(my $row = $dbq->fetchrow_hashref())
	{
		$autoreply_from = $row->{'email'};
		$autoreply_name = $autoreply_name || $row->{'name'};
	}
}

if(! $prev_tid) # generate new ticket if previous not found
{
	$dbq = $dbase->prepare("INSERT INTO rttickets (queueid, requestor, customerid, subject, createtime) 
			VALUES ($queue, ?, $reqcustid, ?, $timestamp)");
	$dbq->execute($mh_from, $mh_subject);

	$dbq = $dbase->prepare("SELECT id FROM rttickets WHERE queueid = $queue 
			AND requestor = ? AND subject = ? AND createtime = $timestamp");
	$dbq->execute($mh_from, $mh_subject);
	$row = $dbq->fetchrow_hashref();
	$ticket_id = $row->{'id'};

	$dbq = $dbase->prepare("INSERT INTO rtmessages (ticketid, mailfrom, replyto, 
			customerid, subject, messageid, headers, body, createtime) 
			VALUES ($ticket_id, ?, ?, $reqcustid, ?, ?, ?, ?, $timestamp)");
	$dbq->execute($mh_from, $mh_replyto, $mh_subject, $mh_msgid, $mailheaders, $mailbody);

	# i think that this expr should be enought to fetch id of previously inserted message
	$dbq = $dbase->prepare("SELECT id FROM rtmessages WHERE ticketid = $ticket_id 
			AND messageid = ? AND createtime = $timestamp");
	$dbq->execute($mh_msgid);
	$row = $dbq->fetchrow_hashref();
	$rt_msgid = $row->{'id'};	

	$ticketid = sprintf("%06d", $ticket_id);
	$autoreply_subject =~ s/\%tid/$ticketid/g;
	$autoreply_subject =~ s/\%subject/$mail_mh_subject/g;
	$autoreply_body =~ s/\%tid/$ticketid/g;
	$autoreply_body =~ s/\%subject/$mail_mh_subject/g;

	# encode confirmation headers
	my $mailto;
	my $c_subject = encode_mimeword($autoreply_subject,'q','UTF-8');
	$autoreply_name = encode_mimeword($autoreply_name,'q','UTF-8');
	$replytoname = encode_mimeword($replytoname,'q','UTF-8');
	$fromname = encode_mimeword($fromname,'q','UTF-8');

	if($autoreply)
	{
		if($replytoemail) {
			$mailto = "$replytoname <$replytoemail>";
		} else {
			$mailto = "$fromname <$fromemail>";
		}

		$Mail::Sender::NO_X_MAILER = 1;
		$Mail::Sender::SITE_HEADERS = "X-Mailer: LMS/RT v.$_version";

	        (new Mail::Sender)->MailMsg ({
		        smtp => $smtp_host,
			from => $autoreply_from,
			auth => $smtp_auth,
			authid => $smtp_user,
			authpwd => $smtp_pass,
			on_errors => undef,
#			debug_level => 4,
#			debug => './log.txt',
			subject => $c_subject,
			fake_from => "\"$autoreply_name\" <$autoreply_from>",
			to => $replytoemail || $fromemail,
			fake_to => $mailto,
			encoding => '8bit',
			charset => 'UTF-8',
			messageid => "<confirm.$ticket_id.$queue.$timestamp\@rtsystem.$hostname>",
			headers => 
				"References: $mh_references $mh_msgid\n"
				."In-Reply-To: $mh_msgid",
			msg => "$autoreply_body\n",
		});	
	}

}
else
{
	# find userid
	$dbq = $dbase->prepare("SELECT id FROM users WHERE email = ? AND email!=''");
	$dbq->execute($fromemail);
	if(my $row = $dbq->fetchrow_hashref())
	{
		$requserid = $row->{'id'};
	}

	# find customerid
	$dbq = $dbase->prepare("SELECT id FROM customers WHERE email = ? AND email!=''");
	$dbq->execute($fromemail);
	if(my $row = $dbq->fetchrow_hashref())
	{
		$reqcustid = $row->{'id'};
	}

	$dbq = $dbase->prepare("INSERT INTO rtmessages (ticketid, mailfrom, customerid, 
			userid, subject, messageid, replyto, headers, body, inreplyto, createtime) 
			VALUES ($prev_tid, ?, $reqcustid, $requserid, ?, ?, ?, ?, ?, $inreplytoid, $timestamp)");
	$dbq->execute($mh_from, $mh_subject, $mh_msgid, $mh_replyto, $mailheaders, $mailbody);
	$dbq = $dbase->prepare("SELECT id FROM rtmessages WHERE ticketid = $prev_tid 
			AND messageid = ? AND createtime = $timestamp");
	$dbq->execute($mh_msgid);
	$row = $dbq->fetchrow_hashref();
	$rt_msgid = $row->{'id'};
	
	if($auto_open)
	{
		$dbq = $dbase->prepare("UPDATE rttickets SET state=1 WHERE id=$prev_tid AND state>1");
		$dbq->execute();
	}

	$ticket_id = $prev_tid;
	$ticketid = sprintf("%06d", $ticket_id);
}

if($notify)
{
	my $body = "$mailbody\n\n$lms_url/?m=rtticketview&id=$ticket_id";

	# find users emails
	$dbq = $dbase->prepare("SELECT name, email FROM users, rtrights 
			WHERE users.id = userid AND queueid = $queue 
			AND email != '' AND (rtrights.rights & 8) = 8");
	$dbq->execute();

	if($dbq->rows() && $reqcustid && $customerinfo)
	{
		my $dbqc = $dbase->prepare("SELECT id, $custname, email, address, zip, city,
				    (SELECT phone FROM customercontacts WHERE customerid = customers.id 
					    ORDER BY id LIMIT 1) AS phone
				    FROM customers WHERE id = $reqcustid");
		$dbqc->execute();
		if(my $customer = $dbqc->fetchrow_hashref())
		{
			$body .= "\n\n-- \n";
			$body .= "$customer->{'customername'}\n";
			$body .= "$customer->{'address'}, $customer->{'zip'} $customer->{'city'}\n";
			$body .= "$customer->{'phone'}\n";
			$body .= "$customer->{'email'}\n";
		}
		$dbqc->finish();
	}

	while(my $row = $dbq->fetchrow_hashref())
	{
		(new Mail::Sender)->MailMsg ({
		        smtp => $smtp_host,
			from => $autoreply_from,
			auth => $smtp_auth,
			authid => $smtp_user,
			authpwd => $smtp_pass,
			on_errors => undef,
#			debug_level => 4,
#			debug => './log.txt',
			subject => "[RT#$ticketid] $mail_mh_subject",
			fake_from => "\"$autoreply_name\" <$autoreply_from>",
			to => $row->{'email'},
			fake_to => "\"$row->{'name'}\" <$row->{'email'}>",
			encoding => '8bit',
			charset => 'UTF-8',
			headers => 
				"References: $mh_references $mh_msgid\n"
				."In-Reply-To: $mh_msgid",
			msg => "$body\n",
		});
	}
}

if(scalar @attachments && $save_path)
{
	mkdir($save_path.'/'.sprintf('%06d',$ticket_id));
	mkdir($save_path.'/'.sprintf('%06d',$ticket_id).'/'.sprintf('%06d',$rt_msgid));
	foreach my $partnum (@attachments)
	{
		$body = $entity->parts($partnum);
		
		# skip part without attachement ie. multipart/alternative
		if(!$body->bodyhandle) { next; } 
		
		my $filename = $body->bodyhandle->path;
		my $content_type = $body->mime_type;
		
		# handle spaces and unknown characters in filename 
		# on systems having problems with that
		$filename =~ s/[^\w\.-_]/_/g; 
		rename($body->bodyhandle->path, $filename);
		move($filename, $save_path.'/'.sprintf('%06d',$ticket_id).'/'.sprintf('%06d',$rt_msgid));
		
		$dbq = $dbase->prepare("INSERT INTO rtattachments (messageid, filename, 
				contenttype) VALUES ($rt_msgid, '".basename($filename)."', '$content_type')");
		$dbq->execute();
	}
}

$parser->filer->purge;	# cleanup
$dbq->finish();
$dbase->disconnect();
