clamav.inc in ClamAV 7
Same filename and directory in other branches
clamav.inc API and helper functions for the ClamAV module.
File
clamav.incView source
<?php
/**
* @file clamav.inc
* API and helper functions for the ClamAV module.
*/
/**
* Scan a file and raise an error on the form element (if required).
*
* @param String $filepath Path to the file to test
* @param String $form_element The form element to set an error on.
* E.g. foo
* E.g. foo][bar][baz
*/
function clamav_scan($filepath, $form_element) {
$result = clamav_scan_file($filepath);
if ($result == CLAMAV_SCANRESULT_INFECTED) {
form_set_error($form_element, t('A virus has been detected in the file. The file will not be accepted.'));
}
elseif ($result == CLAMAV_SCANRESULT_UNCHECKED && variable_get('clamav_unchecked_files', CLAMAV_DEFAULT_UNCHECKED) == CLAMAV_BLOCK_UNCHECKED) {
form_set_error($form_element, t('The anti-virus scanner was not able to check the file. The file cannot be uploaded.'));
}
}
/**
* Scan a single file
*
* @param String $filepath
* Full filepath to the file which is to be scanned. This must be a file which
* is readable by the web server.
* @param optional String $filename
* Filename of the uploaded file. This is used to log the original name of the
* uploaded file ($filepath will typically be a random string generated by
* PHP). Defaults to $filepath if not provided.
*
* @return int
* one of:
* - CLAMAV_SCANRESULT_UNCHECKED
* - CLAMAV_SCANRESULT_CLEAN
* - CLAMAV_SCANRESULT_INFECTED
*/
function clamav_scan_file($filepath, $filename = NULL) {
if (is_null($filename)) {
// Use the filepath to provide a default for the filename.
$filename = basename($filepath);
}
else {
// Ensure that the filename doesn't include the path.
$filename = basename($filename);
}
switch (variable_get('clamav_mode', CLAMAV_DEFAULT_MODE)) {
case CLAMAV_USE_DAEMON:
return _clamav_scan_via_daemon($filepath, $filename);
case CLAMAV_USE_EXECUTABLE:
return _clamav_scan_via_exec($filepath, $filename);
case CLAMAV_USE_DAEMON_UNIX_SOCKET:
return _clamav_scan_via_unix_socket($filepath, $filename);
default:
watchdog('ClamAV', 'Unrecognised configuration.', array(), WATCHDOG_ERROR);
return CLAMAV_SCANRESULT_UNCHECKED;
}
}
/**
* Scan a single file using a daemon.
*
* @param String $filepath
* Full filepath to the file which is to be scanned. This must be a file which
* is readable by the web server.
* @param String $filename
* Filename of the uploaded file (used for logging).
*
* @return int
* one of:
* - CLAMAV_SCANRESULT_UNCHECKED
* - CLAMAV_SCANRESULT_CLEAN
* - CLAMAV_SCANRESULT_INFECTED
*/
function _clamav_scan_via_daemon($filepath, $filename) {
$host = variable_get('clamav_daemon_host', CLAMAV_DEFAULT_HOST);
$port = variable_get('clamav_daemon_port', CLAMAV_DEFAULT_PORT);
// try to open a socket to clamav
$handler = $host && $port ? @fsockopen($host, $port) : FALSE;
if (!$handler) {
watchdog('clamav', 'The clamav module can not connect to the clamav daemon over TCP/IP. The uploaded file %filename could not be scanned.', array(
'%filename' => $filename,
), WATCHDOG_WARNING);
return CLAMAV_SCANRESULT_UNCHECKED;
}
return _clamav_scan_via_daemon_handler($handler, $filepath, $filename);
}
/**
* Scan a single file by connecting to ClamAV over a unix socket.
*
* @param String $filepath
* Full filepath to the file which is to be scanned. This must be a file which
* is readable by the web server.
* @param String $filename
* Filename of the uploaded file (used for logging).
*
* @return int
* one of:
* - CLAMAV_SCANRESULT_UNCHECKED
* - CLAMAV_SCANRESULT_CLEAN
* - CLAMAV_SCANRESULT_INFECTED
*/
function _clamav_scan_via_unix_socket($filepath, $filename) {
$socket_path = variable_get('clamav_daemon_unix_socket', CLAMAV_DEFAULT_UNIX_SOCKET);
$socket = "unix://{$socket_path}";
// try to open a socket to clamav
$handler = $socket_path ? @fsockopen($socket) : FALSE;
if (!$handler) {
watchdog('clamav', 'The clamav module can not connect to the clamav daemon over unix socket. The uploaded file %filename could not be scanned.', array(
'%filename' => $filename,
), WATCHDOG_WARNING);
return CLAMAV_SCANRESULT_UNCHECKED;
}
return _clamav_scan_via_daemon_handler($handler, $filepath, $filename);
}
/**
* Scan a single file via ClamAV daemon.
*
*
* @param resource $handler
* An opened connection to a daemon socket resource.
* @param String $filepath
* Full filepath to the file which is to be scanned. This must be a file which
* is readable by the web server.
* @param String $filename
* Filename of the uploaded file (used for logging).
*
* @return int
* one of:
* - CLAMAV_SCANRESULT_UNCHECKED
* - CLAMAV_SCANRESULT_CLEAN
* - CLAMAV_SCANRESULT_INFECTED
*/
function _clamav_scan_via_daemon_handler($handler, $filepath, $filename) {
// Request a scan from the daemon.
$filehandler = fopen($filepath, 'r');
if ($filehandler) {
// Open a request with the daemon to stream file data.
fwrite($handler, "zINSTREAM\0");
$bytes = filesize($filepath);
if ($bytes > 0) {
// Tell the daemon how many bytes of data we're sending.
fwrite($handler, pack("N", $bytes));
// Send the file data.
stream_copy_to_stream($filehandler, $handler);
}
// Send a zero-length block to indicate that we're done sending file data.
fwrite($handler, pack("N", 0));
$response = fgets($handler);
fclose($filehandler);
fclose($handler);
$response = trim($response);
}
else {
watchdog('clamav', 'Uploaded file %filename could not be scanned: failed to open file handle.', array(
'%filename' => $filename,
), WATCHDOG_WARNING);
return CLAMAV_SCANRESULT_UNCHECKED;
}
// clamd returns a string response in the format:
// stream: OK
// stream: <name of virus> FOUND
// stream: <error string> ERROR
if (preg_match('/^stream: OK$/', $response)) {
// Log the message to watchdog, if verbose mode is used.
if (variable_get('clamav_verbose', CLAMAV_VERBOSE_DEFAULT)) {
watchdog('clamav', 'File %filename scanned by ClamAV and found clean.', array(
'%filename' => $filename,
), WATCHDOG_INFO);
}
return CLAMAV_SCANRESULT_CLEAN;
}
elseif (preg_match('/^stream: (.*) FOUND$/', $response, $matches)) {
$virus_name = $matches[1];
watchdog('clamav', 'Virus detected in uploaded file %filename. Clamav-daemon reported the virus:<br />@virus_name', array(
'%filename' => $filename,
'@virus_name' => $virus_name,
), WATCHDOG_CRITICAL);
return CLAMAV_SCANRESULT_INFECTED;
}
else {
// try to extract the error message from the response.
preg_match('/^stream: (.*) ERROR$/', $response, $matches);
$error_string = $matches[1];
// the error message given by the daemon
watchdog('clamav', 'Uploaded file %filename could not be scanned. Clamscan reported:<br />@error_string', array(
'%filename' => $filename,
'@error_string' => $error_string,
), WATCHDOG_WARNING);
return CLAMAV_SCANRESULT_UNCHECKED;
}
}
/**
* Scan a single file using a clamav executable.
*
* @param String $filepath
* Full filepath to the file which is to be scanned. This must be a file which
* is readable by the web server.
* @param String $filename
* Filename of the uploaded file (used for logging).
*
* @return int
* one of:
* - CLAMAV_SCANRESULT_UNCHECKED
* - CLAMAV_SCANRESULT_CLEAN
* - CLAMAV_SCANRESULT_INFECTED
*/
function _clamav_scan_via_exec($filepath, $filename) {
// get the path to the executable
$executable = variable_get('clamav_executable_path', CLAMAV_DEFAULT_PATH);
// check that the executable is available
if (!file_exists($executable)) {
watchdog('clamav', "The clamscan executable could not be found at %path", array(
'%path' => $executable,
), WATCHDOG_ERROR);
return CLAMAV_SCANRESULT_UNCHECKED;
}
// optional additional options for the executable
$exec_params = trim(variable_get('clamav_executable_parameters', CLAMAV_DEFAULT_PARAMS));
if (!empty($exec_params)) {
// individually shell-escape multiple options in the string
$exec_params = explode(' ', $exec_params);
$exec_params = array_filter($exec_params);
$exec_params = array_map('escapeshellarg', $exec_params);
$exec_params = implode(' ', $exec_params);
}
$filepath = drupal_realpath($filepath);
// using 2>&1 to grab the full command-line output.
$cmd = escapeshellcmd($executable);
$cmd .= !empty($exec_params) ? ' ' . $exec_params : '';
// already escaped
$cmd .= ' ' . escapeshellarg($filepath) . ' 2>&1';
// exec:
// The lines of text output by clamscan are assigned as an array to $output
// The actual result of clamscan is assigned to $result:
// 0 = clean
// 1 = infected
// x = unchecked
exec($cmd, $output, $result);
/**
* clamscan return values (documented from man clamscan)
* 0 : No virus found.
* 1 : Virus(es) found.
* 2 : Some error(s) occured
*
* Specific return codes below retained for backwards-compatibilty
* (with very old versions of clamscan < 0.96)
*
* 40: Unknown option passed.
* 50: Database initialization error.
* 52: Not supported file type.
* 53: Can't open directory.
* 54: Can't open file. (ofm)
* 55: Error reading file. (ofm)
* 56: Can't stat input file / directory.
* 57: Can't get absolute path name of current working directory.
* 58: I/O error, please check your file system.
* 62: Can't initialize logger.
* 63: Can't create temporary files/directories (check permissions).
* 64: Can't write to temporary directory (please specify another one).
* 70: Can't allocate memory (calloc).
* 71: Can't allocate memory (malloc).
*/
switch ($result) {
case 0:
// Log the message to watchdog, if verbose mode is used.
if (variable_get('clamav_verbose', CLAMAV_VERBOSE_DEFAULT)) {
watchdog('clamav', 'File %filename scanned by ClamAV and found clean.', array(
'%filename' => $filename,
), WATCHDOG_INFO);
}
return CLAMAV_SCANRESULT_CLEAN;
case 1:
watchdog('clamav', 'Virus detected in uploaded file %filename. Clamscan reported:<br />!clamscan_output', array(
'%filename' => $filename,
'!clamscan_output' => _clamav_sanitise_output($output),
), WATCHDOG_CRITICAL);
return CLAMAV_SCANRESULT_INFECTED;
default:
$descriptions = array(
2 => "Some error(s) occured.",
40 => "Unknown option passed.",
50 => "Database initialization error.",
52 => "Not supported file type.",
53 => "Can't open directory.",
54 => "Can't open file. (ofm)",
55 => "Error reading file. (ofm)",
56 => "Can't stat input file / directory.",
57 => "Can't get absolute path name of current working directory.",
58 => "I/O error, please check your file system.",
62 => "Can't initialize logger.",
63 => "Can't create temporary files/directories (check permissions).",
64 => "Can't write to temporary directory (please specify another one).",
70 => "Can't allocate memory (calloc).",
71 => "Can't allocate memory (malloc).",
);
$description = array_key_exists($result, $descriptions) ? $descriptions[$result] : 'unknown error';
watchdog('clamav', 'Uploaded file %filename could not be scanned. Clamscan reported: [@error_code] - @error_description Output:<br />!clamscan_output', array(
'%filename' => $filename,
'@error_code' => $result,
'@error_description' => $description,
'!clamscan_output' => _clamav_sanitise_output($output),
), WATCHDOG_WARNING);
return CLAMAV_SCANRESULT_UNCHECKED;
}
}
/**
* Sanitise output from clamscan.
*
* @param array $output
* Lines of output from clamscan executable.
*
* @return string
* Sanitised output.
*/
function _clamav_sanitise_output($output) {
if (!is_array($output)) {
$output = array(
$output,
);
}
$output = array_map('check_plain', $output);
return implode('<br />', $output);
}
/**
* Get the version information for clamav.
*
* @param Mixed $settings For executable:
* String: path to the clamscan executable
* For daemon:
* Array: providing the keys 'host' and 'port'
*
* @return String
* The version string, as provided by clamav.
*/
function clamav_get_version($settings) {
if (is_string($settings) && !empty($settings)) {
return _clamav_get_version_via_exec($settings);
}
elseif (is_array($settings) && isset($settings['host']) && isset($settings['port'])) {
return _clamav_get_version_via_daemon($settings['host'], $settings['port']);
}
elseif (is_array($settings) && isset($settings['unix_socket_path'])) {
$socket = "unix://{$settings['unix_socket_path']}";
return _clamav_get_version_via_daemon($socket, NULL);
}
}
/**
* Get version information from a clamav executable.
*
* @param String $executable_path
*
* @return String
*/
function _clamav_get_version_via_exec($executable_path) {
if (is_file($executable_path)) {
return exec(escapeshellcmd($executable_path) . ' -V');
}
else {
return NULL;
}
}
/**
* Get version information from a clamav daemon.
*
* @param String $host
* @param Int $port
*
* @return String
*/
function _clamav_get_version_via_daemon($host, $port) {
$handler = @fsockopen($host, $port);
if (!$handler) {
return NULL;
}
fwrite($handler, "VERSION\n");
$content = fgets($handler);
fclose($handler);
return $content;
}
Functions
Name | Description |
---|---|
clamav_get_version | Get the version information for clamav. |
clamav_scan | Scan a file and raise an error on the form element (if required). |
clamav_scan_file | Scan a single file |
_clamav_get_version_via_daemon | Get version information from a clamav daemon. |
_clamav_get_version_via_exec | Get version information from a clamav executable. |
_clamav_sanitise_output | Sanitise output from clamscan. |
_clamav_scan_via_daemon | Scan a single file using a daemon. |
_clamav_scan_via_daemon_handler | Scan a single file via ClamAV daemon. |
_clamav_scan_via_exec | Scan a single file using a clamav executable. |
_clamav_scan_via_unix_socket | Scan a single file by connecting to ClamAV over a unix socket. |