Merge pull request #1145 from ownantivir/master

Add file-socket-support for files_antivir-app
This commit is contained in:
Bart Visscher 2013-07-19 08:28:28 -07:00
Родитель fdfdae8c52 c19c127961
Коммит 4a6a81a470
9 изменённых файлов: 431 добавлений и 18 удалений

Просмотреть файл

@ -13,6 +13,7 @@ remove the file if it's infected.
The App is not complete yet, the following works/is done:
* It can be configured to work with the executable or the daemon mode of ClamAV
* If used in daemon mode it can connect through network- or local file-socket
* In daemon mode, it sends files to a remote/local server using INSTREAM command
* When the user uploads a file, it's checked
* If an uploaded file is infected, it's deleted and a notification is shown to the user on screen and an email is sent with details.
@ -35,6 +36,10 @@ The App is not complete yet, the following works/is done:
* Owncloud 4
* ClamAV (Binaries or a server running ClamAV in daemon mode)
## 3rd party software used
* Simplesocketclient for connecting to ClamAV file-socket: http://github.com/kijin/simplesocket
## Install
* Install and enable the App

Просмотреть файл

@ -2,10 +2,10 @@
<info>
<id>files_antivirus</id>
<name>Antivirus App for files</name>
<version>0.4</version>
<version>0.4-1</version>
<description>Verify files for virus using ClamAV</description>
<licence>AGPL</licence>
<author>Manuel Delgado, Bart Visscher</author>
<author>Manuel Delgado, Bart Visscher, thinksilicon.de</author>
<require>5</require>
<types>
<filesystem/>

Просмотреть файл

