Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403] [Versions 39 and 310]
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 * Manager 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 29 /** 30 * Class used for various antivirus related stuff. 31 * 32 * @package core_antivirus 33 * @copyright 2015 Ruslan Kabalin, Lancaster University. 34 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 35 */ 36 class manager { 37 /** 38 * Returns list of enabled antiviruses. 39 * 40 * @return array Array ('antivirusname'=>stdClass antivirus object). 41 */ 42 private static function get_enabled() { 43 global $CFG; 44 45 $active = array(); 46 if (empty($CFG->antiviruses)) { 47 return $active; 48 } 49 50 foreach (explode(',', $CFG->antiviruses) as $e) { 51 if ($antivirus = self::get_antivirus($e)) { 52 if ($antivirus->is_configured()) { 53 $active[$e] = $antivirus; 54 } 55 } 56 } 57 return $active; 58 } 59 60 /** 61 * Scan file using all enabled antiviruses, throws exception in case of infected file. 62 * 63 * @param string $file Full path to the file. 64 * @param string $filename Name of the file (could be different from physical file if temp file is used). 65 * @param bool $deleteinfected whether infected file needs to be deleted. 66 * @throws \core\antivirus\scanner_exception If file is infected. 67 * @return void 68 */ 69 public static function scan_file($file, $filename, $deleteinfected) { 70 global $USER; 71 $antiviruses = self::get_enabled(); 72 foreach ($antiviruses as $antivirus) { 73 // Attempt to scan, catching internal exceptions. 74 try { 75 $result = $antivirus->scan_file($file, $filename); 76 } catch (\core\antivirus\scanner_exception $e) { 77 // If there was a scanner exception (such as ClamAV denying upload), send messages and rethrow. 78 $notice = $antivirus->get_scanning_notice(); 79 $incidentdetails = $antivirus->get_incident_details($file, $filename, $notice, false); 80 self::send_antivirus_messages($antivirus, $incidentdetails); 81 throw $e; 82 } 83 84 $notice = $antivirus->get_scanning_notice(); 85 if ($result === $antivirus::SCAN_RESULT_FOUND) { 86 // Infection found, send notification. 87 $incidentdetails = $antivirus->get_incident_details($file, $filename, $notice); 88 self::send_antivirus_messages($antivirus, $incidentdetails); 89 90 // Move to quarantine folder. 91 $zipfile = \core\antivirus\quarantine::quarantine_file($file, $filename, $incidentdetails, $notice); 92 // If file not stored due to disabled quarantine, store a message. 93 if (empty($zipfile)) { 94 $zipfile = get_string('quarantinedisabled', 'antivirus'); 95 } 96 97 // Log file infected event. 98 $params = [ 99 'context' => \context_system::instance(), 100 'relateduserid' => $USER->id, 101 'other' => ['filename' => $filename, 'zipfile' => $zipfile, 'incidentdetails' => $incidentdetails], 102 ]; 103 $event = \core\event\virus_infected_file_detected::create($params); 104 $event->trigger(); 105 106 if ($deleteinfected) { 107 unlink($file); 108 } 109 throw new \core\antivirus\scanner_exception('virusfound', '', array('item' => $filename)); 110 } else if ($result === $antivirus::SCAN_RESULT_ERROR) { 111 // Here we need to generate a different incident based on an error. 112 $incidentdetails = $antivirus->get_incident_details($file, $filename, $notice, false); 113 self::send_antivirus_messages($antivirus, $incidentdetails); 114 } 115 } 116 } 117 118 /** 119 * Scan data steam using all enabled antiviruses, throws exception in case of infected data. 120 * 121 * @param string $data The variable containing the data to scan. 122 * @throws \core\antivirus\scanner_exception If data is infected. 123 * @return void 124 */ 125 public static function scan_data($data) { 126 global $USER; 127 $antiviruses = self::get_enabled(); 128 foreach ($antiviruses as $antivirus) { 129 // Attempt to scan, catching internal exceptions. 130 try { 131 $result = $antivirus->scan_data($data); 132 } catch (\core\antivirus\scanner_exception $e) { 133 // If there was a scanner exception (such as ClamAV denying upload), send messages and rethrow. 134 $notice = $antivirus->get_scanning_notice(); 135 $filename = get_string('datastream', 'antivirus'); 136 $incidentdetails = $antivirus->get_incident_details('', $filename, $notice, false); 137 self::send_antivirus_messages($antivirus, $incidentdetails); 138 139 throw $e; 140 } 141 142 $filename = get_string('datastream', 'antivirus'); 143 $notice = $antivirus->get_scanning_notice(); 144 145 if ($result === $antivirus::SCAN_RESULT_FOUND) { 146 // Infection found, send notification. 147 $incidentdetails = $antivirus->get_incident_details('', $filename, $notice); 148 self::send_antivirus_messages($antivirus, $incidentdetails); 149 150 // Copy data to quarantine folder. 151 $zipfile = \core\antivirus\quarantine::quarantine_data($data, $filename, $incidentdetails, $notice); 152 // If file not stored due to disabled quarantine, store a message. 153 if (empty($zipfile)) { 154 $zipfile = get_string('quarantinedisabled', 'antivirus'); 155 } 156 157 // Log file infected event. 158 $params = [ 159 'context' => \context_system::instance(), 160 'relateduserid' => $USER->id, 161 'other' => ['filename' => $filename, 'zipfile' => $zipfile, 'incidentdetails' => $incidentdetails], 162 ]; 163 $event = \core\event\virus_infected_data_detected::create($params); 164 $event->trigger(); 165 166 throw new \core\antivirus\scanner_exception('virusfound', '', array('item' => get_string('datastream', 'antivirus'))); 167 } else if ($result === $antivirus::SCAN_RESULT_ERROR) { 168 // Here we need to generate a different incident based on an error. 169 $incidentdetails = $antivirus->get_incident_details('', $filename, $notice, false); 170 self::send_antivirus_messages($antivirus, $incidentdetails); 171 } 172 } 173 } 174 175 /** 176 * Returns instance of antivirus. 177 * 178 * @param string $antivirusname name of antivirus. 179 * @return object|bool antivirus instance or false if does not exist. 180 */ 181 public static function get_antivirus($antivirusname) { 182 global $CFG; 183 184 $classname = '\\antivirus_' . $antivirusname . '\\scanner'; 185 if (!class_exists($classname)) { 186 return false; 187 } 188 return new $classname(); 189 } 190 191 /** 192 * Get the list of available antiviruses. 193 * 194 * @return array Array ('antivirusname'=>'localised antivirus name'). 195 */ 196 public static function get_available() { 197 $antiviruses = array(); 198 foreach (\core_component::get_plugin_list('antivirus') as $antivirusname => $dir) { 199 $antiviruses[$antivirusname] = get_string('pluginname', 'antivirus_'.$antivirusname); 200 } 201 return $antiviruses; 202 } 203 204 /** 205 * This function puts all relevant information into the messages required, and sends them. 206 * 207 * @param \core\antivirus\scanner $antivirus the scanner engine. 208 * @param string $incidentdetails details of the incident. 209 * @return void 210 */ 211 public static function send_antivirus_messages(\core\antivirus\scanner $antivirus, string $incidentdetails) { 212 $messages = $antivirus->get_messages(); 213 214 // If there is no messages, and a virus is found, we should generate one, then send it. 215 if (empty($messages)) { 216 $antivirus->message_admins($antivirus->get_scanning_notice(), FORMAT_MOODLE, 'infected'); 217 $messages = $antivirus->get_messages(); 218 } 219 220 foreach ($messages as $message) { 221 222 // Check if the information is already in the current scanning notice. 223 if (!empty($antivirus->get_scanning_notice()) && 224 strpos($antivirus->get_scanning_notice(), $message->fullmessage) === false) { 225 // This is some extra information. We should append this to the end of the incident details. 226 $incidentdetails .= \html_writer::tag('pre', $message->fullmessage); 227 } 228 229 // Now update the message to the detailed version, and format. 230 $message->name = 'infected'; 231 $message->fullmessagehtml = $incidentdetails; 232 $message->fullmessageformat = FORMAT_MOODLE; 233 $message->fullmessage = format_text_email($incidentdetails, $message->fullmessageformat); 234 235 // Now we must check if message is going to a real account. 236 // It may be an email that needs to be sent to non-user address. 237 if ($message->userto->id === -1) { 238 // If this doesnt exist, send a regular email. 239 email_to_user( 240 $message->userto, 241 get_admin(), 242 $message->subject, 243 $message->fullmessage, 244 $message->fullmessagehtml 245 ); 246 } else { 247 // And now we can send. 248 message_send($message); 249 } 250 } 251 } 252 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body