#!/usr/bin/perl
#
#  LMS version 1.8.12 Tagan
#
#  Copyright (C) 2001-2006 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.59.2.4 2006/01/16 09:35:17 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 Net::SMTP;
use Sys::Hostname;
use Text::Iconv;

$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.8.12 Tagan';

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-2006 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-2006 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 $dbencoding = $ini->val('database', 'server_encoding') || 'UTF-8';

my $save_path = $ini->val('rt', 'mail_dir') || '';
my $queue = $queue || $ini->val('rt', 'default_queue');
my $smtpserver = $ini->val('rt', 'smtp_server') || 'localhost';
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 $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') || '';

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"
}

# very ugly code, but we really need tempdir..
my $tmpdir = $tmp_dir || ((defined $ENV{'TMP'} && ! ($ENV{'TMP'} eq '')) ? $ENV{'TMP'} : '/tmp');

my $parser = new MIME::Parser;

# TODO - FIXME - better handling of temporary dir - we should use sth simillar to mktemp();

$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;
$mailbody =~ s/\\/\\\\/g;
$mailbody =~ s/\'/\\\'/g;
$mailheaders =~ s/\\/\\\\/g;
$mailheaders =~ s/\'/\\\'/g;
$mh_from =~ s/\\/\\\\/g;
$mh_from =~ s/\'/\\\'/g;
$mh_to =~ s/\\/\\\\/g;
$mh_to =~ s/\'/\\\'/g;
$mh_cc =~ s/\\/\\\\/g;
$mh_cc =~ s/\'/\\\'/g;
$mh_msgid =~ s/\\/\\\\/g;
$mh_msgid =~ s/\'/\\\'/g;
$mh_subject =~ s/\\/\\\\/g;
$mh_subject =~ s/\'/\\\'/g;
$mh_replyto =~ s/\\/\\\\/g;
$mh_replyto =~ s/\'/\\\'/g;
$mh_references =~ s/\\/\\\\/g;
$mh_references =~ s/\'/\\\'/g;

my $dbase;
my $utsfmt;

