# -*- Perl -*-
#***********************************************************************
#
# mimedefang-filter
#
# Suggested minimum-protection filter for Microsoft Windows clients, plus
# SpamAssassin checks if SpamAssassin is installed.
#
# Copyright (C) 2002 Roaring Penguin Software Inc.
#
# This program may be distributed under the terms of the GNU General
# Public License, Version 2, or (at your option) any later version.
#
# $Id$
#***********************************************************************

#***********************************************************************
# Set administrator's e-mail address here.  The administrator receives
# quarantine messages and is listed as the contact for site-wide
# MIMEDefang policy.  A good example would be 'defang-admin@mydomain.com'
#***********************************************************************
$AdminAddress = 'postmaster@localhost';
$AdminName = "MIMEDefang Administrator's Full Name";

#***********************************************************************
# Set the e-mail address from which MIMEDefang quarantine warnings and
# user notifications appear to come.  A good example would be
# 'mimedefang@mydomain.com'.  Make sure to have an alias for this
# address if you want replies to it to work.
#***********************************************************************
$DaemonAddress = 'mimedefang@localhost';

#***********************************************************************
# If you set $AddWarningsInline to 1, then MIMEDefang tries *very* hard
# to add warnings directly in the message body (text or html) rather
# than adding a separate "WARNING.TXT" MIME part.  If the message
# has no text or html part, then a separate MIME part is still used.
#***********************************************************************
$AddWarningsInline = 0;

#***********************************************************************
# To enable syslogging of virus and spam activity, add the following
# to the filter:
# md_graphdefang_log_enable();
# You may optionally provide a syslogging facility by passing an
# argument such as:  md_graphdefang_log_enable('local4');  If you do this, be
# sure to setup the new syslog facility (probably in /etc/syslog.conf).
# An optional second argument causes a line of output to be produced
# for each recipient (if it is 1), or only a single summary line
# for all recipients (if it is 0.)  The default is 1.
# Comment this line out to disable logging.
#***********************************************************************
md_graphdefang_log_enable('mail', 1);

#***********************************************************************
# Uncomment this to block messages with more than 50 parts.  This will
# *NOT* work unless you're using Roaring Penguin's patched version
# of MIME tools, version MIME-tools-5.411a-RP-Patched-02 or later.
#
# WARNING: DO NOT SET THIS VARIABLE unless you're using at least
# MIME-tools-5.411a-RP-Patched-02; otherwise, your filter will fail.
#***********************************************************************
# $MaxMIMEParts = 50;

#***********************************************************************
# Set various stupid things your mail client does below.
#***********************************************************************

# Set the next one if your mail client cannot handle multiple "inline"
# parts.
$Stupidity{"NoMultipleInlines"} = 0;

# Detect and load Perl modules
detect_and_load_perl_modules();

#***********************************************************************
#
# Synology block
#
#***********************************************************************
use JSON;
use Encode;
use MIME::Base64;
use MailPlusServer::Disclaimer;
use utf8;

sub read_json_config {
	my($fname) = @_;
	my $fh;
	my $config = {};
	my $json_str;

	eval {
		open $fh, '<', $fname or die "failed to open file $fname: $!";
		$json_str = do { local $/; <$fh> };
		$config = JSON::decode_json($json_str);
	} or do {
		md_syslog('err', "Failed to load config: $@");
	};
	close $fh if $fh;

	return $config;
}


my $SynoConfigFile = '/var/packages/MailPlus-Server/target/etc/mimedefang/mimedefang.cf';
my $SynoConfig = read_json_config($SynoConfigFile);

my $MPSDisclaimer = MailPlusServer::Disclaimer->new($SynoConfig);


#***********************************************************************
# %PROCEDURE: filter_begin
# %ARGUMENTS:
#  $entity -- the parsed MIME::Entity
# %RETURNS:
#  Nothing
# %DESCRIPTION:
#  Called just before e-mail parts are processed
#***********************************************************************
sub filter_begin {
	my($entity) = @_;
}

#***********************************************************************
# %PROCEDURE: filter
# %ARGUMENTS:
#  entity -- a Mime::Entity object (see MIME-tools documentation for details)
#  fname -- the suggested filename, taken from the MIME Content-Disposition:
#           header.  If no filename was suggested, then fname is ""
#  ext -- the file extension (everything from the last period in the name
#         to the end of the name, including the period.)
#  type -- the MIME type, taken from the Content-Type: header.
#
#  NOTE: There are two likely and one unlikely place for a filename to
#  appear in a MIME message:  In Content-Disposition: filename, in
#  Content-Type: name, and in Content-Description.  If you are paranoid,
#  you will use the re_match and re_match_ext functions, which return true
#  if ANY of these possibilities match.  re_match checks the whole name;
#  re_match_ext checks the extension.  See the sample filter below for usage.
# %RETURNS:
#  Nothing
# %DESCRIPTION:
#  This function is called once for each part of a MIME message.
#  There are many action_*() routines which can decide the fate
#  of each part; see the mimedefang-filter man page.
#***********************************************************************
sub filter {
	my($entity, $fname, $ext, $type) = @_;

	return if message_rejected(); # Avoid unnecessary work

	return action_accept();
}

