AutorÃa | Ultima modificación | Ver Log |
<?php// This file is part of Moodle - http://moodle.org///// Moodle is free software: you can redistribute it and/or modify// it under the terms of the GNU General Public License as published by// the Free Software Foundation, either version 3 of the License, or// (at your option) any later version.//// Moodle 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 General Public License for more details.//// You should have received a copy of the GNU General Public License// along with Moodle. If not, see <http://www.gnu.org/licenses/>./*** Manager class for antivirus integration.** @package core_antivirus* @copyright 2015 Ruslan Kabalin, Lancaster University.* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/namespace core\antivirus;defined('MOODLE_INTERNAL') || die();/*** Class used for various antivirus related stuff.** @package core_antivirus* @copyright 2015 Ruslan Kabalin, Lancaster University.* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later*/class manager {/*** Returns list of enabled antiviruses.** @return array Array ('antivirusname'=>stdClass antivirus object).*/private static function get_enabled() {global $CFG;$active = array();if (empty($CFG->antiviruses)) {return $active;}foreach (explode(',', $CFG->antiviruses) as $e) {if ($antivirus = self::get_antivirus($e)) {if ($antivirus->is_configured()) {$active[$e] = $antivirus;}}}return $active;}/*** Scan file using all enabled antiviruses, throws exception in case of infected file.** @param string $file Full path to the file.* @param string $filename Name of the file (could be different from physical file if temp file is used).* @param bool $deleteinfected whether infected file needs to be deleted.* @throws \core\antivirus\scanner_exception If file is infected.* @return void*/public static function scan_file($file, $filename, $deleteinfected) {global $USER;$antiviruses = self::get_enabled();$notifylevel = (int)get_config('antivirus', 'notifylevel');foreach ($antiviruses as $antivirus) {// Attempt to scan, catching internal exceptions.try {$result = $antivirus->scan_file($file, $filename);} catch (\core\antivirus\scanner_exception $e) {$notice = $antivirus->get_scanning_notice();$incidentdetails = $antivirus->get_incident_details($file, $filename, $notice, false);// Log scan error event.$params = ['context' => \context_system::instance(),'relateduserid' => $USER->id,'other' => ['filename' => $filename, 'incidentdetails' => $incidentdetails],];$event = \core\event\antivirus_scan_file_error::create($params);$event->trigger();// If there was a scanner exception (such as ClamAV denying// upload), send messages (on error and above), and rethrow.if ($notifylevel === $antivirus::SCAN_RESULT_ERROR) {$notice = $antivirus->get_scanning_notice();self::send_antivirus_messages($antivirus, $incidentdetails);}throw $e;}$notice = $antivirus->get_scanning_notice();if ($result === $antivirus::SCAN_RESULT_FOUND) {// Infection found, send notification.$incidentdetails = $antivirus->get_incident_details($file, $filename, $notice);self::send_antivirus_messages($antivirus, $incidentdetails);// Move to quarantine folder.$zipfile = \core\antivirus\quarantine::quarantine_file($file, $filename, $incidentdetails, $notice);// If file not stored due to disabled quarantine, store a message.if (empty($zipfile)) {$zipfile = get_string('quarantinedisabled', 'antivirus');}// Log file infected event.$params = ['context' => \context_system::instance(),'relateduserid' => $USER->id,'other' => ['filename' => $filename, 'zipfile' => $zipfile, 'incidentdetails' => $incidentdetails],];$event = \core\event\virus_infected_file_detected::create($params);$event->trigger();if ($deleteinfected) {unlink($file);}// Get custom message to display to user from antivirus engine.$displaymessage = $antivirus->get_virus_found_message();$placeholders = array_merge(['item' => $filename], $displaymessage['placeholders']);throw new \core\antivirus\scanner_exception($displaymessage['string'],'',$placeholders,null,$displaymessage['component']);} else if ($result === $antivirus::SCAN_RESULT_ERROR) {// Here we need to generate a different incident based on an error.$incidentdetails = $antivirus->get_incident_details($file, $filename, $notice, false);// Log scan error event.$params = ['context' => \context_system::instance(),'relateduserid' => $USER->id,'other' => ['filename' => $filename, 'incidentdetails' => $incidentdetails],];$event = \core\event\antivirus_scan_file_error::create($params);$event->trigger();// Send a notification if required (error or above).if ($notifylevel === $antivirus::SCAN_RESULT_ERROR) {self::send_antivirus_messages($antivirus, $incidentdetails);}}}}/*** Scan data steam using all enabled antiviruses, throws exception in case of infected data.** @param string $data The variable containing the data to scan.* @throws \core\antivirus\scanner_exception If data is infected.* @return void*/public static function scan_data($data) {global $USER;$antiviruses = self::get_enabled();$notifylevel = (int)get_config('antivirus', 'notifylevel');foreach ($antiviruses as $antivirus) {// Attempt to scan, catching internal exceptions.try {$result = $antivirus->scan_data($data);} catch (\core\antivirus\scanner_exception $e) {$notice = $antivirus->get_scanning_notice();$incidentdetails = $antivirus->get_incident_details('', $filename, $notice, false);// Log scan error event.$params = ['context' => \context_system::instance(),'relateduserid' => $USER->id,'other' => ['filename' => $filename, 'incidentdetails' => $incidentdetails],];$event = \core\event\antivirus_scan_file_error::create($params);$event->trigger();// If there was a scanner exception (such as ClamAV denying upload), send messages and rethrow.if ($notifylevel === $antivirus::SCAN_RESULT_ERROR) {$notice = $antivirus->get_scanning_notice();$filename = get_string('datastream', 'antivirus');self::send_antivirus_messages($antivirus, $incidentdetails);}throw $e;}$filename = get_string('datastream', 'antivirus');$notice = $antivirus->get_scanning_notice();if ($result === $antivirus::SCAN_RESULT_FOUND) {// Infection found, send notification.$incidentdetails = $antivirus->get_incident_details('', $filename, $notice);self::send_antivirus_messages($antivirus, $incidentdetails);// Copy data to quarantine folder.$zipfile = \core\antivirus\quarantine::quarantine_data($data, $filename, $incidentdetails, $notice);// If file not stored due to disabled quarantine, store a message.if (empty($zipfile)) {$zipfile = get_string('quarantinedisabled', 'antivirus');}// Log file infected event.$params = ['context' => \context_system::instance(),'relateduserid' => $USER->id,'other' => ['filename' => $filename, 'zipfile' => $zipfile, 'incidentdetails' => $incidentdetails],];$event = \core\event\virus_infected_data_detected::create($params);$event->trigger();// Get custom message to display to user from antivirus engine.$displaymessage = $antivirus->get_virus_found_message();$placeholders = array_merge(['item' => get_string('datastream', 'antivirus')], $displaymessage['placeholders']);throw new \core\antivirus\scanner_exception($displaymessage['string'],'',$placeholders,null,$displaymessage['component']);} else if ($result === $antivirus::SCAN_RESULT_ERROR) {// Here we need to generate a different incident based on an error.$incidentdetails = $antivirus->get_incident_details('', $filename, $notice, false);// Log scan error event.$params = ['context' => \context_system::instance(),'relateduserid' => $USER->id,'other' => ['filename' => $filename, 'incidentdetails' => $incidentdetails],];$event = \core\event\antivirus_scan_data_error::create($params);$event->trigger();// Send a notification if required (error or above).if ($notifylevel === $antivirus::SCAN_RESULT_ERROR) {self::send_antivirus_messages($antivirus, $incidentdetails);}}}}/*** Returns instance of antivirus.** @param string $antivirusname name of antivirus.* @return object|bool antivirus instance or false if does not exist.*/public static function get_antivirus($antivirusname) {global $CFG;$classname = '\\antivirus_' . $antivirusname . '\\scanner';if (!class_exists($classname)) {return false;}return new $classname();}/*** Get the list of available antiviruses.** @return array Array ('antivirusname'=>'localised antivirus name').*/public static function get_available() {$antiviruses = array();foreach (\core_component::get_plugin_list('antivirus') as $antivirusname => $dir) {$antiviruses[$antivirusname] = get_string('pluginname', 'antivirus_'.$antivirusname);}return $antiviruses;}/*** This function puts all relevant information into the messages required, and sends them.** @param \core\antivirus\scanner $antivirus the scanner engine.* @param string $incidentdetails details of the incident.* @return void*/public static function send_antivirus_messages(\core\antivirus\scanner $antivirus, string $incidentdetails) {$messages = $antivirus->get_messages();// If there is no messages, and a virus is found, we should generate one, then send it.if (empty($messages)) {$antivirus->message_admins($antivirus->get_scanning_notice(), FORMAT_MOODLE, 'infected');$messages = $antivirus->get_messages();}foreach ($messages as $message) {// Check if the information is already in the current scanning notice.if (!empty($antivirus->get_scanning_notice()) &&strpos($antivirus->get_scanning_notice(), $message->fullmessage) === false) {// This is some extra information. We should append this to the end of the incident details.$incidentdetails .= \html_writer::tag('pre', $message->fullmessage);}// Now update the message to the detailed version, and format.$message->name = 'infected';$message->fullmessagehtml = $incidentdetails;$message->fullmessageformat = FORMAT_MOODLE;$message->fullmessage = format_text_email($incidentdetails, $message->fullmessageformat);// Now we must check if message is going to a real account.// It may be an email that needs to be sent to non-user address.if ($message->userto->id === -1) {// If this doesnt exist, send a regular email.email_to_user($message->userto,get_admin(),$message->subject,$message->fullmessage,$message->fullmessagehtml);} else {// And now we can send.message_send($message);}}}}