#!/usr/bin/perl -w
use strict;
my $VERSION = '0.41';
my %status = ( 'OK' => 0, 'ERROR' => 1 );
$| = 1;

use subs 'die';
sub die {
	print "cgpeasa [$$] @_\n";
	exit $status{OK};
}

# look for required modules
my @required_module = qw/Getopt::Long Config::Tiny MIME::Base64 Pod::Usage Email::Address/;
my @missing_modules = ();
foreach( @required_module ) {
	eval "use $_";
	push @missing_modules, $_ if $@;
}
die "Missing perl modules: @missing_modules" if scalar @missing_modules;

# get normal options from command line
Getopt::Long::Configure("bundling");
my $verbose = 0;
my $help = "";
my $show_version = "";
my $conf = '';
my $maildir = '';
my $filter = '';
my %default = ( maildir => '.', filter=>'/usr/bin/spamc -u $ACCNT' );
Getopt::Long::GetOptions(
	"V|version"=>\$show_version,
	"v|verbose+"=>\$verbose,"h|help"=>\$help,
	"c|conf=s"=>\$conf,
	"m|maildir=s"=>\$maildir,
	"f|filter=s"=>\$filter,
	) or pod2usage(-verbose=>1, -exitval=>$status{ERROR});

# get CGP options from command line
my ($returnpath,$recipients,$account,$extra) = getargs(@ARGV);

# help and version info
die "$VERSION" if $show_version;
pod2usage(-verbose => 2, -exitval=>$status{OK}) if $help;
pod2usage(-verbose => 1, -exitval=>$status{OK}) if !$account;

# read configuration file (--conf) and then apply command arguments
my $settings = Config::Tiny->new();
if( $conf ) {
	$settings = Config::Tiny->read($conf) or die "cannot read config file";
}
foreach( grep { defined } keys %default ) {
	$settings->{_}{$_} = $default{$_} unless $settings->{_}{$_};
}
$maildir = $settings->{_}{maildir} unless $maildir;
$filter = $settings->{_}{filter} unless $filter;

die "delivery folder does not exist ($maildir)" unless -d $maildir;

# read email from stdin (cgserver Execute Action protocol)
my $text = "";
#$text .= "Return-Path: <$returnpath>\n" if $returnpath;
while(<STDIN>) {
	$text .= $_;
}

# invoke the filter (--filter)
# (write email to tmp file, pass to filter, direct filter's stdout to maildir.  a hypothetical future version would implement the spamc protocol and talk to spamd directly)
my $msgfilename = $$ . "-" . time . "-" . int(rand(10)); # xxx should do this in maildir format. see http://cr.yp.to/proto/maildir.html
if( $filter ) {
	$filter =~ s/\$ACCNT/$account/g;
	# start with an Envelope-To header so CGP knows where to send the mail when we resubmit it (cgserver requires that it appear before To,Cc,Bcc fields)
	# writing it now instead of passing it to the filter guarantees it will exist. cgserver will not deliver the message without it.
	open(MESSAGEF,">$maildir/$msgfilename.tmp");
	print MESSAGEF "Envelope-To: <$account>\n";
	close(MESSAGEF);
	open(FILTER,"| $filter >> $maildir/$msgfilename.tmp") or die "cannot invoke filter: $!";
	print FILTER $text;
	close(FILTER);
}
else {
	# add an Envelope-To header so CGP knows where to send the mail when we resubmit it.
	# write the original message immediately afterwards because no filter was specified
	open(MESSAGEP,">$maildir/$msgfilename.tmp");
	print MESSAGEP "Envelope-To: <$account>\n";
	print MESSAGEP $text;
	close(MESSAGEP);	
}

# move the message to cgp's folder and give it the '.sub' extension cgserver requires to recognize it
system("mv","$maildir/$msgfilename.tmp","$maildir/$msgfilename.sub");
die "cannot move message to maildir: $!" unless -f "$maildir/$msgfilename.sub";

# Must exit with 0 for CGP to continue processing this message
exit $status{OK};


