Scanning files with ClamAV from CakePHP

by Sander Marechal

One of the requirements for the upcoming public release of Officeshots.org is that all uploaded files are run through a virus scanner before they are made available. Picking a virus scanner for this job was easy. ClamAV is open source, well supported, actively maintained and comes pre-packaged for Debian Lenny which we use for the Officeshots servers. Finding a PHP library to interact with ClamAV proved harder though. The 3rd party library page for ClamAV points to two different libraries that provide PHP bindings for ClamAV but both appear to be dead and expunged from the internet. So, I created my own using the clamd TCP API, and because Officeshots is built using CakePHP I implemented it as a Cake plugin.

You can download the clamd-0.1.tar.gz plugin or check out the source from my Subversion repository with the following command:

  1. ~$ svn checkout https://svn.jejik.com/cakephp/plugins/clamd/trunk clamd

Or you can browse the repository online. In the rest of this article I will show you how you can use this plugin.

Install ClamAV (on Debian)

Start off by installing ClamAV. On Debian and derivative distributions this is very simple.

  1. ~# aptitude install clamav clamav-freshclam

Make sure that the ClamAV daemon is running and that freshclam regularly updates the virus definition database. On Debian Lenny this will be done automatically. Please refer to the ClamAV installation guide for installation on other Linux distributions or on Windows machines. After you have installed the ClamAV daemon you can test it with the clamdscan command.

  1. ~$ clamdscan somefile.odt
  2. /home/you/somefile.odt: OK
  3.  
  4. ----------- SCAN SUMMARY -----------
  5. Infected files: 0
  6. Time: 0.011 sec (0 m 0 s)

Install the Clamd CakePHP plugin

This is really easy. Extract the package and move the clamd directory to your plugins directory in your CakePHP application.

  1. ~$ tar -zxvf clamd-0.1.tar.gz
  2. ~$ mv clamd your-cakephp/app/plugins/

Configuration

Start by including the Clamd plugin.

  1. App::import('Core', 'clamd.Clamd');

When you create the Clamd object you can pass the configuration to the constructor. The configuration consists of options that will be passed to fsockopen(). For example:

  1. $Clamd = new Clamd(
  2.     'host' => '127.0.0.1',
  3.     'port' => 3310,
  4.     'timeout' => 60
  5. );

You can also connect to a local Unix socket. Here is how you connect to the default socket on Debian Lenny:

  1. $Clamd = new Clamd(
  2.     'host' => 'unix:///var/run/clamav/clamd.ctl',
  3.     'port' => 0
  4. );

Usage

To test the Clamd connection you can use the ping() method.

  1. echo $Clamd->ping() ? 'Success!' : $Clamd->lastError();

You can use the scan method to scan a single file. The result will be one on the Clamd constants Clamd::OK, Clamd::FOUND or Clamd::ERROR.

  1. if ($Clamd->scan('/path/to/file', $message) === Clamd::FOUND) {
  2.     echo "The file is infected with '$message'!\n";
  3. }

You can also recursively scan an entire directory. You will get back an array containing the results of the scan. Note that this array only contains files that returned Clamd::FOUND or Clamd::ERROR. Scanned files which are clean will not be returned. A result looks like this:

  1. array(3) {
  2.     ['file'] => '/full/path/to/file'
  3.     ['status'] => self::FOUND | self::ERROR
  4.     ['message'] => virus name | error message
  5. }

Also, by default Clamd stops scanning as soon as the first infection is found. Pass TRUE as the second parameter to scan all files. Example usage:

  1. $results = $Clamd->rscan('/path/to/directory', true);
  2. foreach ($results as $result) {
  3.     if ($result['status'] === Clamd::FOUND) {
  4.         echo "File '$result[file]' is infected with '$result[message]'!\n";
  5.     }
  6. }

The Clamd shell

The Clamd plugin also contains a simple interactive Cake Shell which you can use to test Clamd and scan files interactively. You can start the shell from your app directory with:

  1. ~$ cake clamd

You can use the following commands in the interactive shell:

exit|quit|q
Quit the shell
connect <host> [<port> [<timeout>]]
Connect to a ClamAV daemon
ping
Send a PING command to the clamav daemon
scan <file>
Scan <file> for viruses
rscan <directory>
Recursively scan <directory> for viruses. Only infected files and errors are returned.
help
Show the help
Creative Commons Attribution-ShareAlike

Comments

#1 David Persson (http://cakephp.org)

Great idea but why not split the communication through the socket to it's own "vendor" class (PLUGIN/vendors/ClamSocket.php) and then have a data source using the ClamSocket (PLUGIN/models/datasources/clam_source.php) ? Configuration could then be provided in the connections config. Another possibility would be to only have a data source and move everything there.

Anyways good work!

P.s.: I did something like that with the queue plugin: https://github.com/davidpersson/queue/tree

#2 Sander Marechal (http://www.jejik.com)

Thanks. I'll have a look at using a DataSource.

By the way, we apparently think alike. Guess what I wrote at the same time as the Clamd plugin? A Beantalkd Queue plugin for CakePHP so I could run the scanning process in the background :-)

Comments have been retired for this article.