#***********************************************************************
# %PROCEDURE: filter_multipart
# %ARGUMENTS:
#  entity -- a Mime::Entity object (see MIME-tools documentation for details)
#  fname -- the suggested filename, taken from the MIME Content-Disposition:
#           header.  If no filename was suggested, then fname is ""
#  ext -- the file extension (everything from the last period in the name
#         to the end of the name, including the period.)
#  type -- the MIME type, taken from the Content-Type: header.
# %RETURNS:
#  Nothing
# %DESCRIPTION:
#  This is called for multipart "container" parts such as message/rfc822.
#  You cannot replace the body (because multipart parts have no body),
#  but you should check for bad filenames.
#***********************************************************************
sub filter_multipart {
	my($entity, $fname, $ext, $type) = @_;

	return if message_rejected(); # Avoid unnecessary work

	return action_accept();
}


#***********************************************************************
# %PROCEDURE: defang_warning
# %ARGUMENTS:
#  oldfname -- the old file name of an attachment
#  fname -- the new "defanged" name
# %RETURNS:
#  A warning message
# %DESCRIPTION:
#  This function customizes the warning message when an attachment
#  is defanged.
#***********************************************************************
sub defang_warning {
	my($oldfname, $fname) = @_;
	return
		"An attachment named '$oldfname' was converted to '$fname'.\n" .
		"To recover the file, right-click on the attachment and Save As\n" .
		"'$oldfname'\n";
}

sub extract_attached_mails {
	my($entity) = @_;
	my $fh;
	my $attached_mail = '';
	my @parts = $entity->parts;
	my @mails = ();

	foreach my $p (@parts) {
		my $header = $p->head();
		my $filename = Encode::decode('MIME-Header', $header->recommended_filename);
		my $is_from_mailplus = ($filename =~ /^syno_report_mail_/);
		if ($p->mime_type eq "message/rfc822" || $is_from_mailplus) {
			open $fh, '>', \$attached_mail or md_syslog('err', "failed to open variable: $!");
			$p->print_body($fh);
			close($fh);

			if ($is_from_mailplus) {
				$attached_mail = decode_base64($attached_mail);
			}

			push(@mails, $attached_mail);
		}
	}

	return \@mails;
}

sub report_mail_check {
	my $is_report_mail = 0;
	my $report_account = '';
	my $report_address = '';

	if (!$SynoConfig->{spam_report_enable}) {
		return ($is_report_mail, $report_account, $report_address);
	}

	for my $Recipient (@Recipients) {
		$Recipient =~ s/^<//;
		$Recipient =~ s/>$//;
		my ($RecipientName, $RecipientDomain) = split('@', $Recipient);

		$RecipientName = lc $RecipientName;
		$RecipientDomain = lc $RecipientDomain;

		if (($RecipientName eq $SynoConfig->{ham_report_account} || $RecipientName eq $SynoConfig->{spam_report_account}) && exists($SynoConfig->{local_domain_map}->{$RecipientDomain})) {
			$is_report_mail = 1;
			$report_account = $RecipientName;
			$report_address = $RecipientName . '@' . $RecipientDomain;
			last;
		}
	}

	return ($is_report_mail, $report_account, $report_address);
}

sub lda_send_mail {
	my($fromAddr, $toAddr, $toAccount, $body) = @_;

	my($pid);

	# Fork and exec for safety instead of involving shell
	$pid = open(CHILD, "|-");
	if (!defined($pid)) {
		md_syslog('err', "Cannot fork to run sendmail");
		return;
	}

	if ($pid) {   # In the parent -- pipe mail message to the child
		print CHILD $body;
		close(CHILD);
		return;
	}

	# In the child -- invoke Sendmail

	# Direct stdout to stderr, or we will screw up communication with
	# the multiplexor..
	open(STDOUT, ">&STDERR");

	my(@cmd);
	if ($fromAddr ne "") {
		push(@cmd, "-f");
		push(@cmd, "$fromAddr");
	} else {
		# push(@cmd, "-f<>");
	}
	push(@cmd, "-d");
	push(@cmd, $toAccount);

	# In curlies to silence Perl warning...
	my $lda = '/var/packages/MailPlus-Server/target/libexec/dovecot/dovecot-lda-setuid';
	{ exec($lda, @cmd); }

	# exec failed!
	md_syslog('err', "Could not exec $sm: $!");
	exit(1);
	# NOTREACHED
}

sub filter_end {
	my($entity) = @_;

	# No sense doing any extra work
	return if message_rejected();

	# decode utf8 for sender and recipients
	$Sender = Encode::decode_utf8($Sender);
	foreach my $Recipient (@Recipients) {
		$Recipient = Encode::decode_utf8($Recipient);
	}

	# handle report mail
	my ($is_report_mail, $report_account, $report_address) = report_mail_check();
	if ($is_report_mail) {
		my @report_mails = @{extract_attached_mails($entity)};
		foreach my $mail (@report_mails) {
			# use lda to sendmail
			lda_send_mail('', $report_address, $report_account, $mail);
		}

		# discard original report mail
		return action_discard();
	}

	# Check disclaimer rule
	my ($has_disclaimer, $disclaimer_txt, $disclaimer_html) = $MPSDisclaimer->get_disclaimer($Sender, \@Recipients);
	if ($has_disclaimer) {
		append_text_boilerplate($entity, $disclaimer_txt, 0);
		append_html_boilerplate($entity, $disclaimer_html, 0);
	}
}

# DO NOT delete the next line, or Perl will complain.
1;

