#!/usr/bin/perl
#####
## mailfilter: Final delivery (DEFAULTDELIVERY) mechanism for VectorStar, mbox format
# Rev 2.1
# By: Laird Bedore
# Date: 7/20/06
#####
##  This script is based on VectorStar's old mailfilter 1.3 script. It does
##  the following:
##  1. spam (as marked by SpamAssassin) is delivered straight to the 
##     $SPAMFOLDER file, or bounced, depending on configuration.
##  2. spam (as determined by this filter) is delivered straight to the
##     $SPAMFOLDER file, or bounced, depending on configuration.
##  3. Non-spam is delivered straight to the INBOX.
#####
## Exit codes:
#  0 = Let qmail continue its delivery work (we do not want this!)
# 99 = Don't do anything after this filter. (we deliver the message)
#100 = bounce back to sender. print your bounce message to STDOUT first.
#111 = defer to queue for later delivery. (temporary error, like flock)
#####

#####
## Configuration Options
#    NOTE: tilde (~) does not work to determine $HOME. use environment variables.
$F_PATH = $ENV{'HOME'}."/.mail/";			# Path to user's mailbox files. include trailing /
$F_USERCONFIG = $ENV{'HOME'}."/.mailfilter.conf";	# Path to user's config file.
$F_MAILRULES = $ENV{'HOME'}."/.mailrules";		# Path to user's mailrules file
$FLOCK_RETRIES = 10;					# Number of retries on write lock to mailbox
## Default configuration settings
$conf{'INBOX'} = "INBOX";				# Name of inbox folder
$conf{'SPAMFOLDER'} = "spam";				# Name of spam folder
$conf{'BOUNCE'} = 0;					# Default: do not bounce spam
$conf{'BOUNCEMSG'} = "spam rejected";			# Users can set their own bounce messages
$conf{'DELIVER'} = 1;					# Default: deliver spam
$conf{'REWRITE_SUBJ'} = 1;				# Default: append subject text for spam



#####
## Start of main()
#
# 1. Read config file variables.
# 2. Check message for SpamAssassin's mark
# 3. If not already considered spam, run STDIN through checking filter
# 4. Route mail based on output of filter

## Set the umask to 077.
umask 077;

## Read the user's config file
&read_config;

## $F_PATH and $conf{'INBOX'} should already exist. If not, the user
##  does not have email service enabled.
if (! -d "$F_PATH" || ! -f "$F_PATH$conf{'INBOX'}") {
	print "Sorry, no mailbox here by that name. (#5.1.1)";
	exit 100;
}
## If the $conf{'SPAMFOLDER'} does not exist, create it.
if (! -f "$F_PATH$conf{'SPAMFOLDER'}") {
	system("touch $F_PATH$conf{'SPAMFOLDER'}");
}

## Grab the STDIN and add a From line, so it's a standard mail
@maildata = <STDIN>;
$headerline = "From $ENV{'SENDER'} ".`date -u "+%a %b %d %T %Y"`;
unshift(@maildata,$headerline);

## Check to see if SpamAssassin has already identified as spam
foreach $line (@maildata) {
	if ($line =~ /^X-Spam-Flag: YES/) {	# It's spam!
		if ($conf{'DELIVER'}) {
			write_to_folder($F_PATH.$conf{'SPAMFOLDER'},@maildata) || exit 111;
		}
		if ($conf{'BOUNCE'}) {
			print "Email rejected: spam mail is not allowed here!";
			exit 100;
		}
		exit 99;
	} # End if x-spam-flag: yes
} # End foreach

## Run pattern-matching filter
$filter_result = 256;
if (-f $F_MAILRULES) {
	open(FILTER,"|grep -i -f $F_MAILRULES >/dev/null") or (print "System problems encountered while delivering email" && exit 111);
	for ($n = 0; $n <= $#maildata;$n++) {
		print FILTER $maildata[$n];
	}
	close(FILTER);
	$filter_result = $?;
	# 256 = no match
	# 0 = match
}

if ($filter_result == 0) { # spam found!
	if ($conf{'DELIVER'}) {
		if ($conf{'REWRITE_SUBJ'}) {
			@maildata = replace_subject(@maildata);
		}
		write_to_folder($F_PATH.$conf{'SPAMFOLDER'},@maildata) || exit 111;
	} # End if deliver=1
	if ($conf{'BOUNCE'}) {
		print $conf{'BOUNCEMSG'};
		exit 100;
	}
	exit 99;
} else { 
	# Not spam. deliver to $conf{'INBOX'};
	#exitcode - not a spam match - just deliver
	write_to_folder($F_PATH.$conf{'INBOX'},@maildata) || exit 111;
	exit 99;
}

## End of main()
#####


#####
## Functions
#
sub write_to_folder {
	# Exit codes: 0 = failure, 1 = success
	my($folder,@maillines) = @_;
	my $retries = 0;
	if (! -w "$folder") {
		print "Could not write to mailbox";
		return 0;
	}
	system("/usr/bin/quota $ENV{'USER'} >/dev/null 2>/dev/null");
	if ($?) {
		# Quota exceeded.
		print "could not deliver-disk quota exceeded.";
		return 0;
	}
	open(FOLDER,">>$folder") or return 0;
	while ($retries < $FLOCK_RETRIES) {
		if (flock(FOLDER,6)) {
			seek(FOLDER,0,2);
			print FOLDER join("",@maillines)."\n";
			flock(FOLDER,8);
			my $ret = close(FOLDER);
			if ($ret == 1) {
				return 1;
			} else {
				print "could not deliver-disk quota exceeded.";
				return 0;
			}
		}
		sleep 1;
		$retries++;
	}	
	close(FOLDER);
	print "server timed out trying to save to mailbox.";
	return 0;
}

sub read_config {
	my $var,$val;
	if (! -f $F_USERCONFIG) {
		return 0;
	}
	open(CONF,"$F_USERCONFIG") or return 0;
	while ($line = <CONF>) {
		# strip newlines
		$line =~ s/\n//g;
		# Strip quotes
		$line =~ s/\"//g;
		# Strip backticks
		$line =~ s/\`//g;
		# ignore comments, otherwise set variable
		if ($line !~ /^#/) {
			($var,$val) = split(/=/,$line);
			$conf{$var} = $val;
		}
	}
	close(CONF);
	return 1;
}

sub replace_subject {
	my(@mailjunk) = @_;
	for ($n = 0; $n <= $#mailjunk;$n++) {
		if ($mailjunk[$n] =~ /^Subject:/) {
			$mailjunk[$n] =~ s/^Subject: /Subject: (SPAM Score: 11) /;
		}
	}
	return @mailjunk;
}

### End of File.