if($dbtype eq "mysql")
{
	$dbase = DBI->connect("DBI:mysql:database=$dbname;host=$dbhost","$dbuser","$dbpasswd", { RaiseError => 1 });
	$utsfmt = "UNIX_TIMESTAMP()";
}
elsif($dbtype eq "postgres")
{
	$dbase = DBI->connect("DBI:Pg:dbname=$dbname;host=$dbhost","$dbuser","$dbpasswd", { RaiseError => 1 });
	$utsfmt = "EXTRACT(EPOCH FROM CURRENT_TIMESTAMP(0))";
}
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 = '$lastref'");
	$dbq->execute();
	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-]*)\"/ || /charset\=([a-zA-Z0-9-]*)/)
{
	my $charset = uc($1);
	if(uc($dbencoding) == 'UNICODE')
	{
		$dbencoding = 'UTF-8';
	}
	if($charset ne $dbencoding)
	{
		my $converter = Text::Iconv->new($charset, $dbencoding);
		$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 $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;

if(! $prev_tid) # generate new ticket if previous not found
{
	# find queue ID if not specified
	if(!$queue)
	{
		$dbq = $dbase->prepare("SELECT id FROM rtqueues WHERE email = '$toemail' OR email = '$toemail2'");
		$dbq->execute();
		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 = '$fromemail'");
	$dbq->execute();
	if(my $row = $dbq->fetchrow_hashref())
	{
		$reqcustid = $row->{'id'};
	}

	$dbq = $dbase->prepare("INSERT INTO rttickets (queueid, requestor, customerid, subject, createtime) VALUES ($queue, '$mh_from', $reqcustid, '$mh_subject', $timestamp)");
	$dbq->execute();

	$dbq = $dbase->prepare("SELECT id FROM rttickets WHERE queueid = $queue AND requestor = '$mh_from' AND subject = '$mh_subject' AND createtime = $timestamp");
	$dbq->execute();
	$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, '$mh_from', '$mh_replyto', $reqcustid, '$mh_subject', '$mh_msgid', '$mailheaders', '$mailbody', $timestamp)");
	$dbq->execute();

	# 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 = '$mh_msgid' AND createtime = $timestamp");
	$dbq->execute();
	$row = $dbq->fetchrow_hashref();
	$rt_msgid = $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'};
		}
	}
	
	my $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 $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');
	
	my $smtp = Net::SMTP->new($smtpserver);
	$smtp->mail("$autoreply_from");
	$smtp->to($replytoemail || $fromemail);
	$smtp->data();
	if($replytoemail) {
		$smtp->datasend("To: $replytoname <$replytoemail>\n");
	} else {
		$smtp->datasend("To: $fromname <$fromemail>\n");
	}
	$smtp->datasend("From: \"$autoreply_name\" <$autoreply_from>\n");
	$smtp->datasend("Mime-Version: 1.0\n");
	$smtp->datasend("Content-type: text/plain; charset=UTF-8\n");
	$smtp->datasend("Content-transfer-encoding: 8bit\n");
	$smtp->datasend("Subject: $c_subject\n");
	$smtp->datasend("References: $mh_references $mh_msgid\n");
	$smtp->datasend("Message-ID: <confirm.$ticket_id.$queue.$timestamp\@rtsystem.$hostname>\n");
	$smtp->datasend("In-Reply-To: $mh_msgid\n");
	$smtp->datasend("X-Mailer: LMS/RT v.$_version\n");
	$smtp->datasend("\n$autoreply_body\n");
	$smtp->dataend();
	$smtp->quit;	
	
	if($notify)
	{
		# find users emails
		$dbq = $dbase->prepare("SELECT name, email FROM users, rtrights WHERE users.id=userid AND queueid = $queue AND email!='' AND rtrights.rights>2");
		$dbq->execute();
		while(my $row = $dbq->fetchrow_hashref())
		{
			my $smtp = Net::SMTP->new($smtpserver);
			$smtp->mail("$autoreply_from");
			$smtp->to($row->{'email'});
			$smtp->data();
			$smtp->datasend("To: \"$row->{'name'}\" <$row->{'email'}>\n");
			$smtp->datasend("From: \"$autoreply_name\" <$autoreply_from>\n");
			$smtp->datasend("Mime-Version: 1.0\n");
			$smtp->datasend("Content-type: text/plain; charset=UTF-8\n");
			$smtp->datasend("Content-transfer-encoding: 8bit\n");
			$smtp->datasend("Subject: [RT#$ticketid] $mail_mh_subject\n");
			$smtp->datasend("X-Mailer: LMS/RT v.$_version\n");
			$smtp->datasend("\n");
			$smtp->datasend("$lms_url/?m=rtticketview&id=$ticket_id\n\n");
			$smtp->datasend("$orig_mailbody");
			$smtp->dataend();
			$smtp->quit;	
		}
	}
}
else
{
	# find userid
	$dbq = $dbase->prepare("SELECT id FROM users WHERE email = '$fromemail' AND email!=''");
	$dbq->execute();
	if(my $row = $dbq->fetchrow_hashref())
	{
		$requserid = $row->{'id'};
	}

	# find customerid
	$dbq = $dbase->prepare("SELECT id FROM customers WHERE email = '$fromemail' AND email!=''");
	$dbq->execute();
	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, '$mh_from', $reqcustid, $requserid, '$mh_subject', '$mh_msgid', '$mh_replyto', '$mailheaders', '$mailbody', $inreplytoid, $timestamp)");
	$dbq->execute();
	$dbq = $dbase->prepare("SELECT id FROM rtmessages WHERE ticketid = $prev_tid AND messageid = '$mh_msgid' AND createtime = $timestamp");
	$dbq->execute();
	$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;
}

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();
