This commit is contained in:
Bart Visscher 2012-12-17 18:05:02 +01:00
Родитель 9596fa1161
Коммит b89abd5cae
9 изменённых файлов: 374 добавлений и 0 удалений

50
README.md Executable file
Просмотреть файл

@ -0,0 +1,50 @@
#Owncloud Antivirus App
files_antivirus is an antivirus app for [Owncloud](https://github.com/owncloud) based on [ClamAV](http://www.clamav.net).
v0.2
##Details
The idea is to check for virus at upload-time, notifying the user (on screen and/or email) and
remove the file if it's infected.
##Status
The App is not complete yet...
* It can be configured to work with the executable or the daemon mode of ClamAV
* 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.
* Tested in Linux only
##In progress
* Test uploading from clients
##ToDo
* Background Job to scan all files
* File size limit
* Configurations Tuneups
* Other OS Testing
* Look for ideas :P
## Requirements
* Owncloud 4
* ClamAV (Binaries or a server running ClamAV in daemon mode)
## Install
* Download App tarball or clone repo
* [master](https://github.com/valarauco/files_antivirus/tarball/master)
* [v0.2](https://github.com/valarauco/files_antivirus/archive/v0.2.tar.gz)
* `git clone git://github.com/valarauco/files_antivirus.git`
* Unpack the tarball inside the apps directory of Owncloud
* Activate the App
* Go to Admin Panel and configure the App
Author:
[Manuel Delgado López](https://github.com/valarauco/) :: manuel.delgado at ucr.ac.cr

27
appinfo/app.php Executable file
Просмотреть файл

@ -0,0 +1,27 @@
<?php
/**
* ownCloud - files_antivirus
*
* @author Manuel Deglado
* @copyright 2012 Manuel Deglado manuel.delgado@ucr.ac.cr
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
OC::$CLASSPATH['OC_Files_Antivirus'] = OC_App::getAppPath('files_antivirus').'/lib/clamav.php';
OC_APP::registerAdmin('files_antivirus', 'settings');
OC_Hook::connect('OC_Filesystem', 'post_write', 'OC_Files_Antivirus', 'av_scan');

13
appinfo/info.xml Executable file
Просмотреть файл

@ -0,0 +1,13 @@
<?xml version="1.0"?>
<info>
<id>files_antivirus</id>
<name>Antivirus App for files</name>
<description>Verify files for virus using ClamAV</description>
<licence>AGPL</licence>
<author>Manuel Delgado</author>
<require>4</require>
<shipped>false</shipped>
<types>
<filesystem/>
</types>
</info>

1
appinfo/version Executable file
Просмотреть файл

@ -0,0 +1 @@
0.2

20
js/settings.js Executable file
Просмотреть файл

@ -0,0 +1,20 @@
function av_mode_show_options(str){
if ( str == 'daemon'){
$('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'){
$('p.av_host').hide('slow');
$('p.av_port').hide('slow');
$('p.av_chunk_size').hide('slow');
$('p.av_path').show('slow');
}
}
$(document).ready(function() {
$("#av_mode").change(function () {
var str = $("#av_mode").val();
av_mode_show_options(str);
});
$("#av_mode").change();
});

198
lib/clamav.php Executable file
Просмотреть файл

@ -0,0 +1,198 @@
<?php
/**
* ownCloud - files_antivirus
*
* @author Manuel Deglado
* @copyright 2012 Manuel Deglado manuel.delgado@ucr.ac.cr
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
// The file was not checked (e.g. because the AV daemon wasn't running).
define('CLAMAV_SCANRESULT_UNCHECKED', -1);
// The file was checked and found to be clean.
define('CLAMAV_SCANRESULT_CLEAN', 0);
// The file was checked and found to be infected.
define('CLAMAV_SCANRESULT_INFECTED', 1);
class OC_Files_Antivirus {
public static function av_scan($path) {
$path=$path[\OC_Filesystem::signal_param_path];
if ($path != '') {
$files_view = \OCP\Files::getStorage("files");
if ($files_view->file_exists($path)) {
$root=OC_User::getHome(OC_User::getUser()).'/files';
$file = $root.$path;
$result = self::clamav_scan($file);
switch($result) {
case CLAMAV_SCANRESULT_UNCHECKED:
//TODO: Show warning to the user: The file can not be checked
break;
case CLAMAV_SCANRESULT_INFECTED:
//remove file
$files_view->unlink($path);
OCP\JSON::error(array("data" => array( "message" => "Virus detected! Can't upload the file." )));
$email = OC_Preferences::getValue(OC_User::getUser(), 'settings', 'email', '');
\OCP\Util::writeLog('files_antivirus', 'Email: '.$email, \OCP\Util::DEBUG);
if (!empty($email) ) {
$tmpl = new OC_Template('files_antivirus', 'notification');
$tmpl->assign('file', $path, false);
$tmpl->assign('host', OCP\Util::getServerHost(), false);
$tmpl->assign('user', OC_User::getUser(), false);
$msg = $tmpl->fetchPage();
$from = 'security-noreply@' . OCP\Util::getServerHost();
\OC_MAIL::send($email, OC_User::getUser(), 'Malware detected', $msg, $from, 'ownCloud', 1);
}
exit();
break;
case CLAMAV_SCANRESULT_CLEAN:
//do nothing
break;
}
}
}
}
private static function clamav_scan($filepath) {
$av_mode = \OCP\Config::getAppValue('files_antivirus', 'av_mode', 'executable');
switch($av_mode) {
case 'daemon':
return self::_clamav_scan_via_daemon($filepath);
case 'executable':
return self::_clamav_scan_via_exec($filepath);
}
}
private static function _clamav_scan_via_daemon($filepath) {
$av_host = \OCP\Config::getAppValue('files_antivirus', 'av_host', '');
$av_port = \OCP\Config::getAppValue('files_antivirus', 'av_port', '');
$av_chunk_size = \OCP\Config::getAppValue('files_antivirus', 'av_chunk_size', '1024');
// try to open a socket to clamav
$shandler = ($av_host && $av_port) ? @fsockopen($av_host, $av_port) : false;
if(!$shandler) {
\OCP\Util::writeLog('files_antivirus', 'The clamav module is not configured for daemon mode.', \OCP\Util::ERROR);
return false;
}
$fhandler = fopen($filepath, "r");
if(!$fhandler) {
\OCP\Util::writeLog('files_antivirus', 'File could not be open.', \OCP\Util::ERROR);
return false;
}
// request scan from the daemon
fwrite($shandler, "nINSTREAM\n");
while (!feof($fhandler)) {
$chunk = fread($fhandler, $av_chunk_size);
$chunk_len = pack('N', strlen($chunk));
fwrite($shandler, $chunk_len.$chunk);
}
fwrite($shandler, pack('N', 0));
$response = fgets($shandler);
\OCP\Util::writeLog('files_antivirus', 'Response :: '.$response, \OCP\Util::WARN);
fclose($shandler);
fclose($fhandler);
// clamd returns a string response in the format:
// filename: OK
// filename: <name of virus> FOUND
// filename: <error string> ERROR
if (preg_match('/.*: OK$/', $response)) {
return CLAMAV_SCANRESULT_CLEAN;
}
elseif (preg_match('/.*: (.*) FOUND$/', $response, $matches)) {
$virus_name = $matches[1];
\OCP\Util::writeLog('files_antivirus', 'Virus detected in file. Clamav reported the virus: '.$virus_name, \OCP\Util::WARN);
return CLAMAV_SCANRESULT_INFECTED;
}
else {
// try to extract the error message from the response.
preg_match('/.*: (.*) ERROR$/', $response, $matches);
$error_string = $matches[1]; // the error message given by the daemon
\OCP\Util::writeLog('files_antivirus', 'File could not be scanned. Clamscan reported: '.$error_string, \OCP\Util::WARN);
return CLAMAV_SCANRESULT_UNCHECKED;
}
}
private static function _clamav_scan_via_exec($filepath) {
\OCP\Util::writeLog('files_antivirus', 'Exec scan: '.$filepath, \OCP\Util::DEBUG);
// get the path to the executable
$av_path = \OCP\Config::getAppValue('files_antivirus', 'av_path', '/usr/bin/clamscan');
// check that the executable is available
if (!file_exists($av_path)) {
\OCP\Util::writeLog('files_antivirus', 'The clamscan executable could not be found at '.$av_path, \OCP\Util::ERROR);
return CLAMAV_SCANRESULT_UNCHECKED;
}
// using 2>&1 to grab the full command-line output.
$cmd = escapeshellcmd($av_path) ." ". escapeshellarg($filepath) . " 2>&1";
exec($cmd, $output, $result);
/**
* clamscan return values (documented from man clamscan)
* 0 : No virus found.
* 1 : Virus(es) found.
* X : Error.
* TODO: add errors?
*/
switch($result) {
case 0:
\OCP\Util::writeLog('files_antivirus', 'Result CLEAN!', \OCP\Util::DEBUG);
return CLAMAV_SCANRESULT_CLEAN;
case 1:
$line = 0;
$report = array();
while ( strpos($output[$line], "--- SCAN SUMMARY ---") === FALSE ) {
if (preg_match('/.*: (.*) FOUND$/', $output[$line], $matches)) {
$report[] = $matches[1];
}
$line++;
}
\OCP\Util::writeLog('files_antivirus', 'Virus detected in file. Clamscan reported: '.implode(', ', $report), \OCP\Util::WARN);
return CLAMAV_SCANRESULT_INFECTED;
default:
$descriptions = array(
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';
\OCP\Util::writeLog('files_antivirus', 'File could not be scanned. Clamscan reported: '.$result, \OCP\Util::WARN);
return CLAMAV_SCANRESULT_UNCHECKED;
}
}
}

48
settings.php Executable file
Просмотреть файл

@ -0,0 +1,48 @@
<?php
/**
* ownCloud - files_antivirus
*
* @author Manuel Deglado
* @copyright 2012 Manuel Deglado manuel.delgado@ucr.ac.cr
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
* License as published by the Free Software Foundation; either
* version 3 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU AFFERO GENERAL PUBLIC LICENSE for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
$params = array(
'av_mode' => 'executable',
'av_host' => '',
'av_port' => '',
'av_chunk_size' => '1024',
'av_path' => '/usr/bin/clamscan',
);
if($_POST){
foreach($params as $param => $default){
if(isset($_POST[$param])){
OCP\Config::setAppValue('files_antivirus', $param, $_POST[$param]);
}
}
}
// fill template
$tmpl = new OC_Template( 'files_antivirus', 'settings');
OCP\Util::addScript('files_antivirus', 'settings');
foreach($params as $param => $default){
$value = OCP\Config::getAppValue('files_antivirus', $param, $default);
$tmpl->assign($param, $value);
}
return $tmpl->fetchPage();

4
templates/notification.php Executable file
Просмотреть файл

@ -0,0 +1,4 @@
<p><?php echo str_replace('{user}', $_['user'], $l->t('Greetings {user},')); ?> </p>
<p style='margin-left:20px'><?php echo $l->t('Sorry, but a malware was detected in a file you tried to upload and it had to be deleted.'); ?> <br />
<?php echo str_replace('{host}', $_['host'], $l->t('This email is a notification from {host}. Please, do not reply.')); ?> </p>
<p style='margin-left:20px'><?php echo str_replace('{file}', $_['file'], $l->t('File uploaded: {file}')); ?> </p>

13
templates/settings.php Executable file
Просмотреть файл

@ -0,0 +1,13 @@
<form id="antivirus" action="#" method="post">
<fieldset class="personalblock">
<legend><strong><?php echo $l->t('Antivirus Configuration');?></strong></legend>
<p class='av_mode'><label for="av_mode"><?php echo $l->t('Mode');?></label>
<select id="av_mode" name="av_mode"><?php echo html_select_options(array('executable' => $l->t('Executable'), 'daemon' => $l->t('Daemon')), $_['av_mode']) ?></select>
</p>
<p class='av_host'><label for="av_host"><?php echo $l->t('Host');?></label><input type="text" id="av_host" name="av_host" value="<?php echo $_['av_host']; ?>" title="<?php echo $l->t('Address of Antivirus Host.'). ' ' .$l->t('Not required in Executable Mode.');?>"></p>
<p class='av_port'><label for="av_port"><?php echo $l->t('Port');?></label><input type="text" id="av_port" name="av_port" value="<?php echo $_['av_port']; ?>" title="<?php echo $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 echo $l->t('Stream Length');?></label><input type="text" id="av_chunk_size" name="av_chunk_size" value="<?php echo $_['av_chunk_size']; ?>" title="<?php echo $l->t('ClamAV StreamMaxLength value in bytes.'). ' ' .$l->t('Not required in Executable Mode.');?>"> bytes</p>
<p class='av_path'><label for="av_path"><?php echo $l->t('Path to clamscan');?></label><input type="text" id="av_path" name="av_path" value="<?php echo $_['av_path']; ?>" title="<?php echo $l->t('Path to clamscan executable.'). ' ' .$l->t('Not required in Daemon Mode.');?>"></p>
<input type="submit" value="<?php echo $l->t('Save');?>" />
</fieldset>
</form>