@ -1,10 +1,18 @@
function av_mode_show_options(str){
if ( str == 'daemon'){
$('p.av_socket').hide('slow');
$('p.av_host').show('slow');
$('p.av_port').show('slow');
$('p.av_chunk_size').show('slow');
$('p.av_path').hide('slow');
} else if (str == 'executable'){
} else if ( str == 'socket' ) {
$('p.av_socket').show('slow');
$('p.av_path').hide('slow');
$('p.av_host').hide('slow');
$('p.av_port').hide('slow');
$('p.av_chunk_size').hide('slow');
} else if (str == 'executable'){
$('p.av_socket').hide('slow');
$('p.av_host').hide('slow');
$('p.av_port').hide('slow');
$('p.av_chunk_size').hide('slow');

Просмотреть файл

@ -1,24 +1,26 @@
<?php $TRANSLATIONS = array(
"Greetings {user}," => "Herzlich Willkommen {user},",
"Sorry, but a malware was detected in a file you tried to upload and it had to be deleted." => "Entschuldige, es wurde Malware in einer Datei gefunden die Du hochladen wolltest und sie musste gelöscht werden.",
"This email is a notification from {host}. Please, do not reply." => "Diese E-Mail ist eine Benachrichtigung von {host}. Bitte nicht antworten.",
"File uploaded: {file}" => "Datei {file} hochgeladen.",
"Sorry, but a malware was detected in a file you tried to upload and it had to be deleted." => "Entschuldigung, in einer Datei, die Sie hochladen wollten, wurde Malware gefunden und sie musste daher gelöscht werden.",
"This email is a notification from {host}. Please, do not reply." => "Diese E-Mail ist eine Benachrichtigung von {host}. Bitte antworten Sie nicht.",
"File uploaded: {file}" => "Datei wurde hochgeladen: {file}",
"Antivirus Configuration" => "Antivirus-Konfiguration",
"Mode" => "Modus",
"Executable" => "ausführbar",
"Daemon" => "Dienst",
"Executable" => "Ausführbar",
"Daemon" => "Dienst (Daemon)",
"Daemon (Socket)" => "Dienst (Socket)",
"Host" => "Host",
"Address of Antivirus Host." => "Adresse des Antivirus-Servers",
"Not required in Executable Mode." => "Nicht benötigt im ausführbaren Modus",
"Address of Antivirus Host." => "Die Adresse des Antivirus-Hosts.",
"Not required in Executable Mode." => "Nicht erforderlich im ausführbaren Modus.",
"Port" => "Port",
"Port number of Antivirus Host." => "Port des Antivirus-Servers",
"Stream Length" => "Stream-Länge",
"ClamAV StreamMaxLength value in bytes." => "ClamAV-Wert 'StreamMaxLength' in Bytes.",
"Port number of Antivirus Host." => "Die Portnummer des Antivirus-Hosts.",
"Stream Length" => "Übertragungslänge",
"ClamAV StreamMaxLength value in bytes." => "ClamAV StreamMaxLength-Wert in Bytes.",
"Path to clamscan" => "Pfad zu clamscan",
"Path to clamscan executable." => "Pfad zur clamscan Datei",
"Not required in Daemon Mode." => "Nicht erforderlich im Dienst Modus",
"Action for infected files found while scanning" => "Aktion für infizierte Dateien die beim Scannen gefunden werden",
"Only log" => "Nur protokollieren",
"Path to clamscan executable." => "Pfad zum clamscan-Programm",
"ClamAV Socket" => "Pfad zum ClamAV-Socket",
"Not required in Daemon Mode." => "Nicht erforderlich im Dienstmodus (Daemon)",
"Action for infected files found while scanning" => "Aktion für infizierte Dateien, welche beim Scannen gefunden wurden",
"Only log" => "Nur loggen",
"Delete file" => "Datei löschen",
"Save" => "Speichern"
);

Просмотреть файл

@ -7,6 +7,7 @@
"Mode" => "Modus",
"Executable" => "Ausführbar",
"Daemon" => "Dienst (Daemon)",
"Daemon (Socket)" => "Dienst (Socket)",
"Host" => "Host",
"Address of Antivirus Host." => "Die Adresse des Antivirus-Hosts.",
"Not required in Executable Mode." => "Nicht erforderlich im ausführbaren Modus.",
@ -16,6 +17,7 @@
"ClamAV StreamMaxLength value in bytes." => "ClamAV StreamMaxLength-Wert in Bytes.",
"Path to clamscan" => "Pfad zu clamscan",
"Path to clamscan executable." => "Pfad zum clamscan-Programm",
"ClamAV Socket" => "Pfad zum ClamAV-Socket",
"Not required in Daemon Mode." => "Nicht erforderlich im Dienstmodus (Daemon)",
"Action for infected files found while scanning" => "Aktion für infizierte Dateien, welche beim Scannen gefunden wurden",
"Only log" => "Nur loggen",

Просмотреть файл

@ -75,6 +75,8 @@ class OC_Files_Antivirus {
return self::_clamav_scan_via_daemon($filepath);
case 'executable':
return self::_clamav_scan_via_exec($filepath);
case 'socket':
return self::_clamav_scan_via_socket($filepath);
}
}
@ -194,4 +196,45 @@ class OC_Files_Antivirus {
return CLAMAV_SCANRESULT_UNCHECKED;
}
}
private static function _clamav_scan_via_socket( $filepath ) {
$av_socket = \OCP\Config::getAppValue( 'files_antivirus', 'av_socket', '' );
require_once( 'simplesocketclient.php' );
$socket = new SimpleSocketClient( $av_socket, false, 5 );
if( $socket->connect() === false ) {
\OCP\Util::writeLog( 'files_antivirus', 'Could not connect to Clamd via socket '.$av_socket.'!', \OCP\Util::ERROR );
return CLAMAV_SCANRESULT_UNCHECKED;
}
$socket->write('SCAN ' . $filepath );
$response = $socket->readline();
$response = trim($response);
$socket->disconnect();
if (!strncmp($response, $filepath . ':', strlen($filepath) + 1)) {
// Cut the filename from the response.
$response = substr($response, strlen($filepath) + 2);
// OK
if ($response === 'OK') {
\OCP\Util::writeLog( 'files_antivirus', 'Result CLEAN!', \OCP\Util::DEBUG );
return CLAMAV_SCANRESULT_CLEAN;
}
// FOUND
if (substr($response, strlen($response) - 5) === 'FOUND') {
$virus = substr($response, 0, strlen($response) - 6);
\OCP\Util::writeLog( 'files_antivirus', 'Virus detected in file. Clamd reported '.$virus , \OCP\Util::WARN );
return CLAMAV_SCANRESULT_INFECTED;
}
// ERROR
if (substr($response, strlen($response) - 5) === 'ERROR') {
substr( $response, 0, strlen( $response ) - 6 );
\OCP\Util::writeLog( 'files_antivirus', 'File could not be scanned. Clamd reported '.$response, \OCP\Util::ERROR );
return CLAMAV_SCANRESULT_UNCHECKED;
}
}
\OCP\Util::writeLog( 'files_antivirus', 'No response from Clamd!', \OCP\Util::ERROR );
return CLAMAV_SCANRESULT_UNCHECKED;
}
}

351
lib/simplesocketclient.php Normal file
Просмотреть файл

