See Release Notes
Long Term Support Release
<?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. $result = $antivirus->scan_file($file, $filename); > try {if ($result === $antivirus::SCAN_RESULT_FOUND) {> } catch (\core\antivirus\scanner_exception $e) { // Infection found. > $notice = $antivirus->get_scanning_notice(); if ($deleteinfected) { > $incidentdetails = $antivirus->get_incident_details($file, $filename, $notice, false); unlink($file); > } > // Log scan error event. throw new \core\antivirus\scanner_exception('virusfound', '', array('item' => $filename)); > $params = [ } > 'context' => \context_system::instance(), } > 'relateduserid' => $USER->id, } > 'other' => ['filename' => $filename, 'incidentdetails' => $incidentdetails], > ]; /** > $event = \core\event\antivirus_scan_file_error::create($params); * Scan data steam using all enabled antiviruses, throws exception in case of infected data. > $event->trigger(); * > * @param string $data The varaible containing the data to scan. > // If there was a scanner exception (such as ClamAV denying * @throws \core\antivirus\scanner_exception If data is infected. > // upload), send messages (on error and above), and rethrow. * @return void > if ($notifylevel === $antivirus::SCAN_RESULT_ERROR) { */ > $notice = $antivirus->get_scanning_notice(); public static function scan_data($data) { > self::send_antivirus_messages($antivirus, $incidentdetails); $antiviruses = self::get_enabled(); > } foreach ($antiviruses as $antivirus) { > $result = $antivirus->scan_data($data); > throw $e; if ($result === $antivirus::SCAN_RESULT_FOUND) { > } throw new \core\antivirus\scanner_exception('virusfound', '', array('item' => get_string('datastream', 'antivirus'))); > } > $notice = $antivirus->get_scanning_notice();< // Infection 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(); >< throw new \core\antivirus\scanner_exception('virusfound', '', array('item' => $filename));> > // 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); > }< * @param string $data The varaible containing the data to scan.> * @param string $data The variable containing the data to scan./**> global $USER;* Returns instance of antivirus.> $notifylevel = (int)get_config('antivirus', 'notifylevel');*> // Attempt to scan, catching internal exceptions. * @param string $antivirusname name of antivirus. > try {* @return object|bool antivirus instance or false if does not exist.> } catch (\core\antivirus\scanner_exception $e) { */ > $notice = $antivirus->get_scanning_notice(); public static function get_antivirus($antivirusname) { > $incidentdetails = $antivirus->get_incident_details('', $filename, $notice, false); global $CFG; > > // Log scan error event. $classname = '\\antivirus_' . $antivirusname . '\\scanner'; > $params = [ if (!class_exists($classname)) { > 'context' => \context_system::instance(), return false; > 'relateduserid' => $USER->id, } > 'other' => ['filename' => $filename, 'incidentdetails' => $incidentdetails], return new $classname(); > ]; } > $event = \core\event\antivirus_scan_file_error::create($params); > $event->trigger(); /** > * Get the list of available antiviruses. > // If there was a scanner exception (such as ClamAV denying upload), send messages and rethrow. * > if ($notifylevel === $antivirus::SCAN_RESULT_ERROR) { * @return array Array ('antivirusname'=>'localised antivirus name'). > $notice = $antivirus->get_scanning_notice(); */ > $filename = get_string('datastream', 'antivirus'); public static function get_available() { > self::send_antivirus_messages($antivirus, $incidentdetails); $antiviruses = array(); > } foreach (\core_component::get_plugin_list('antivirus') as $antivirusname => $dir) { > $antiviruses[$antivirusname] = get_string('pluginname', 'antivirus_'.$antivirusname); > throw $e; } > } return $antiviruses; > } > $filename = get_string('datastream', 'antivirus'); } > $notice = $antivirus->get_scanning_notice(); >< throw new \core\antivirus\scanner_exception('virusfound', '', array('item' => get_string('datastream', 'antivirus')));> // 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); > }> } > > /** > * 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); > } > }