--- SweepViruses.pm	Fri Jun  1 09:02:10 2007
+++ SweepViruses.pm.mod	Fri Jun  1 10:53:06 2007
@@ -36,8 +36,10 @@
 no  strict 'subs'; # Allow bare words for parameter %'s
 
 use POSIX qw(:signal_h setsid); # For Solaris 9 SIG bug workaround
 use DirHandle;
+use IO::Socket::INET;
+use IO::Socket::UNIX;
 
 use vars qw($VERSION $ScannerPID);
 
 ### The package version, both in 1.23 style *and* usable by MakeMaker:
@@ -290,15 +292,15 @@
     SupportScanning     => $S_SUPPORTED,
     SupportDisinfect    => $S_NONE,
   },
   "clamd"  => {
-    Name		=> 'ClamAV Daemon',
-    Lock                => 'ClamAVBusy.lock',
-    CommonOptions       => '--no-summary --stdout',
+    Name                => 'Clamd',
+    Lock                => 'ClamDBusy.lock',
+    CommonOptions       => '',
     DisinfectOptions    => '',
     ScanOptions         => '',
-    InitParser          => \&InitClamAVParser,
-    ProcessOutput       => \&ProcessClamAVOutput,
+    InitParser          => \&InitClamAVModParser,
+    ProcessOutput       => \&ProcessClamAVModOutput,
     SupportScanning     => $S_SUPPORTED,
     SupportDisinfect    => $S_NONE,
   },
   "clamav"  => {
@@ -978,8 +980,11 @@
         exit;
       } elsif ($scanner eq 'clamavmodule') {
         ClamAVModule($subdir, $disinfect, $batch);
         exit;
+      } elsif ($scanner eq 'clamd') {
+        ClamdScan($subdir, $disinfect, $batch);
+        exit;
       } else {
         exec "$sweepcommand $instdir $voptions $subdir";
         MailScanner::Log::WarnLog("Can't run commercial checker $scanner " .
                                   "(\"$sweepcommand\"): $!");
@@ -1379,17 +1384,17 @@
   #print STDERR "Output is \"$logout\"\n";
   ($keyword, $virusname, $filename) = split(/:: /, $line, 3);
 
   if ($keyword =~ /^error/i && $logout !~ /rar module failure/i) {
-    MailScanner::Log::InfoLog("ClamAVModule::%s", $logout);
+    MailScanner::Log::InfoLog("$Name::%s", $logout);
     return 1;
   } elsif ($keyword =~ /^info/i || $logout =~ /rar module failure/i) {
     return 0;
   } elsif ($keyword =~ /^clean/i) {
     return 0;
   } else {
     # Must be an infection reports
-    MailScanner::Log::InfoLog("ClamAVModule::%s", $logout);
+    MailScanner::Log::InfoLog("$Name::%s", $logout);
 
     ($dot, $id, $part, @rest) = split(/\//, $filename);
     $report = $Name . ': ' if $Name;
     $infections->{"$id"}{"$part"}
@@ -3076,6 +3081,209 @@
       "http://www.sng.ecs.soton.ac.uk/mailscanner/install/codestatus.shtml" .
       " as it will tell you what to do."); 
 }
 
+sub ClamdScan {
+    my($dirname, $messagebatch) = @_;
+    my($dir, $child, $childname, $filename, $results, $virus);
+
+	# Clamd MUST have the full path to the file/dir it's scanning
+	# so let's build the scan dir here and remove that pesky \. at the end
+
+    my $ScanDir = "$global::MS->{work}->{dir}/$dirname";
+	$ScanDir =~ s/\/\.$//;
+
+	# If we don't have the required perl libs exit in a fashion the
+	# parser will understand
+	unless (eval { require IO::Socket::INET } ){
+		print "ERROR:: You Need IO-Socket-INET To Use clamd As A " .
+			"Scanner :: $dirname\n";
+		exit 1;
+    }
+	unless (eval { require IO::Socket::UNIX } ){
+		print "ERROR:: You Need IO-Socket-INET To Use clamd As A " .
+			"Scanner :: $dirname\n";
+		exit 1;
+    }
+
+    # The default scan type is set here and if threading has been enabled
+	# switch to threaded scanning
+	my $ScanType = "CONTSCAN";
+	my $LockFile = MailScanner::Config::Value('clamdlockfile');
+	my $TCP = 1;
+	my $TimeOut = MailScanner::Config::Value('virusscannertimeout');
+	my $UseThreads = MailScanner::Config::Value('clamdusethreads');
+	$ScanType = "MULTISCAN" if $UseThreads;
+
+	my $PingTimeOut = 90; # should respond much faster than this to PING
+	my $Port = MailScanner::Config::Value('clamdport');
+	my $Socket = MailScanner::Config::Value('clamdsocket');
+	my $line = '';
+	my $sock;
+
+	# If we did not receive a socket file name then we run in TCP mode
+
+	$TCP = 0 if $Socket =~ /^\//;
+
+	# Print our current parameters if we are in debug mode
+	MailScanner::Log::DebugLog("Debug Mode Is On\n");
+	MailScanner::Log::DebugLog("Use Threads : YES") if $UseThreads;
+	MailScanner::Log::DebugLog("Use Threads : NO") unless $UseThreads;
+	MailScanner::Log::DebugLog("Socket    : %s\n", $Socket)  unless $TCP;
+	MailScanner::Log::DebugLog("IP        : %s\n", $Socket) if $TCP;
+	MailScanner::Log::DebugLog("IP        : Using Sockets\n") unless $TCP;
+	MailScanner::Log::DebugLog("Port      : %s\n", $Port) if $TCP;
+	MailScanner::Log::DebugLog("Lock File : %s\n", $LockFile) if $LockFile ne '';
+	MailScanner::Log::DebugLog("Lock File : NOT USED", $LockFile) unless $LockFile ne '';
+	MailScanner::Log::DebugLog("Time Out  : %s\n", $TimeOut);
+	MailScanner::Log::DebugLog("Scan Dir  : %s\n", $ScanDir);
+
+	# Exit if we cannot find the socket file, or we find the file but it's not
+	# a socket file (and of course we are not using TCP sockets)
+
+	MailScanner::Log::WarnLog("Cannot find Socket %s Exiting!\n",
+		$Socket) if! $TCP && ! -e $Socket;
+	exit 1 if ! $TCP && ! -e $Socket;
+
+	MailScanner::Log::WarnLog("Found %s but it is not a valid UNIX Socket. " .
+		"Exiting\n", $Socket) if ! $TCP && ! -S $Socket;
+	exit 1 if ! $TCP && ! -S $Socket;
+
+	# If there should be a lock file, and it's missing the we assume
+	# the daemon is not running and warn, pass error to parser and leave
+	if ( $LockFile ne '' && ! -e $LockFile ){
+ 		MailScanner::Log::WarnLog("Lock File %s Not Found, Assuming Clamd " .
+		"Is Not Running\n", $LockFile);
+		print "ERROR:: Lock File $LockFile was not found, assuming Clamd  " .
+			"is not currently running :: $dirname\n";
+		exit 1;
+	}
+
+	# Attempt to open the appropriate socket depending on the time (TCP/UNIX)
+	if ( $TCP){
+    	$sock = IO::Socket::INET->new( PeerAddr => $Socket,
+                                   	PeerPort => $Port,
+									Timeout => $TimeOut,
+                                   	Proto     => 'tcp');
+	}else{
+       	$sock = IO::Socket::UNIX->new( Timeout => $TimeOut,
+                                        	Peer => $Socket );
+
+	}
+
+	# If we got here we know we have a socket file but it could be dead
+	# or clamd may not be listening on the TCP socket we are using, either way
+	# we exit with error if we could not open the connection
+
+	if (!$sock ) { # socket file from a dead clamd or clamd is not listening
+    	MailScanner::Log::WarnLog("Could not connect to clamd");
+		print "ERROR:: COULD NOT CONNECT TO CLAMD DAEMON  " .
+			":: $dirname\n";
+    	exit 1;
+	}else{
+		# Make sure the daemon is responsive before passing it something to
+		# scan
+		if ( $sock->connected ) {
+			MailScanner::Log::DebugLog("Clamd : Sending PING");
+			$sock->send("SESSION\n");
+			$sock->send("PING\n");
+			$PingTimeOut += time();
+			$line = '';
+
+			while ($line eq ''){
+				$line = <$sock>;
+				# if we timeout then print error (if debugging) and exit with erro
+				MailScanner::Log::WarnLog("ClamD Timed Out During PING " .
+					"Check!\n") if $PingTimeOut < time;
+				print "ERROR:: CLAM PING TIMED OUT! :: " .
+					"$dirname" if time > $PingTimeOut;
+				exit 1 if time > $PingTimeOut;
+				last if time > $PingTimeOut;
+				chomp($line);
+			}
+
+			MailScanner::Log::DebugLog("Clamd : GOT '%s'",$line);
+			MailScanner::Log::WarnLog("ClamD Responded '%s' Instead of PONG " .
+				"During PING Check, Recommend Restarting Daemon\n",
+				 $line) if $line ne 'PONG';
+			print "ERROR:: CLAMD DID NOT RESPOND PROPERLY TO PING! PLEASE " .
+				"RESTART DAEMON :: $dirname" unless $line eq "PONG";
+			close($sock) unless $line eq "PONG";
+			exit 1 unless $line eq "PONG";
+			MailScanner::Log::DebugLog("ClamD is running\n");
+			# ClamD will have closed the connection so we repopen it
+		}else{
+ 			MailScanner::Log::WarnLog("ClamD has an Unknown problem, recommend you re-start the daemon!\n");
+			print "ERROR:: CLAMD HAS AN UNKOWN PROBLEM, RECOMMEND YOU " .
+			"RESTART THE DAEMON :: $dirname";
+			exit 1;
+		}
+	}
+
+	if ( $sock->connected ) {
+	  # Going to do this the same as Julian does the ClamAVModule
+	  $dir   = new DirHandle;
+	  $child = new DirHandle;
+	  $dir->open($ScanDir)
+    	  or MailScanner::Log::DieLog("Can't open directory %s for scanning, %s",
+        	                          "$dirname", $!);
+	  # Find all the subdirectories
+		while($childname = $dir->read()) {
+    		next unless -d "$ScanDir/$childname"; # Only search subdirs
+	    	next if $childname eq '.' || $childname eq '..';
+
+    		$child->open("$ScanDir/$childname")
+	      	or MailScanner::Log::DieLog("Can't open directory %s for scanning, %s",
+                                  	"$ScanDir/$childname", $!);
+			$TimeOut += time();
+			while($filename = $child->read()) {
+				next if $filename eq '.' || $filename eq '..';
+      			next unless -f "$ScanDir/$childname/$filename"; # Only check files
+				$sock->send("$ScanType $ScanDir/$childname/$filename\n");
+				MailScanner::Log::DebugLog("SENT : $ScanType %s \n", "$ScanDir/$childname/$filename");
+
+		    	$results = '';
+		    	$results = <$sock>;
+				# if we timeout then print error and exit with error
+				MailScanner::Log::WarnLog("ClamD Timed Out!\n") if $TimeOut < time;
+				close($sock) if time > $TimeOut;
+				print "ERROR:: CLAM PING TIMED OUT! :: " .
+					"$dirname/$childname/$filename" if time > $TimeOut;
+				exit 1 if time > $TimeOut;
+				chomp($results);
+
+				# Pull the basedir out and change it to a dot for the parser
+				$results =~ s/$ScanDir/\./;
+  				unless ($results) {
+    				print "ERROR:: $results" . ":: $dirname/$childname/$filename\n";
+    				next;
+  				}
+  				if ($results =~ /.+?:.+?\sFOUND$/) {
+    				print "INFECTED::";
+    				print " $results" . ":: $dirname/$childname/$filename\n";
+				}elsif ($results =~ /^.+\sERROR$/){
+			    	print "ERROR:: $results" . ":: $dirname/$childname/$filename\n";
+					next;
+  				} else {
+    				print "CLEAN:: :: $dirname/$childname/$filename\n";
+  				}
+			} # Reading filenames
+			$child->close;
+		}# Reading $dir
+		$dir->close;
+		$sock->send("END\n");
+		close($sock);
+	}else{
+		# Only way to get here is successfully opening the socket but not
+		# actually connecting to the daemon so there is something amiss and we
+		# leave
+		print "ERROR:: Unknown Clam Error Has Occured Suggest You Restart " .
+			"clamd :: $dirname\n";
+ 		MailScanner::Log::DebugLog("ClamD has an Unknown problem, recommend " .
+			"you re-start the daemon!\n");
+		exit 1;
+	}
+
+} # EO ClamdScan
+
 1;
 
