1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437
|
#!/usr/bin/perl -w
#
# MailScanner - SMTP E-Mail Virus Scanner
# Copyright (C) 2001 Julian Field
#
# $Id: panda-wrapper 3963 2007-06-12 09:13:33Z 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
#
#
# This wrapper, and the Panda support in MailScanner itself, is all
# actually implemented by Rick Cooper <rcooper@dwford.com>.
# All queries to him please.
#
# To test from the command line change to the directory you wish to
# check and issue this command (change paths to reflect your install)
# "/opt/MailScanner/lib/panda-wrapper /usr -nsb -eng -aex -nso -aut -cmp ."
# Make sure your testing dir is one directory deep (don't for get the . BTW)
# example
# test+
# .+ testfiles
# .+ moretestfiles
# execute from directory test and it will scan the testfiles and moretestfiles
# directories. There should be no sub-dirs below those two, this simulates
# MailScanner's process-dir->message-dir structure
my $pavcl;
my $base;
# Just a default in case there is a problem with getting
# the scanner timeout value - belt and suspenders
my $CmdTimeOut = '500';
my $CmdArgv="";
# Set Terminal Rows and Columns
my $TermRows = 24;
my $TermCols = 80;
my @VirusStrings;
my $VirusCount=0;
# Define as global so I don't have to pass private in the
# vt_scroll function. No need to trap all that license crap
# that scrolls off the screen when the -aut switch is passed
# so we will dump everything that scrolls until we find the copyright line
# that is what $FoundTop is for, It is checked in the vt_scroll sub since
# that is where the license information will be found
my $VirtualScreens="";
my $FoundTop = 0;
$pavcl = shift;
$pavcl .= '/bin/pavcl';
if ($ARGV[0] eq '-IsItInstalled') {
exit 0 if -x "$pavcl";
exit 1;
}
# If pavcl is not there, and executable, then exit with an error that the
# panda output parser can do something with
print_and_exit("Panda:ERROR: Could Not Find $pavcl scanner\n") if !-x "$pavcl";
# Since we must have Term::VT102 let's issue an error warning if we don't
# find it installed here. The rest are used my MailScanner in many places
# putting them here prevents wasting time if pavcl is not installed
print_and_exit("Panda:ERROR: YOU NEED TO INSTALL MODULE Term::VT102 PLEASE!(CPAN?)\n")
unless eval { require Term::VT102 };
use Cwd;
use Cwd 'abs_path';
use POSIX qw(:signal_h setsid);
use FileHandle;
# We do not want buffering
$| = 1;
# Get the current working directory and make sure it's
# not a symlink
#$base = cwd();
$base = abs_path();
chomp $base;
# Get the last entry in @ARGV
pop @ARGV;
# Need to get the value of ScannerTimeOut from the
# argument list, and remove it from the list as well
foreach $c_arg (@ARGV){
if ($c_arg =~ /\-t:(\d+)/){
$CmdTimeOut = $1;
}else{
$CmdArgv .= "$c_arg ";
}
}
#There will be a trailing space so let's remove it.
$CmdArgv =~ s/\s+$//;
my @MessageDirs;
# Now read in any message directories under the current
# process directory
my @DirList = <$base/*>;
$VirusCount =0;
# Scan the process directory for any message directories
# there will be files in here as well but they are not scanned
# since they should only be header files
foreach $dir (@DirList){
my $temp;
# If this is a directory then split the directory name
# from the full path (which includes the process directory)
# Scan this entire directory in one pass, process and continue
if (-d $dir){
$dir =~ s/^.+\/(.+)$/$1/;
$temp = scan_virus($dir,$base);
if ($temp =~ /\t\tFOUND:/){
$VirusStrings[$VirusCount]= $temp;
$VirusCount++;
}
}
}
# If no virus was found print string to tell parser no viruses were
# found, otherwise print our report and exit
print "Virus: 0\n" unless $VirusCount;
foreach $outline (@VirusStrings){
print "$outline\n";
}
exit 0;
sub scan_virus{
# Make sure our Virtual Screen is clean when called.
$VirtualScreens = "";
# Setup our scan variables
# Make sure they are free of newlines
my ($DirName,$ProcessDir) = @_;
$DirName =~ s/\n//g;
my @TLast;
# Now make sure our current msg dir is free from any parts of the
# base process dir
$DirName =~ s/^.*?\/(.*)$/$1/g;
# Exit with an error the output parser can understand and log if
# we cannot change to the message directory
print_and_exit("Panda:ERROR: Could not change to message dir - > $DirName\n") if !chdir($DirName);
# Intialize our virus name and string vars
my $VName;
my $Str;
# Set up the scan command to scan the current directory
my $Cmd = "$pavcl './' $CmdArgv 2>&1";
# We need to track the previous line, with all processing completed
# because that is where the file name will be when/if we find a virus.
# We also track the last archive file (filename.ext[filename] formated)
# so when we find an infected file from inside an archive (name will end
# with a ]) we can add the archive_name-> string before the file name
my $LastLine = "";
my $LastArchive = "";
# We need to check our return string to see if we have already found
# this file. pavcl will return an archived file as the bare name as well
# as the archive_name[file_name] format and we don't want to show the
# same file twice.
# Also initialize the return string var
my $TestString = "";
my $TestString2 = "";
my $rc = "";
my $StrIdx=0;
# We are going to use SafePipe, with a the configured scanner time out
# Exit with error if the command times out
my $PipeOut = SafePipe($Cmd,$CmdTimeOut);
print_and_exit("Panda:ERROR: $pavcl timed out!\n")
if $PipeOut eq "COMMAND_TIMED_OUT";
# Now we intialize our VT102 object.It will process
# the output into a virtual screen which we can then deal with
# easily
my $vt = Term::VT102->new ('cols' => $TermCols, 'rows' => $TermRows);
# We have to watch for a screen scroll if there are more than
# four infections found so we set the callback for scroll_down
# to a function that will grab the line that is being scrolled off
$vt->callback_set ('SCROLL_DOWN', \&vt_scroll);
# Now process the command output, if there is a scroll_down callback
# $VirtualScreens will be intialized with what ever lines are being
# scrolled off the screen
$vt->process ($PipeOut);
# This section is for the current, last screen. The last screen's out
# put will only hold four infections at a time, anything prior to
# that was handled in vt_scroll. If there was no scrolling then
# we will will need to process the current and/or only screen here
for($StrIdx=0;$StrIdx <= $TermRows;$StrIdx++){
# if we call for a value out of our virtual screen's column/row
# it will return undef so we don't want to attempt a .= on an
# undefined error, it's so messy. We don't worry about
# the license information here because it's alreay gone.
$VirtualScreens.= $vt->row_plaintext($StrIdx)."\n"
if defined($vt->row_text($StrIdx));
}
# Now we split our VT102 output on the newlines and feed each line
# into the parse loop to look for any viruses
@CurrentScreen = split(/\n/,$VirtualScreens);
foreach $Str (@CurrentScreen){
# Patch next line from Rick Cooper
$Str =~ s/\s+file can not be modified//i;
# Depending on the version of panda one is using there is a difference
# in the wording of the virus found string
if ($Str =~ /(found virus|virus encontrado|encontrado virus|virus found)\s+:\s?(.*?)$/i) {
# Don't want leading and trailing spaces in the virus name
$VName = $2;
$VName =~ s/^\s+|\s+$//g;
# Now build the return string containing the virus name,
# the infected file, the message dir and process/base dir
# We get the file name from the line just before the one that
# triggered the virus found logic. Since we may encounter
# multiple viruses we will continue to join the "found strings"
# until the entire output is processed
$LastLine =~ s/\n|^\s+|\s+$//g;
# Now we build the test strings to find duplicates. We need to
# check for bare name only and archive_name->file_name
# Cannot really do that with a | because we have to quote all
# special characters in each string and the ($ArchiveName->)?
# would get escaped and never match \($ArchiveName->\)\?
$TestString = "FOUND:$VName##::##$LastLine##::##$DirName##::##$ProcessDir";
$TestString =~ s/(\/|\.|\^|\$|\*|\+|\?|\{|\}|\[|\]|\(|\))/\\$1/g;
$TestString2 = "FOUND:$VName##::##$LastArchive->$LastLine##::##$DirName##::##$ProcessDir";
$TestString2 = "FOUND:$VName##::##$LastLine##::##$DirName##::##$ProcessDir"
if $LastLine =~ /.\-\>./mg;
$TestString2 =~ s/(\/|\.|\^|\$|\*|\+|\?|\{|\}|\[|\]|\(|\))/\\$1/g;
# Append the current virus information, if we have not already
# reported this virus
$rc .= "\t\tFOUND:$VName##::##$LastLine##::##$DirName##::##$ProcessDir\n"
if $rc !~ /$TestString/mg and $rc !~ /$TestString2/mg;
}# End of found a virus
# We get the file name from the line preceeding the virus found
# string. The file name can appear in two formats
# If there is only one file in the archive the entire
# archive_name[file_name] is returned and we want to
# change this so the reports look more like the other scanners
# in the format of my.zip->my.file.
$LastLine = $Str;
$LastArchive = $LastLine if $LastLine =~ /^.+\[.*?\].*$/;
$LastArchive =~ s/^(.+)\[.*?\]/$1/ if $LastLine =~ /^.+\[.*?\]/;
$LastArchive =~ s/^.+\/(.+)$/$1/g;
$LastArchive =~ s/^\s+|\s+$//g;
$LastLine = "$1->$2" if $LastLine =~ /^(.+)\[(.+)\]/;
$LastLine =~ s/^.+\/(.+)$/$1/g;
$LastLine =~ s/^\s+|\s+$//g;
}
# Pull off any terminating new line here. Return OK if no virus found
# or our report string if there was.
chomp($rc);
print_and_exit("Panda:ERROR: Could not return from message dir - > $DirName\n") if !chdir("../");
return "OK" if $rc eq "";
return $rc;
}
# This is the same SafePipe from MailScanner::Message.pm
sub SafePipe {
my ($Cmd, $TimeOut) = @_;
my($Kid, $pid, $TimedOut, $Str);
$Kid = new FileHandle;
$TimedOut = 0;
$? = 0; # Make sure there's no junk left in here
eval {
die "Can't fork: $!" unless defined($pid = open($Kid, '-|'));
if ($pid) {
# In the parent
# Set up a signal handler and set the alarm time to the timeout
# value passed to the function
local $SIG{ALRM} = sub { $TimedOut = 1; die "Command Timed Out" };
alarm $TimeOut;
# while the command is running we will collect it's output
# in the $Str variable. We don't process it in any way here so
# whatever called us will get back exactly what they would have
# gotten with a system() or backtick call
while(<$Kid>) {
$Str .= $_;
#print STDERR "SafePipe : Processing line \"$_\"\n";
}
$pid = 0; # 2.54
alarm 0;
# Workaround for bug in perl shipped with Solaris 9,
# it doesn't unblock the SIGALRM after handling it.
eval {
my $unblockset = POSIX::SigSet->new(SIGALRM);
sigprocmask(SIG_UNBLOCK, $unblockset)
or die "Could not unblock alarm: $!\n";
};
} else {
# In the child
POSIX::setsid();
# Execute the command via an exec call, bear in mind this will only
# capture STDIN so if you need STDERR, or both you have to handle, for
# example, 2>&1 as part of the command line just as you would with
# system() or backticks
#
#the line following the
# call should *never* be reached unless the call it's self fails
# In this instance I need to know what kind of terminal pavcl
# is writting to and every *nix understands vt100 and it's easy
# to parse the output as sent by pavcl to a vt100 terminal
$ENV{TERM} = 'vt100';
my @args = ( "$Cmd" );
# Just to be safe let's take control of STDIN and then exec the
# command
open STDIN, "< /dev/null";
exec @args
or die "Failed to execute commad in safepipe";
exit 1;
}
};
alarm 0; # 2.53
# Catch failures other than the alarm
die "pavcl died with real error $@" if $@ and $@ !~ /Command Timed Out/;
# In which case any failures must be the alarm
if ($@ or $pid>0) {
# Kill the running child process
my($i);
kill -15, $pid;
# Wait for up to 5 seconds for it to die
for ($i=0; $i<5; $i++) {
sleep 1;
waitpid($pid, &POSIX::WNOHANG);
($pid=0),last unless kill(0, $pid);
kill -15, $pid;
}
# And if it didn't respond to 11 nice kills, we kill -9 it
if ($pid) {
kill -9, $pid;
waitpid $pid, 0; # 2.53
}
}
# If the command timed out return the string below, otherwise
# return the command output in $Str
return $Str unless $TimedOut;
return "COMMAND_TIMED_OUT";
}
# This sub is just a way to do a single line error string return and
# exit with information the output parser would understand
sub print_and_exit{
$ES = $_[0];
print $ES;
exit 0;
}
# This sub handles the scroll callbacks so we can keep the lines
# scrolling up and off the virus display area of the pavcl output
sub vt_scroll {
my ($vtobject, $type, $arg1, $arg2, $private) = @_;
my ($i,$ts);
$ts = "";
# pavcl scrolls one line at a time so we just grab
# the line that is leaving and append it to our VirtualScreen
$ts = $vtobject->row_plaintext($arg1)if defined($vtobject->row_plaintext($arg1));
$ts =~ s/\s+$//g if $ts ne "";
$FoundTop = 1
if $ts =~ /Panda Antivirus Linux,\s+\(c\)\s+Panda Software \d{4}/i;
$VirtualScreens .= $ts."\n" if $ts ne "" and $FoundTop;
}
|