# This sub looks at the command line arguments and separates them into return path, recipients, account, and extra arguments.
sub getargs {
	my (@args) = @_;
	my $returnpath;
	my @recipients;
	my $account;
	my @extra;
	while( @args ) {
		if( $args[0] eq "-p" ) {
			shift @args;
			while( scalar(@args) && substr($args[0],0,1) ne "-" ) {
				$returnpath = shift(@args);
			}
			next;
		}
		if( $args[0] eq "-r" ) {
			shift @args;
			while( scalar(@args) && substr($args[0],0,1) ne "-" ) {
				push @recipients, shift(@args);
			}
			next;
		}
		if( $args[0] eq "-u" ) {
			shift @args;
			while( scalar(@args) && substr($args[0],0,1) ne "-" ) {
				$account = shift(@args);
			}
			next;
		}
		push @extra, shift(@args);
	}
	return ($returnpath,\@recipients,$account,\@extra);
}


=pod

=head1 NAME

cgpeasa - the Rule Action component of CGP Whitelist

=head1 SYNOPSIS

Site-wide application:
 Any Route is LOCAL*
 Execute [ACCNT] /usr/local/bin/cgpeasa -m /var/CommuniGate/Submitted --
 
Per-user or per-domain application:
 Submit Address is SMTP*
 Execute [ACCNT] /usr/local/bin/cgpeasa -m /tmp/cgpeasa --
 
 Crontab entry for root:
 * * * * * mv /tmp/cgpeasa/* /var/CommuniGate/Submitted/

Command line: 
 cgpeasa -- -u account.name@domain.com

=head1 OPTIONS

=over

=item -m,--maildir /tmp

When running as a site-wide action, you can write to cgserver Submitted folder directly.
When running as a domain or user action, you have to write to a temp folder and run a cronjob 
(as root or cgserver user) to copy it into the Submitted folder. For example:

 * * * * * mv /tmp/cgpeasa/* /var/CommuniGate/Submitted/
 
The default maildir is current directory. 

=item -f,--filter '/usr/bin/spamc -u $ACCNT'

You can change the default filter with this option. If the term $ACCNT appears in your
filter command, it will be replaced with the value of the -u option ([ACCNT] from cgserver).

=item -c,--conf /etc/mail/cgpeasa.conf

You can use this option and specify other options in the config file:

 maildir = /var/CommuniGate/Submitted
 filter = /usr/bin/spamc

=back

=head1 NOTES

If we encounter a problem, we print the report on STDOUT and exit with status 0 so that
CGP will continue to process the message and enter our report in the log.

For domain and user level applications, there must be a valid OS username that matches the OS Integration
preferences for the user (even in domain level, the destination account used). 

Because cgpeasa does not run as root, it cannot place messages in the CGP Submitted folder. So
instead, it places them in its own folder, and you must set up a periodic movement. The simplest way
is a crontab entry similar to this one:

* * * * * mv /Users/jonathan/cgpwl/messages/* /var/CommuniGate/Submitted/

=head1 CHANGES

Sun Aug 28 19:04:00 PST 2005
+ version 0.1

Fri Nov 11 17:09:33 PST 2005
+ now using external program (cgpwl) to retrieve the whitelist
+ added safe check for required modules (currently Getopt::Long Config::Tiny MIME::Base64 Email::Address Email::Simple Pod::Usage)
+ copyright notice and GNU GPL
+ inline documentation with POD
+ override of die() exits with OK code for the CGP Execute API
+ added --help option
+ added --version option
+ added --conf option
+ added --verbose option
+ version 0.2

Sat Nov 12 16:53:29 PST 2005
+ repurposed as a spamassassin "plugin" for cgserver. now calls spamc and submits the rewritten email to cgserver via Submitted folder
+ removed code that extracted sender address from the email and called the cgpwl command to compare it to the account whitelist
+ renamed delivery folder in the config file to maildir
+ added --maildir option
+ added --filter option
+ the Envelope-To header required by cgserver is now written without using Email::Simple. dependency on Email::Simple removed.
+ version 0.4

Thu Nov 17 11:01:53 PST 2005
+ all filehandle names are now unique to help identify errors in the CGP SystemLog
+ fixed --conf option
+ version 0.41

=head1 AUTHOR

Jonathan Buhacoff <jonathan@pnc.net>

=head1 COPYRIGHT AND LICENSE

 Copyright (C) 2005 Jonathan Buhacoff

 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.

 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

 http://www.gnu.org/licenses/gpl.txt
 
=cut
