From b89abd5caed344f67bcdfcccec75055911ca3203 Mon Sep 17 00:00:00 2001 From: Bart Visscher Date: Mon, 17 Dec 2012 18:05:02 +0100 Subject: [PATCH] Move code into subdirectory --- README.md | 50 ++++++++++ appinfo/app.php | 27 +++++ appinfo/info.xml | 13 +++ appinfo/version | 1 + js/settings.js | 20 ++++ lib/clamav.php | 198 +++++++++++++++++++++++++++++++++++++ settings.php | 48 +++++++++ templates/notification.php | 4 + templates/settings.php | 13 +++ 9 files changed, 374 insertions(+) create mode 100755 README.md create mode 100755 appinfo/app.php create mode 100755 appinfo/info.xml create mode 100755 appinfo/version create mode 100755 js/settings.js create mode 100755 lib/clamav.php create mode 100755 settings.php create mode 100755 templates/notification.php create mode 100755 templates/settings.php diff --git a/README.md b/README.md new file mode 100755 index 0000000..2bffa25 --- /dev/null +++ b/README.md @@ -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 diff --git a/appinfo/app.php b/appinfo/app.php new file mode 100755 index 0000000..84c1ec6 --- /dev/null +++ b/appinfo/app.php @@ -0,0 +1,27 @@ +. +* +*/ + +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'); diff --git a/appinfo/info.xml b/appinfo/info.xml new file mode 100755 index 0000000..c0d495c --- /dev/null +++ b/appinfo/info.xml @@ -0,0 +1,13 @@ + + + files_antivirus + Antivirus App for files + Verify files for virus using ClamAV + AGPL + Manuel Delgado + 4 + false + + + + diff --git a/appinfo/version b/appinfo/version new file mode 100755 index 0000000..3b04cfb --- /dev/null +++ b/appinfo/version @@ -0,0 +1 @@ +0.2 diff --git a/js/settings.js b/js/settings.js new file mode 100755 index 0000000..94db0ff --- /dev/null +++ b/js/settings.js @@ -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(); +}); diff --git a/lib/clamav.php b/lib/clamav.php new file mode 100755 index 0000000..97db1b9 --- /dev/null +++ b/lib/clamav.php @@ -0,0 +1,198 @@ +. +* +*/ + +// 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: FOUND + // filename: 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; + } + } +} diff --git a/settings.php b/settings.php new file mode 100755 index 0000000..16dedf6 --- /dev/null +++ b/settings.php @@ -0,0 +1,48 @@ +. + * + */ + +$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(); diff --git a/templates/notification.php b/templates/notification.php new file mode 100755 index 0000000..a9342fc --- /dev/null +++ b/templates/notification.php @@ -0,0 +1,4 @@ +

t('Greetings {user},')); ?>

+

t('Sorry, but a malware was detected in a file you tried to upload and it had to be deleted.'); ?>
+ t('This email is a notification from {host}. Please, do not reply.')); ?>

+

t('File uploaded: {file}')); ?>

diff --git a/templates/settings.php b/templates/settings.php new file mode 100755 index 0000000..293efb5 --- /dev/null +++ b/templates/settings.php @@ -0,0 +1,13 @@ +
+
+ t('Antivirus Configuration');?> +

+ +

+

+

+

bytes

+

+ +
+