File: panda-wrapper

package info (click to toggle)
mailscanner 4.79.11-2.2
  • links: PTS
  • area: main
  • in suites: squeeze
  • size: 5,820 kB
  • ctags: 1,309
  • sloc: perl: 25,655; sh: 2,666; xml: 624; makefile: 242
file content (437 lines) | stat: -rwxr-xr-x 14,456 bytes parent folder | download
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;
}