#
# MailScanner - SMTP E-Mail Virus Scanner
# Copyright (C) 2002 Julian Field
#
# $Id: Quarantine.pm 3638 2006-06-17 20:28:07Z sysjkf $
#
# 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# The author, Julian Field, can be contacted by email at
# Jules@JulianField.net
# or by paper mail at
# Julian Field
# Dept of Electronics & Computer Science
# University of Southampton
# Southampton
# SO17 1BJ
# United Kingdom
#
package MailScanner::Quarantine;
use strict 'vars';
use strict 'refs';
no strict 'subs'; # Allow bare words for parameter %'s
use File::Copy;
use vars qw($VERSION);
### The package version, both in 1.23 style *and* usable by MakeMaker:
$VERSION = substr q$Revision: 3638 $, 10;
# Attributes are
#
# $dir set by new The root of the quarantine tree
# $uid set by new The UID to change files to
# $gid set by new The GID to change files to
# $changeowner set by new Should I try to chown the files at all?
# $fileumask set by new Umask to use before creating files
# $dirumask set by new Umask to use before mkdir 0777;
#
# Constructor.
# Takes dir => directory queue resides in
sub new {
my $type = shift;
my $this = {};
# Work out the uid and gid they want to use for the quarantine dir
my($currentuid, $currentgid) = ($<, $();
my($destuid, $destuname, $destgid, $destgname);
$destuname = MailScanner::Config::Value('quarantineuser') ||
MailScanner::Config::Value('runasuser');
$destgname = MailScanner::Config::Value('quarantinegroup') ||
MailScanner::Config::Value('runasgroup');
$this->{changeowner} = 0;
if ($destuname ne "" || $destgname ne "") {
$destuid = $destuname?getpwnam($destuname):0;
$destgid = $destgname?getgrnam($destgname):0;
$this->{gid} = $destgid if $destgid != $currentgid;
$this->{uid} = $destuid if $destuid != $currentuid;
} else {
$destuid = 0;
$destgid = 0;
$this->{gid} = 0;
$this->{uid} = 0;
}
# Create a test file to try with chown
my($testfn, $testfh, $worked);
$testfn = MailScanner::Config::Value('lockfiledir') || '/tmp';
$testfn .= "/MailScanner.ownertest.$$";
$testfh = new FileHandle;
$testfh->open(">$testfn") or
MailScanner::Log::WarnLog('Could not test file ownership abilities on %s, please delete the file', $testfn);
print $testfh "Testing file owner and group permissions for MailScanner\n";
$testfh->close;
# Now test the changes to see if we can do them
my($changeuid, $changegid);
if ($destgid != $currentgid) {
$worked = chown $currentuid, $destgid, $testfn;
if ($worked) {
#print STDERR "Can change the GID of the quarantine\n";
$changegid = 1;
}
} else {
$changegid = 0;
}
if ($destuid != $currentuid) {
$worked = chown $destuid, $destgid, $testfn;
if ($worked) {
#print STDERR "Can change the UID of the quarantine\n";
$changeuid = 1;
}
} else {
$changeuid = 0;
}
unlink $testfn;
# Finally store the results
$this->{uid} = $currentuid unless $changeuid;
$this->{gid} = $currentgid unless $changegid;
$this->{changeowner} = 1 if $changeuid || $changegid;
# Now to work out the new umask
# Default is 0600 for files, which gives 0700 for directories
my($perms, $dirumask, $fileumask);
$perms = MailScanner::Config::Value('quarantineperms') || '0600';
$perms = sprintf "0%lo", $perms unless $perms =~ /^0/; # Make it octal
$dirumask = $perms;
$dirumask =~ s/[1-7]/$&|1/ge; # If they want r or w give them x too
$this->{dirumask} = oct($dirumask) ^ 0777;
$fileumask = $perms;
$this->{fileumask} = oct($fileumask) ^ 0777;
#print STDERR sprintf("File Umask = 0%lo\n", $this->{fileumask});
#print STDERR sprintf("Dir Umask = 0%lo\n", $this->{dirumask});
my($dir);
$dir = MailScanner::Config::Value('quarantinedir');
#print STDERR "Creating quarantine at dir $dir\n";
umask $this->{dirumask};
mkdir($dir, 0777) unless -d $dir;
chown $this->{uid}, $this->{gid}, $dir if $this->{changeowner};
umask 0077; # As this is in startup code, assume something daft will happen
# Cannot make today's directory here as the date will change while the
# program is running.
$this->{dir} = $dir;
bless $this, $type;
return $this;
}
# Work out the name of today's directory segment
sub TodayDir {
my($day, $month, $year);
# Create today's directory if necessary
($day, $month, $year) = (localtime)[3,4,5];
$month++;
$year += 1900;
return sprintf("%04d%02d%02d", $year, $month, $day);
}
# Store infected files in the quarantine
sub StoreInfections {
my $this = shift;
my($message) = @_;
my($qdir, $todaydir, $msgdir, $uid, $gid, $changeowner, @chownlist);
#print STDERR "In StoreInfections\n";
# Create today's directory if necessary
#$todaydir = $this->{dir} . '/' . TodayDir();
$qdir = MailScanner::Config::Value('quarantinedir', $message);
$todaydir = $qdir . '/' . $message->{datenumber}; # TodayDir();
$uid = $this->{uid};
$gid = $this->{gid};
$changeowner = $this->{changeowner};
umask $this->{dirumask};
unless (-d $qdir) {
mkdir($qdir, 0777);
chown $uid, $gid, $qdir if $changeowner;
}
unless (-d $todaydir) {
mkdir($todaydir, 0777);
chown $uid, $gid, $todaydir if $changeowner;
}
# Create directory for this message
$msgdir = "$todaydir/" . $message->{id};
unless (-d $msgdir) {
mkdir($msgdir, 0777);
chown $uid, $gid, $msgdir if $changeowner;
}
# Is there a report for the whole message? If so, save the whole thing.
# Also save the whole thing if they have asked us to quarantine entire
# messages, not just infections.
umask $this->{fileumask};
if ($message->{allreports}{""} ||
MailScanner::Config::Value('quarantinewholemessage',$message) =~ /1/) {
#print STDERR "Saving entire message to $msgdir\n";
MailScanner::Log::NoticeLog("Saved entire message to $msgdir");
$message->{store}->CopyEntireMessage($message, $msgdir, 'message',
$uid, $gid, $changeowner);
push @chownlist, "$msgdir/message" if -f "$msgdir/message";
# Remember where we archived it, so we can put it in postmaster notice
push @{$message->{quarantineplaces}}, $msgdir;
#print STDERR "1 Added $msgdir to quarantine\n";
}
# Now just quarantine the infected attachment files.
my($indir, $attachment, $report);
$indir = $global::MS->{work}->{dir} . '/' . $message->{id};
while(($attachment, $report) = each %{$message->{allreports}}) {
# Skip reports pertaining to entire message, we've done those.
next unless $attachment;
if ($message->{deleteattach}{$attachment}) {
MailScanner::Log::NoticeLog("Deleted infected \"%s\"", $attachment);
} else {
#print STDERR "Quarantining $attachment to $msgdir\n";
MailScanner::Log::NoticeLog("Saved infected \"%s\" to %s", $attachment,
$msgdir);
# May be faster to do this with a Perl module File::Copy
#system($global::cp . " -p \"$indir/$attachment\" \"$msgdir/$attachment\"");
copy("$indir/$attachment", "$msgdir/$attachment");
push @chownlist, "$msgdir/$attachment";
# Remember where we archived it, so we can put it in postmaster notice
push @{$message->{quarantineplaces}}, $msgdir;
#print STDERR "2 Added $msgdir to quarantine\n";
}
}
chown $uid, $gid, @chownlist if @chownlist && $changeowner;
# Reset the umask to safe value
umask 0077;
}
syntax highlighted by Code2HTML, v. 0.9.1