Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 4.3.x will end 7 October 2024 (12 months).
  • Bug fixes for security issues in 4.3.x will end 21 April 2025 (18 months).
  • PHP version: minimum PHP 8.0.0 Note: minimum PHP version has increased since Moodle 4.1. PHP 8.2.x is supported too.

Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Base class for antivirus integration.
  19   *
  20   * @package    core_antivirus
  21   * @copyright  2015 Ruslan Kabalin, Lancaster University.
  22   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23   */
  24  
  25  namespace core\antivirus;
  26  
  27  defined('MOODLE_INTERNAL') || die();
  28  require_once($CFG->dirroot . '/iplookup/lib.php');
  29  
  30  /**
  31   * Base abstract antivirus scanner class.
  32   *
  33   * @package    core
  34   * @subpackage antivirus
  35   * @copyright  2015 Ruslan Kabalin, Lancaster University.
  36   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  37   */
  38  abstract class scanner {
  39      /** Scanning result indicating no virus found. */
  40      const SCAN_RESULT_OK = 0;
  41      /** Scanning result indicating that virus is found. */
  42      const SCAN_RESULT_FOUND = 1;
  43      /** Scanning result indicating the error. */
  44      const SCAN_RESULT_ERROR = 2;
  45  
  46      /** @var \stdClass the config for antivirus */
  47      protected $config;
  48      /** @var string scanning notice */
  49      protected $scanningnotice = '';
  50      /** @var array any admin messages generated by a plugin. */
  51      protected $messages = [];
  52  
  53      /**
  54       * Class constructor.
  55       *
  56       * @return void.
  57       */
  58      public function __construct() {
  59          // Populate config variable, inheriting class namespace is matching
  60          // full plugin name, so we can use it directly to retrieve plugin
  61          // configuration.
  62          $ref = new \ReflectionClass(get_class($this));
  63          $this->config = get_config($ref->getNamespaceName());
  64      }
  65  
  66      /**
  67       * Config get method.
  68       *
  69       * @param string $property config property to get.
  70       * @return mixed
  71       * @throws \coding_exception
  72       */
  73      public function get_config($property) {
  74          if (property_exists($this->config, $property)) {
  75              return $this->config->$property;
  76          }
  77          throw new \coding_exception('Config property "' . $property . '" doesn\'t exist');
  78      }
  79  
  80      /**
  81       * Get scanning notice.
  82       *
  83       * @return string
  84       */
  85      public function get_scanning_notice() {
  86          return $this->scanningnotice;
  87      }
  88  
  89      /**
  90       * Set scanning notice.
  91       *
  92       * @param string $notice notice to set.
  93       * @return void
  94       */
  95      protected function set_scanning_notice($notice) {
  96          $this->scanningnotice = $notice;
  97      }
  98  
  99      /**
 100       * Are the antivirus settings configured?
 101       *
 102       * @return bool True if plugin has been configured.
 103       */
 104      public abstract function is_configured();
 105  
 106      /**
 107       * Scan file.
 108       *
 109       * @param string $file Full path to the file.
 110       * @param string $filename Name of the file (could be different from physical file if temp file is used).
 111       * @return int Scanning result constants.
 112       */
 113      public abstract function scan_file($file, $filename);
 114  
 115      /**
 116       * Scan data.
 117       *
 118       * By default it saves data variable content to file and then scans it using
 119       * scan_file method, however if antivirus plugin permits scanning data directly,
 120       * the method can be overridden.
 121       *
 122       * @param string $data The variable containing the data to scan.
 123       * @return int Scanning result constants.
 124       */
 125      public function scan_data($data) {
 126          // Prepare temp file.
 127          $tempdir = make_request_directory();
 128          $tempfile = $tempdir . DIRECTORY_SEPARATOR . rand();
 129          file_put_contents($tempfile, $data);
 130  
 131          // Perform a virus scan now.
 132          return $this->scan_file($tempfile, get_string('datastream', 'antivirus'));
 133      }
 134  
 135      /**
 136       * This function pushes given messages into the message queue, which will be sent by the antivirus manager.
 137       *
 138       * @param string $notice The body of the email to be sent.
 139       * @param string $format The body format.
 140       * @param string $eventname event name
 141       * @return void
 142       * @throws \coding_exception
 143       * @throws \moodle_exception
 144       */
 145      public function message_admins($notice, $format = FORMAT_PLAIN, $eventname = 'errors') {
 146          $noticehtml = $format !== FORMAT_PLAIN ? format_text($notice, $format) : '';
 147          $site = get_site();
 148  
 149          $subject = get_string('emailsubject', 'antivirus', format_string($site->fullname));
 150          $notifyemail = get_config('antivirus', 'notifyemail');
 151          // If one email address is specified, construct a message to fake account.
 152          if (!empty($notifyemail)) {
 153              $user = new \stdClass();
 154              $user->id = -1;
 155              $user->email = $notifyemail;
 156              $user->mailformat = 1;
 157              $admins = [$user];
 158          } else {
 159              // Otherwise, we message all admins.
 160              $admins = get_admins();
 161          }
 162  
 163          foreach ($admins as $admin) {
 164              $eventdata = new \core\message\message();
 165              $eventdata->courseid          = SITEID;
 166              $eventdata->component         = 'moodle';
 167              $eventdata->name              = $eventname;
 168              $eventdata->userfrom          = get_admin();
 169              $eventdata->userto            = $admin;
 170              $eventdata->subject           = $subject;
 171              $eventdata->fullmessage       = $notice;
 172              $eventdata->fullmessageformat = $format;
 173              $eventdata->fullmessagehtml   = $noticehtml;
 174              $eventdata->smallmessage      = '';
 175  
 176              // Now add the message to an array to be sent by the antivirus manager.
 177              $this->messages[] = $eventdata;
 178          }
 179      }
 180  
 181      /**
 182       * Return incident details
 183       *
 184       * @param string $file full path to the file
 185       * @param string $filename original name of the file
 186       * @param string $notice notice from antivirus
 187       * @param string $virus if this template is due to a virus found.
 188       * @return string the incident details
 189       * @throws \coding_exception
 190       */
 191      public function get_incident_details($file = '', $filename = '', $notice = '', $virus = true) {
 192          global $OUTPUT, $USER;
 193          if (empty($notice)) {
 194              $notice = $this->get_scanning_notice();
 195          }
 196          $classname = get_class($this);
 197          $component = explode('\\', $classname)[0];
 198  
 199          $content = new \stdClass();
 200          $unknown = get_string('unknown', 'antivirus');
 201          $content->header = get_string('emailinfectedfiledetected', 'antivirus');
 202          $content->filename = !empty($filename) ? $filename : $unknown;
 203          $content->scanner = $component;
 204          // Check for empty file, or file not uploaded.
 205          if (!empty($file) && filesize($file) !== false) {
 206              $content->filesize = display_size(filesize($file));
 207              $content->contenthash = \file_storage::hash_from_path($file);
 208              $content->contenttype = mime_content_type($file);
 209          } else {
 210              $content->filesize = $unknown;
 211              $content->contenthash = $unknown;
 212              $content->contenttype = $unknown;
 213          }
 214  
 215          $content->author = \core_user::is_real_user($USER->id) ? fullname($USER) . " ($USER->username)" : $unknown;
 216          $content->ipaddress = getremoteaddr();
 217          $geoinfo = iplookup_find_location(getremoteaddr());
 218          $content->geoinfo = $geoinfo['city'] . ', ' . $geoinfo['country'];
 219          $content->date = userdate(time(), get_string('strftimedatetimeshort'));
 220          $content->referer = isset($_SERVER['HTTP_REFERER']) ? $_SERVER['HTTP_REFERER'] : $unknown;
 221          $content->notice = $notice;
 222          $report = new \moodle_url('/report/infectedfiles/index.php');
 223          $content->report = $report->out();
 224  
 225          // If this is not due to a virus, we need to change the header line.
 226          if (!$virus) {
 227              $content->header = get_string('emailscannererrordetected', 'antivirus');
 228          }
 229  
 230          return $OUTPUT->render_from_template('core/infected_file_email', $content);
 231      }
 232  
 233      /**
 234       * Getter method for messages queued by the antivirus scanner.
 235       *
 236       * @return array
 237       */
 238      public function get_messages() : array {
 239          return $this->messages;
 240      }
 241  
 242      /**
 243       * Getter method for the antivirus message displayed in the exception.
 244       *
 245       * @return array array of string and component to pass to exception constructor.
 246       */
 247      public function get_virus_found_message() {
 248          // Base antivirus found string.
 249          return ['string' => 'virusfound', 'component' => 'antivirus', 'placeholders' => []];
 250      }
 251  }