mirror of
https://github.com/moodle/moodle.git
synced 2025-04-14 04:52:36 +02:00
MDL-50907 antivirus_clamav: Implement data scanning using unix sockets.
We use clamav data stream scanning functionality.
This commit is contained in:
parent
bd74bda383
commit
ddf14b32fe
@ -28,6 +28,8 @@ defined('MOODLE_INTERNAL') || die();
|
||||
|
||||
/** Default socket timeout */
|
||||
define('ANTIVIRUS_CLAMAV_SOCKET_TIMEOUT', 10);
|
||||
/** Default socket data stream chunk size */
|
||||
define('ANTIVIRUS_CLAMAV_SOCKET_CHUNKSIZE', 1024);
|
||||
|
||||
/**
|
||||
* Class implementing ClamAV antivirus.
|
||||
@ -67,6 +69,9 @@ class scanner extends \core\antivirus\scanner {
|
||||
|
||||
// Execute the scan using preferable method.
|
||||
$method = 'scan_file_execute_' . $this->get_config('runningmethod');
|
||||
if (!method_exists($this, $method)) {
|
||||
throw new \coding_exception('Attempting to call non-existing method ' . $method);
|
||||
}
|
||||
$return = $this->$method($file);
|
||||
|
||||
if ($return === self::SCAN_RESULT_ERROR) {
|
||||
@ -80,6 +85,32 @@ class scanner extends \core\antivirus\scanner {
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan data.
|
||||
*
|
||||
* @param string $data The varaible containing the data to scan.
|
||||
* @return int Scanning result constant.
|
||||
*/
|
||||
public function scan_data($data) {
|
||||
// We can do direct stream scanning if unixsocket running method is in use,
|
||||
// if not, use default process.
|
||||
if ($this->get_config('runningmethod') === 'unixsocket') {
|
||||
$return = $this->scan_data_execute_unixsocket($data);
|
||||
|
||||
if ($return === self::SCAN_RESULT_ERROR) {
|
||||
$this->message_admins($this->get_scanning_notice());
|
||||
// If plugin settings require us to act like virus on any error,
|
||||
// return SCAN_RESULT_FOUND result.
|
||||
if ($this->get_config('clamfailureonupload') === 'actlikevirus') {
|
||||
return self::SCAN_RESULT_FOUND;
|
||||
}
|
||||
}
|
||||
return $return;
|
||||
} else {
|
||||
return parent::scan_data($data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the string equivalent of a numeric clam error code
|
||||
*
|
||||
@ -186,21 +217,75 @@ class scanner extends \core\antivirus\scanner {
|
||||
// After scanning we revert permissions to initial ones.
|
||||
chmod($file, $perms);
|
||||
// Parse the output.
|
||||
$splitoutput = explode(': ', $output);
|
||||
$message = trim($splitoutput[1]);
|
||||
if ($message === 'OK') {
|
||||
return self::SCAN_RESULT_OK;
|
||||
return $this->parse_unixsocket_response($output);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan data using unix socket.
|
||||
*
|
||||
* We are running INSTREAM command and passing data stream in chunks.
|
||||
* The format of the chunk is: <length><data> where <length> is the size of the following
|
||||
* data in bytes expressed as a 4 byte unsigned integer in network byte order and <data>
|
||||
* is the actual chunk. Streaming is terminated by sending a zero-length chunk.
|
||||
* Do not exceed StreamMaxLength as defined in clamd.conf, otherwise clamd will
|
||||
* reply with INSTREAM size limit exceeded and close the connection.
|
||||
*
|
||||
* @param string $data The varaible containing the data to scan.
|
||||
* @return int Scanning result constant.
|
||||
*/
|
||||
private function scan_data_execute_unixsocket($data) {
|
||||
$socket = stream_socket_client('unix://' . $this->get_config('pathtounixsocket'), $errno, $errstr, ANTIVIRUS_CLAMAV_SOCKET_TIMEOUT);
|
||||
if (!$socket) {
|
||||
// Can't open socket for some reason, notify admins.
|
||||
$notice = get_string('errorcantopensocket', 'antivirus_clamav', "$errstr ($errno)");
|
||||
$this->set_scanning_notice($notice);
|
||||
return self::SCAN_RESULT_ERROR;
|
||||
} else {
|
||||
// Initiate data stream scanning.
|
||||
// Using 'n' as command prefix is forcing clamav to only treat \n as newline delimeter,
|
||||
// this is to avoid unexpected newline characters on different systems.
|
||||
fwrite($socket, "nINSTREAM\n");
|
||||
// Send data in chunks of ANTIVIRUS_CLAMAV_SOCKET_CHUNKSIZE size.
|
||||
while (strlen($data) > 0) {
|
||||
$chunk = substr($data, 0, ANTIVIRUS_CLAMAV_SOCKET_CHUNKSIZE);
|
||||
$data = substr($data, ANTIVIRUS_CLAMAV_SOCKET_CHUNKSIZE);
|
||||
$size = pack('N', strlen($chunk));
|
||||
fwrite($socket, $size);
|
||||
fwrite($socket, $chunk);
|
||||
}
|
||||
// Terminate streaming.
|
||||
fwrite($socket, pack('N', 0));
|
||||
|
||||
$output = stream_get_line($socket, 4096);
|
||||
fclose($socket);
|
||||
|
||||
// Parse the output.
|
||||
return $this->parse_unixsocket_response($output);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse unix socket command response.
|
||||
*
|
||||
* @param string $output The unix socket command response.
|
||||
* @return int Scanning result constant.
|
||||
*/
|
||||
private function parse_unixsocket_response($output) {
|
||||
$splitoutput = explode(': ', $output);
|
||||
$message = trim($splitoutput[1]);
|
||||
if ($message === 'OK') {
|
||||
return self::SCAN_RESULT_OK;
|
||||
} else {
|
||||
$parts = explode(' ', $message);
|
||||
$status = array_pop($parts);
|
||||
if ($status === 'FOUND') {
|
||||
return self::SCAN_RESULT_FOUND;
|
||||
} else {
|
||||
$parts = explode(' ', $message);
|
||||
$status = array_pop($parts);
|
||||
if ($status === 'FOUND') {
|
||||
return self::SCAN_RESULT_FOUND;
|
||||
} else {
|
||||
$notice = get_string('clamfailed', 'antivirus_clamav', $this->get_clam_error_code(2));
|
||||
$notice .= "\n\n" . $output;
|
||||
$this->set_scanning_notice($notice);
|
||||
return self::SCAN_RESULT_ERROR;
|
||||
}
|
||||
$notice = get_string('clamfailed', 'antivirus_clamav', $this->get_clam_error_code(2));
|
||||
$notice .= "\n\n" . $output;
|
||||
$this->set_scanning_notice($notice);
|
||||
return self::SCAN_RESULT_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user