@ -0,0 +1,351 @@
<?php
/**
* A Simple Socket Client for PHP 5.2+
*
* This class implements a generic socket client. It can be extended to
* create client libraries working with text-based protocols.
* In addition to socket creation, reading, and writing capabilities,
* this class also supports lazy loading (not connect until actually needed),
* as well as basic key validation and command building helper methods.
*
* URL: http://github.com/kijin/simplesocket
* Version: 0.1.8
*
* Copyright (c) 2010-2011, Kijin Sung <kijin.sung@gmail.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
class SimpleSocketClient
{
/**
* Connection settings.
*
* Although these properties can be manipulated directly by children,
* it is best to keep them in the hands of the parent class.
*/
protected $_con = null;
protected $_host = '';
protected $_port = 0;
protected $_timeout = 0;
/**
* Default host and port.
*
* These properties can be overridden by children to provide default values
* for host and port if they're not passed to the constructor.
*/
protected $_default_host = null;
protected $_default_port = null;
/**
* Constructor.
*
* Host and port must be supplied at the time of instantiation.
*
* @param string The hostname, IP address, or UNIX socket for the server.
* @param int The port of the server, or false for UNIX sockets.
* @param int Connection timeout in seconds. [optional: default is 5]
*/
public function __construct($host = null, $port = null, $timeout = 5)
{
// A quick check for IPv6 addresses. (They contain colons.)
if (strpos($host, ':') !== false && strpos($host, '[') === false)
{
$host = '[' . $host . ']';
}
// Use default values?
if (is_null($host) && is_null($port))
{
$host = $this->_default_host;
$port = $this->_default_port;
}
// Keep the connection info, but don't connect now.
$this->_host = $host;
$this->_port = $port;
$this->_timeout = $timeout;
}
/**
* Connect to the server.
*
* Normally, this method is useful only for debugging purposes, because
* it will be called automatically the first time a read/write operation
* is attempted.
*/
public function connect()
{
// If already connected, don't do anything.
if ($this->_con !== null && $this->_con !== false) return true;
// If a previous connection attempt had failed, do not retry.
if ($this->_con === false) throw new SimpleSocketException('Cannot connect to ' . $this->_host . ' port ' . $this->_port);
// Attempt to connect.
$socket = $this->_port ? ($this->_host . ':' . $this->_port) : ('unix://' . $this->_host);
$this->_con = stream_socket_client($socket, $errno, $errstr, $this->_timeout);
// If there's an error, set $_con to false, and throw an exception.
if (!$this->_con)
{
$this->_con = false;
throw new SimpleSocketException('Cannot connect to ' . $this->_host . ' port ' . $this->_port . ': ' . $errstr . ' (code ' . $errno . ')');
}
// Return true to indicate success.
return true;
}
/**
* Disconnect from the server.
*
* Normally, this method is useful only for debugging purposes, because
* it will be automatically called in the event of an error (resulting in
* reconnection the next time a read/write operation is attempted), as
* well as at the end of the execution of the script.
*/
public function disconnect()
{
// Close the socket.
@fclose($this->_con);
$this->_con = null;
// Return true to indicate success.
return true;
}
/**
* Generic read method.
*
* This method reads a specified number of bytes from the socket.
* By default, it will also read a CRLF sequence (2 bytes) in addition to
* the specified number of bytes, and remove that CRLF sequence once it
* has been read. This is useful for most text-based protocols; however,
* if you do not want such behavior, pass additional 'false' arguments.
*
* @param int The number of bytes to read, or -1 to read until EOF.
* @param bool Whether or not to read CRLF at the end, too. [optional: default is true]
* @return string Data read from the socket.
* @throws Exception If an error occurs while reading from the socket.
*/
public function read($bytes = -1, $autonewline = true)
{
// If not connected yet, connect now.
if ($this->_con === null) $this->connect();
// Read the data from the socket.
$data = stream_get_contents($this->_con, $bytes);
// If $autonewline is true, read 2 more bytes.
if ($autonewline && $bytes !== -1) stream_get_contents($this->_con, 2);
// If the result is false, throw an exception.
if ($data === false)
{
$this->disconnect();
throw new SimpleSocketException('Cannot read ' . $bytes . ' bytes from ' . $this->_host . ' port ' . $this->_port);
}
// Otherwise, return the data.
return $data;
}
/**
* Generic readline method.
*
* This method reads one line from the socket, i.e. it reads until it hits
* a CRLF sequence. By default, that CRLF sequence will be removed from the
* return value. This is useful for most text-based protocols; however,
* if you do not want such behavior, pass an additional 'false' argument.
*
* @param bool Whether or not to strip CRLF from the end of the response. [optional: default is true]
* @return string Data read from the socket.
* @throws Exception If an error occurs while reading from the socket.
*/
public function readline($trim = true)
{
// If not connected yet, connect now.
if ($this->_con === null) $this->connect();
// Read a line from the socket.
$data = fgets($this->_con);
// If the result is false, throw an exception.
if ($data === false)
{
$this->disconnect();
throw new SimpleSocketException('Cannot read a line from ' . $this->_host . ' port ' . $this->_port);
}
// Otherwise, trim and return the data.
if ($trim && substr($data, strlen($data) - 2) === "\r\n") $data = substr($data, 0, strlen($data) - 2);
return $data;
}
/**
* Generic write method.
*
* This method writes a string to the socket. By default, this method will
* write a CRLF sequence in addition to the given string. This is useful
* for most text-based protocols; however, if you do not want such behavior,
* make sure to pass an additional 'false' argument.
*
* @param string The string to write to the socket.
* @param bool Whether or write CRLF in addition to the given string. [optional: default is true]
* @return bool True on success.
* @throws Exception If an error occurs while reading from the socket.
*/
public function write($string, $autonewline = true)
{
// If not connected yet, connect now.
if ($this->_con === null) $this->connect();
// If $autonewline is true, add CRLF to the content.
if ($autonewline) $string .= "\r\n";
// Write the whole string to the socket.
while ($string !== '')
{
// Start writing.
$written = fwrite($this->_con, $string);
// If the result is false, throw an exception.
if ($written === false)
{
$this->disconnect();
throw new SimpleSocketException('Cannot write to ' . $this->_host . ' port ' . $this->_port);
}
// If nothing was written, it probably means we've already done writing.
if ($written == 0) return true;
// Prepare the string for the next write.
$string = substr($string, $written);
}
// Return true to indicate success.
return true;
}
/**
* Generic key validation method.
*
* This method will throw an exception if:
* - The key is empty.
* - The key is more than 250 bytes long.
* - The key contains characters outside of the ASCII printable range.
*
* @param string The key to validate.
* @return bool True of the key is valid.
* @throws Exception If the key is invalid.
*/
public function validate_key($key)
{
if ($key === '') throw new InvalidKeyException('Key is empty');
if (strlen($key) > 250) throw new InvalidKeyException('Key is too long: ' . $key);
if (preg_match('/[^\\x21-\\x7e]/', $key)) throw new InvalidKeyException('Illegal character in key: ' . $key);
return true;
}
/**
* Generic command building method.
*
* This method will accept one or more string arguments, and return them
* all concatenated with one space between each. If this is convenient
* for you, help yourself.
*
* @param string As many arguments as you wish.
* @return string The concatenated string.
*/
public function build_command( /* arguments */ )
{
$args = func_get_args();
return implode(' ' , $args);
}
/**
* Destructor.
*
* Although not really necessary, the destructor will attempt to
* disconnect in case something weird happens.
*/
public function __destruct()
{
@fclose($this->_con);
}
}
/**
* Exception class.
*/
class SimpleSocketException extends Exception { }
class InvalidKeyException extends SimpleSocketException { }

