Differences Between: [Versions 310 and 311] [Versions 311 and 400] [Versions 311 and 401] [Versions 311 and 402] [Versions 311 and 403] [Versions 39 and 311]
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 110 // Get custom message to display to user from antivirus engine. 111 $displaymessage = $antivirus->get_virus_found_message(); 112 $placeholders = array_merge(['item' => $filename], $displaymessage['placeholders']); 113 114 throw new \core\antivirus\scanner_exception( 115 $displaymessage['string'], 116 '', 117 $placeholders, 118 null, 119 $displaymessage['component'] 120 ); 121 } else if ($result === $antivirus::SCAN_RESULT_ERROR) { 122 // Here we need to generate a different incident based on an error. 123 $incidentdetails = $antivirus->get_incident_details($file, $filename, $notice, false); 124 self::send_antivirus_messages($antivirus, $incidentdetails); 125 } 126 } 127 } 128 129 /** 130 * Scan data steam using all enabled antiviruses, throws exception in case of infected data. 131 * 132 * @param string $data The variable containing the data to scan. 133 * @throws \core\antivirus\scanner_exception If data is infected. 134 * @return void 135 */ 136 public static function scan_data($data) { 137 global $USER; 138 $antiviruses = self::get_enabled(); 139 foreach ($antiviruses as $antivirus) { 140 // Attempt to scan, catching internal exceptions. 141 try { 142 $result = $antivirus->scan_data($data); 143 } catch (\core\antivirus\scanner_exception $e) { 144 // If there was a scanner exception (such as ClamAV denying upload), send messages and rethrow. 145 $notice = $antivirus->get_scanning_notice(); 146 $filename = get_string('datastream', 'antivirus'); 147 $incidentdetails = $antivirus->get_incident_details('', $filename, $notice, false); 148 self::send_antivirus_messages($antivirus, $incidentdetails); 149 150 throw $e; 151 } 152 153 $filename = get_string('datastream', 'antivirus'); 154 $notice = $antivirus->get_scanning_notice(); 155 156 if ($result === $antivirus::SCAN_RESULT_FOUND) { 157 // Infection found, send notification. 158 $incidentdetails = $antivirus->get_incident_details('', $filename, $notice); 159 self::send_antivirus_messages($antivirus, $incidentdetails); 160 161 // Copy data to quarantine folder. 162 $zipfile = \core\antivirus\quarantine::quarantine_data($data, $filename, $incidentdetails, $notice); 163 // If file not stored due to disabled quarantine, store a message. 164 if (empty($zipfile)) { 165 $zipfile = get_string('quarantinedisabled', 'antivirus'); 166 } 167 168 // Log file infected event. 169 $params = [ 170 'context' => \context_system::instance(), 171 'relateduserid' => $USER->id, 172 'other' => ['filename' => $filename, 'zipfile' => $zipfile, 'incidentdetails' => $incidentdetails], 173 ]; 174 $event = \core\event\virus_infected_data_detected::create($params); 175 $event->trigger(); 176 177 // Get custom message to display to user from antivirus engine. 178 $displaymessage = $antivirus->get_virus_found_message(); 179 $placeholders = array_merge(['item' => get_string('datastream', 'antivirus')], $displaymessage['placeholders']); 180 181 throw new \core\antivirus\scanner_exception( 182 $displaymessage['string'], 183 '', 184 $placeholders, 185 null, 186 $displaymessage['component'] 187 ); 188 } else if ($result === $antivirus::SCAN_RESULT_ERROR) { 189 // Here we need to generate a different incident based on an error. 190 $incidentdetails = $antivirus->get_incident_details('', $filename, $notice, false); 191 self::send_antivirus_messages($antivirus, $incidentdetails); 192 } 193 } 194 } 195 196 /** 197 * Returns instance of antivirus. 198 * 199 * @param string $antivirusname name of antivirus. 200 * @return object|bool antivirus instance or false if does not exist. 201 */ 202 public static function get_antivirus($antivirusname) { 203 global $CFG; 204 205 $classname = '\\antivirus_' . $antivirusname . '\\scanner'; 206 if (!class_exists($classname)) { 207 return false; 208 } 209 return new $classname(); 210 } 211 212 /** 213 * Get the list of available antiviruses. 214 * 215 * @return array Array ('antivirusname'=>'localised antivirus name'). 216 */ 217 public static function get_available() { 218 $antiviruses = array(); 219 foreach (\core_component::get_plugin_list('antivirus') as $antivirusname => $dir) { 220 $antiviruses[$antivirusname] = get_string('pluginname', 'antivirus_'.$antivirusname); 221 } 222 return $antiviruses; 223 } 224 225 /** 226 * This function puts all relevant information into the messages required, and sends them. 227 * 228 * @param \core\antivirus\scanner $antivirus the scanner engine. 229 * @param string $incidentdetails details of the incident. 230 * @return void 231 */ 232 public static function send_antivirus_messages(\core\antivirus\scanner $antivirus, string $incidentdetails) { 233 $messages = $antivirus->get_messages(); 234 235 // If there is no messages, and a virus is found, we should generate one, then send it. 236 if (empty($messages)) { 237 $antivirus->message_admins($antivirus->get_scanning_notice(), FORMAT_MOODLE, 'infected'); 238 $messages = $antivirus->get_messages(); 239 } 240 241 foreach ($messages as $message) { 242 243 // Check if the information is already in the current scanning notice. 244 if (!empty($antivirus->get_scanning_notice()) && 245 strpos($antivirus->get_scanning_notice(), $message->fullmessage) === false) { 246 // This is some extra information. We should append this to the end of the incident details. 247 $incidentdetails .= \html_writer::tag('pre', $message->fullmessage); 248 } 249 250 // Now update the message to the detailed version, and format. 251 $message->name = 'infected'; 252 $message->fullmessagehtml = $incidentdetails; 253 $message->fullmessageformat = FORMAT_MOODLE; 254 $message->fullmessage = format_text_email($incidentdetails, $message->fullmessageformat); 255 256 // Now we must check if message is going to a real account. 257 // It may be an email that needs to be sent to non-user address. 258 if ($message->userto->id === -1) { 259 // If this doesnt exist, send a regular email. 260 email_to_user( 261 $message->userto, 262 get_admin(), 263 $message->subject, 264 $message->fullmessage, 265 $message->fullmessagehtml 266 ); 267 } else { 268 // And now we can send. 269 message_send($message); 270 } 271 } 272 } 273 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body