Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.2.x will end 22 April 2024 (12 months).
  • Bug fixes for security issues in 4.2.x will end 7 October 2024 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.1.x is supported too.
<?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/>.

/**
 * Base 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();
require_once($CFG->dirroot . '/iplookup/lib.php');

/**
 * Base abstract antivirus scanner class.
 *
 * @package    core
 * @subpackage antivirus
 * @copyright  2015 Ruslan Kabalin, Lancaster University.
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 */
abstract class scanner {
    /** Scanning result indicating no virus found. */
    const SCAN_RESULT_OK = 0;
    /** Scanning result indicating that virus is found. */
    const SCAN_RESULT_FOUND = 1;
    /** Scanning result indicating the error. */
    const SCAN_RESULT_ERROR = 2;

< /** @var stdClass the config for antivirus */
> /** @var \stdClass the config for antivirus */
protected $config; /** @var string scanning notice */ protected $scanningnotice = ''; /** @var array any admin messages generated by a plugin. */ protected $messages = []; /** * Class constructor. * * @return void. */ public function __construct() { // Populate config variable, inheriting class namespace is matching // full plugin name, so we can use it directly to retrieve plugin // configuration. $ref = new \ReflectionClass(get_class($this)); $this->config = get_config($ref->getNamespaceName()); } /** * Config get method. * * @param string $property config property to get. * @return mixed * @throws \coding_exception */ public function get_config($property) { if (property_exists($this->config, $property)) { return $this->config->$property; } throw new \coding_exception('Config property "' . $property . '" doesn\'t exist'); } /** * Get scanning notice. * * @return string */ public function get_scanning_notice() { return $this->scanningnotice; } /** * Set scanning notice. * * @param string $notice notice to set. * @return void */ protected function set_scanning_notice($notice) { $this->scanningnotice = $notice; } /** * Are the antivirus settings configured? * * @return bool True if plugin has been configured. */ public abstract function is_configured(); /** * Scan 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). * @return int Scanning result constants. */ public abstract function scan_file($file, $filename); /** * Scan data. * * By default it saves data variable content to file and then scans it using * scan_file method, however if antivirus plugin permits scanning data directly, * the method can be overridden. * * @param string $data The variable containing the data to scan. * @return int Scanning result constants. */ public function scan_data($data) { // Prepare temp file. $tempdir = make_request_directory(); $tempfile = $tempdir . DIRECTORY_SEPARATOR . rand(); file_put_contents($tempfile, $data); // Perform a virus scan now. return $this->scan_file($tempfile, get_string('datastream', 'antivirus')); } /** * This function pushes given messages into the message queue, which will be sent by the antivirus manager. * * @param string $notice The body of the email to be sent. * @param string $format The body format. * @param string $eventname event name * @return void * @throws \coding_exception * @throws \moodle_exception */ public function message_admins($notice, $format = FORMAT_PLAIN, $eventname = 'errors') { $noticehtml = $format !== FORMAT_PLAIN ? format_text($notice, $format) : ''; $site = get_site(); $subject = get_string('emailsubject', 'antivirus', format_string($site->fullname)); $notifyemail = get_config('antivirus', 'notifyemail'); // If one email address is specified, construct a message to fake account. if (!empty($notifyemail)) { $user = new \stdClass(); $user->id = -1; $user->email = $notifyemail; $user->mailformat = 1; $admins = [$user]; } else { // Otherwise, we message all admins. $admins = get_admins(); } foreach ($admins as $admin) { $eventdata = new \core\message\message(); $eventdata->courseid = SITEID; $eventdata->component = 'moodle'; $eventdata->name = $eventname; $eventdata->userfrom = get_admin(); $eventdata->userto = $admin; $eventdata->subject = $subject; $eventdata->fullmessage = $notice; $eventdata->fullmessageformat = $format; $eventdata->fullmessagehtml = $noticehtml; $eventdata->smallmessage = ''; // Now add the message to an array to be sent by the antivirus manager. $this->messages[] = $eventdata; } } /** * Return incident details * * @param string $file full path to the file * @param string $filename original name of the file * @param string $notice notice from antivirus * @param string $virus if this template is due to a virus found. * @return string the incident details * @throws \coding_exception */ public function get_incident_details($file = '', $filename = '', $notice = '', $virus = true) { global $OUTPUT, $USER; if (empty($notice)) { $notice = $this->get_scanning_notice(); } $classname = get_class($this); $component = explode('\\', $classname)[0]; $content = new \stdClass(); $unknown = get_string('unknown', 'antivirus'); $content->header = get_string('emailinfectedfiledetected', 'antivirus'); $content->filename = !empty($filename) ? $filename : $unknown; $content->scanner = $component; // Check for empty file, or file not uploaded. if (!empty($file) && filesize($file) !== false) { $content->filesize = display_size(filesize($file)); $content->contenthash = \file_storage::hash_from_path($file); $content->contenttype = mime_content_type($file); } else { $content->filesize = $unknown; $content->contenthash = $unknown; $content->contenttype = $unknown; } $content->author = \core_user::is_real_user($USER->id) ? fullname($USER) . " ($USER->username)" : $unknown; $content->ipaddress = getremoteaddr(); $geoinfo = iplookup_find_location(getremoteaddr()); $content->geoinfo = $geoinfo['city'] . ', ' . $geoinfo['country']; $content->date = userdate(time(), get_string('strftimedatetimeshort')); $content->referer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : $unknown; $content->notice = $notice; $report = new \moodle_url('/report/infectedfiles/index.php'); $content->report = $report->out(); // If this is not due to a virus, we need to change the header line. if (!$virus) { $content->header = get_string('emailscannererrordetected', 'antivirus'); } return $OUTPUT->render_from_template('core/infected_file_email', $content); } /** * Getter method for messages queued by the antivirus scanner. * * @return array */ public function get_messages() : array { return $this->messages; } /** * Getter method for the antivirus message displayed in the exception. * * @return array array of string and component to pass to exception constructor. */ public function get_virus_found_message() { // Base antivirus found string. return ['string' => 'virusfound', 'component' => 'antivirus', 'placeholders' => []]; } }