Просмотреть файл

@ -25,6 +25,7 @@ OCP\User::checkAdminUser();
$params = array(
'av_mode' => 'executable',
'av_socket' => '/var/run/clamav/clamd.ctl',
'av_host' => '',
'av_port' => '',
'av_chunk_size' => '1024',

Просмотреть файл

@ -2,8 +2,9 @@
<fieldset class="personalblock">
<legend><strong><?php p($l->t('Antivirus Configuration'));?></strong></legend>
<p class='av_mode'><label for="av_mode"><?php p($l->t('Mode'));?></label>
<select id="av_mode" name="av_mode"><?php print_unescaped(html_select_options(array('executable' => $l->t('Executable'), 'daemon' => $l->t('Daemon')), $_['av_mode'])) ?></select>
<select id="av_mode" name="av_mode"><?php print_unescaped(html_select_options(array('executable' => $l->t('Executable'), 'daemon' => $l->t('Daemon'), 'socket' => $l->t('Daemon (Socket)')), $_['av_mode'])) ?></select>
</p>
<p class='av_socket'><label for="av_socket"><?php p($l->t('Socket'));?></label><input type="text" id="av_socket" name="av_socket" value="<?php p($_['av_socket']); ?>" title="<?php p($l->t('Clamav Socket.')).' '.$l->t('Not required in Executable Mode.'); ?>"></p>
<p class='av_host'><label for="av_host"><?php p($l->t('Host'));?></label><input type="text" id="av_host" name="av_host" value="<?php p($_['av_host']); ?>" title="<?php p($l->t('Address of Antivirus Host.')). ' ' .$l->t('Not required in Executable Mode.');?>"></p>
<p class='av_port'><label for="av_port"><?php p($l->t('Port'));?></label><input type="text" id="av_port" name="av_port" value="<?php p($_['av_port']); ?>" title="<?php p($l->t('Port number of Antivirus Host.')). ' ' .$l->t('Not required in Executable Mode.');?>"></p>
<p class='av_chunk_size'><label for="av_chunk_size"><?php p($l->t('Stream Length'));?></label><input type="text" id="av_chunk_size" name="av_chunk_size" value="<?php p($_['av_chunk_size']); ?>" title="<?php p($l->t('ClamAV StreamMaxLength value in bytes.')). ' ' .$l->t('Not required in Executable Mode.');?>"> bytes</p>