diff --git a/admin/antiviruses.php b/admin/antiviruses.php new file mode 100644 index 00000000000..d4d5dcdf037 --- /dev/null +++ b/admin/antiviruses.php @@ -0,0 +1,106 @@ +. + +/** + * Allows admin to configure antiviruses. + * + * @package core_antivirus + * @copyright 2015 Ruslan Kabalin, Lancaster University. + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +require_once('../config.php'); +require_once($CFG->libdir.'/adminlib.php'); +require_once($CFG->libdir.'/tablelib.php'); + +$action = required_param('action', PARAM_ALPHANUMEXT); +$antivirus = required_param('antivirus', PARAM_PLUGIN); +$confirm = optional_param('confirm', 0, PARAM_BOOL); + +$PAGE->set_url('/admin/antiviruses.php', array('action'=>$action, 'antivirus'=>$antivirus)); +$PAGE->set_context(context_system::instance()); + +require_login(); +require_capability('moodle/site:config', context_system::instance()); + +$returnurl = "$CFG->wwwroot/$CFG->admin/settings.php?section=manageantiviruses"; + +// Get currently installed and enabled antivirus plugins. +$available_antiviruses = antiviruses_get_available(); +if (!empty($antivirus) and empty($available_antiviruses[$antivirus])) { + redirect ($returnurl); +} + +$active_antiviruses = explode(',', $CFG->antiviruses); +foreach ($active_antiviruses as $key=>$active) { + if (empty($available_antiviruses[$active])) { + unset($active_antiviruses[$key]); + } +} + +if (!confirm_sesskey()) { + redirect($returnurl); +} + +switch ($action) { + case 'disable': + // Remove from enabled list. + $key = array_search($antivirus, $active_antiviruses); + unset($active_antiviruses[$key]); + break; + + case 'enable': + // Add to enabled list. + if (!in_array($antivirus, $active_antiviruses)) { + $active_antiviruses[] = $antivirus; + $active_antiviruses = array_unique($active_antiviruses); + } + break; + + case 'down': + $key = array_search($antivirus, $active_antiviruses); + // Check auth plugin is valid. + if ($key !== false) { + // move down the list + if ($key < (count($active_antiviruses) - 1)) { + $fsave = $active_antiviruses[$key]; + $active_antiviruses[$key] = $active_antiviruses[$key + 1]; + $active_antiviruses[$key + 1] = $fsave; + } + } + break; + + case 'up': + $key = array_search($antivirus, $active_antiviruses); + // Check auth is valid. + if ($key !== false) { + // move up the list + if ($key >= 1) { + $fsave = $active_antiviruses[$key]; + $active_antiviruses[$key] = $active_antiviruses[$key - 1]; + $active_antiviruses[$key - 1] = $fsave; + } + } + break; + + default: + break; +} + +set_config('antiviruses', implode(',', $active_antiviruses)); +core_plugin_manager::reset_caches(); + +redirect ($returnurl); \ No newline at end of file diff --git a/admin/settings/plugins.php b/admin/settings/plugins.php index 1e3322b1ab0..cd6659ff5b9 100644 --- a/admin/settings/plugins.php +++ b/admin/settings/plugins.php @@ -138,6 +138,16 @@ if ($hassiteconfig) { $plugin->load_settings($ADMIN, 'editorsettings', $hassiteconfig); } +/// Antivirus plugins + $ADMIN->add('modules', new admin_category('antivirussettings', new lang_string('antiviruses', 'antivirus'))); + $temp = new admin_settingpage('manageantiviruses', new lang_string('antivirussettings', 'antivirus')); + $temp->add(new admin_setting_manageantiviruses()); + $ADMIN->add('antivirussettings', $temp); + foreach (core_plugin_manager::instance()->get_plugins_of_type('antivirus') as $plugin) { + /** @var \core\plugininfo\antivirus $plugin */ + $plugin->load_settings($ADMIN, 'antivirussettings', $hassiteconfig); + } + /// License types $ADMIN->add('modules', new admin_category('licensesettings', new lang_string('licenses'))); $temp = new admin_settingpage('managelicenses', new lang_string('managelicenses', 'admin')); diff --git a/lang/en/antivirus.php b/lang/en/antivirus.php new file mode 100644 index 00000000000..77a51b4fd49 --- /dev/null +++ b/lang/en/antivirus.php @@ -0,0 +1,28 @@ +. + +/** + * Strings for component 'antivirus', language 'en' + * + * @package core_antivirus + * @copyright 2015 Ruslan Kabalin, Lancaster University. + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +$string['actantivirushdr'] = 'Available antiviruses'; +$string['antiviruses'] = 'Antiviruses'; +$string['antivirussettings'] = 'Manage antiviruses'; +$string['configantivirusplugins'] = 'Please choose the antivirus plugins you wish to use and arrange them in order of being applied.'; diff --git a/lib/adminlib.php b/lib/adminlib.php index eedbcea2a00..faf8972d254 100644 --- a/lib/adminlib.php +++ b/lib/adminlib.php @@ -6494,6 +6494,179 @@ class admin_setting_manageeditors extends admin_setting { } } +/** + * Special class for antiviruses administration. + * + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class admin_setting_manageantiviruses extends admin_setting { + /** + * Calls parent::__construct with specific arguments + */ + public function __construct() { + $this->nosave = true; + parent::__construct('antivirusesui', get_string('antivirussettings', 'antivirus'), '', ''); + } + + /** + * Always returns true, does nothing + * + * @return true + */ + public function get_setting() { + return true; + } + + /** + * Always returns true, does nothing + * + * @return true + */ + public function get_defaultsetting() { + return true; + } + + /** + * Always returns '', does not write anything + * + * @return string Always returns '' + */ + public function write_setting($data) { + // do not write any setting + return ''; + } + + /** + * Checks if $query is one of the available editors + * + * @param string $query The string to search for + * @return bool Returns true if found, false if not + */ + public function is_related($query) { + if (parent::is_related($query)) { + return true; + } + + $antiviruses_available = antiviruses_get_available(); + foreach ($antiviruses_available as $antivirus=>$antivirusstr) { + if (strpos($antivirus, $query) !== false) { + return true; + } + if (strpos(core_text::strtolower($antivirusstr), $query) !== false) { + return true; + } + } + return false; + } + + /** + * Builds the XHTML to display the control + * + * @param string $data Unused + * @param string $query + * @return string + */ + public function output_html($data, $query='') { + global $CFG, $OUTPUT; + + // display strings + $txt = get_strings(array('administration', 'settings', 'edit', 'name', 'enable', 'disable', + 'up', 'down', 'none')); + $struninstall = get_string('uninstallplugin', 'core_admin'); + + $txt->updown = "$txt->up/$txt->down"; + + $antiviruses_available = antiviruses_get_available(); + $active_antiviruses = explode(',', $CFG->antiviruses); + + $active_antiviruses = array_reverse($active_antiviruses); + foreach ($active_antiviruses as $key=>$antivirus) { + if (empty($antiviruses_available[$antivirus])) { + unset($active_antiviruses[$key]); + } else { + $name = $antiviruses_available[$antivirus]; + unset($antiviruses_available[$antivirus]); + $antiviruses_available[$antivirus] = $name; + } + } + $antiviruses_available = array_reverse($antiviruses_available, true); + $return = $OUTPUT->heading(get_string('actantivirushdr', 'antivirus'), 3, 'main', true); + $return .= $OUTPUT->box_start('generalbox antivirusesui'); + + $table = new html_table(); + $table->head = array($txt->name, $txt->enable, $txt->updown, $txt->settings, $struninstall); + $table->colclasses = array('leftalign', 'centeralign', 'centeralign', 'centeralign', 'centeralign'); + $table->id = 'antivirusmanagement'; + $table->attributes['class'] = 'admintable generaltable'; + $table->data = array(); + + // iterate through auth plugins and add to the display table + $updowncount = 1; + $antiviruscount = count($active_antiviruses); + $url = "antiviruses.php?sesskey=" . sesskey(); + foreach ($antiviruses_available as $antivirus => $name) { + // hide/show link + $class = ''; + if (in_array($antivirus, $active_antiviruses)) { + $hideshow = ""; + $hideshow .= "pix_url('t/hide') . "\" class=\"iconsmall\" alt=\"disable\" />"; + $enabled = true; + $displayname = $name; + } + else { + $hideshow = ""; + $hideshow .= "pix_url('t/show') . "\" class=\"iconsmall\" alt=\"enable\" />"; + $enabled = false; + $displayname = $name; + $class = 'dimmed_text'; + } + + // up/down link (only if auth is enabled) + $updown = ''; + if ($enabled) { + if ($updowncount > 1) { + $updown .= ""; + $updown .= "pix_url('t/up') . "\" alt=\"up\" class=\"iconsmall\" /> "; + } + else { + $updown .= "pix_url('spacer') . "\" class=\"iconsmall\" alt=\"\" /> "; + } + if ($updowncount < $antiviruscount) { + $updown .= ""; + $updown .= "pix_url('t/down') . "\" alt=\"down\" class=\"iconsmall\" />"; + } + else { + $updown .= "pix_url('spacer') . "\" class=\"iconsmall\" alt=\"\" />"; + } + ++ $updowncount; + } + + // settings link + if (file_exists($CFG->dirroot.'/lib/antivirus/'.$antivirus.'/settings.php')) { + $eurl = new moodle_url('/admin/settings.php', array('section'=>'antivirussettings'.$antivirus)); + $settings = "{$txt->settings}"; + } else { + $settings = ''; + } + + $uninstall = ''; + if ($uninstallurl = core_plugin_manager::instance()->get_uninstall_url('antivirus_'.$antivirus, 'manage')) { + $uninstall = html_writer::link($uninstallurl, $struninstall); + } + + // Add a row to the table. + $row = new html_table_row(array($displayname, $hideshow, $updown, $settings, $uninstall)); + if ($class) { + $row->attributes['class'] = $class; + } + $table->data[] = $row; + } + $return .= html_writer::table($table); + $return .= get_string('configantivirusplugins', 'antivirus').'
'.get_string('tablenosave', 'admin'); + $return .= $OUTPUT->box_end(); + return highlight($query, $return); + } +} /** * Special class for license administration. diff --git a/lib/antiviruslib.php b/lib/antiviruslib.php new file mode 100644 index 00000000000..2da0e27e76e --- /dev/null +++ b/lib/antiviruslib.php @@ -0,0 +1,140 @@ +. + +/** + * Utility classes and functions 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 + */ + +defined('MOODLE_INTERNAL') || die(); + +/** + * Returns list of enabled antiviruses. + * + * @return array Array ('antivirusname'=>stdClass antivirus object). + */ +function antiviruses_get_enabled() { + global $CFG; + + $active = array(); + if (empty($CFG->antiviruses)) { + return $active; + } + + foreach(explode(',', $CFG->antiviruses) as $e) { + if ($antivirus = antiviruses_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 bool $deleteinfected whether infected file needs to be deleted. + * @throws moodle_exception If file is infected. + * @return void + */ +function antiviruses_scan_file($file, $deleteinfected) { + $antiviruses = antiviruses_get_enabled(); + foreach ($antiviruses as $antivirus) { + $antivirus->scan_file($file, $deleteinfected); + } +} + +/** + * Returns instance of antivirus. + * + * @param string $antivirusname name of antivirus. + * @return object|bool antivirus instance or false if does not exist. + */ +function antiviruses_get_antivirus($antivirusname) { + global $CFG; + + $libfile = "$CFG->libdir/antivirus/$antivirusname/lib.php"; + if (!file_exists($libfile)) { + return false; + } + require_once($libfile); + $classname = 'antivirus_' . $antivirusname; + if (!class_exists($classname)) { + return false; + } + return new $classname(); +} + +/** + * Get the list of available antiviruses. + * + * @return array Array ('antivirusname'=>'localised antivirus name'). + */ +function antiviruses_get_available() { + $antiviruses = array(); + foreach (core_component::get_plugin_list('antivirus') as $antivirusname => $dir) { + $antiviruses[$antivirusname] = get_string('pluginname', 'antivirus_'.$antivirusname); + } + return $antiviruses; +} + +/** + * Base abstract antivirus 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 antivirus { + /** @var stdClass the config for antivirus */ + protected $config; + + /** + * Class constructor. + * + * @return void. + */ + public function __construct() { + // Populate config variable, child class name is matching full plugin name, + // so we can use it directly to retrieve plugin configuration. + $this->config = get_config(get_class($this)); + } + + /** + * Are the antivirus settings configured? + * + * @return bool True if plugin has been configured. + */ + public abstract function is_configured(); + + /** + * Scan file, 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 moodle_exception If file is infected. + * @return void + */ + public abstract function scan_file($file, $filename, $deleteinfected); +} diff --git a/lib/classes/component.php b/lib/classes/component.php index 4fcad1509a7..8bd659ddffd 100644 --- a/lib/classes/component.php +++ b/lib/classes/component.php @@ -341,6 +341,7 @@ $cache = '.var_export($cache, true).'; $info = array( 'access' => null, 'admin' => $CFG->dirroot.'/'.$CFG->admin, + 'antivirus' => $CFG->dirroot . '/lib/antivirus', 'auth' => $CFG->dirroot.'/auth', 'availability' => $CFG->dirroot . '/availability', 'backup' => $CFG->dirroot.'/backup/util/ui', @@ -417,6 +418,7 @@ $cache = '.var_export($cache, true).'; global $CFG; $types = array( + 'antivirus' => $CFG->dirroot . '/lib/antivirus', 'availability' => $CFG->dirroot . '/availability/condition', 'qtype' => $CFG->dirroot.'/question/type', 'mod' => $CFG->dirroot.'/mod', diff --git a/lib/classes/plugininfo/antivirus.php b/lib/classes/plugininfo/antivirus.php new file mode 100644 index 00000000000..c9f776d1e28 --- /dev/null +++ b/lib/classes/plugininfo/antivirus.php @@ -0,0 +1,119 @@ +. + +/** + * Defines classes used for plugin info. + * + * @package core_antivirus + * @copyright 2015 Ruslan Kabalin, Lancaster University. + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +namespace core\plugininfo; + +use moodle_url, part_of_admin_tree, admin_settingpage; + +defined('MOODLE_INTERNAL') || die(); + + +/** + * Class for Antiviruses + */ +class antivirus extends base { + /** + * Finds all enabled plugins, the result may include missing plugins. + * @return array|null of enabled plugins $pluginname=>$pluginname, null means unknown + */ + public static function get_enabled_plugins() { + global $CFG; + + if (empty($CFG->antiviruses)) { + return array(); + } + + $enabled = array(); + foreach (explode(',', $CFG->antiviruses) as $antivirus) { + $enabled[$antivirus] = $antivirus; + } + + return $enabled; + } + + public function get_settings_section_name() { + return 'antivirussettings' . $this->name; + } + + public function load_settings(part_of_admin_tree $adminroot, $parentnodename, $hassiteconfig) { + global $CFG, $USER, $DB, $OUTPUT, $PAGE; // In case settings.php wants to refer to them. + $ADMIN = $adminroot; // May be used in settings.php. + $plugininfo = $this; // Also can be used inside settings.php. + $antivirus = $this; // Also can be used inside settings.php. + + if (!$this->is_installed_and_upgraded()) { + return; + } + + if (!$hassiteconfig or !file_exists($this->full_path('settings.php'))) { + return; + } + + $section = $this->get_settings_section_name(); + + $settings = new admin_settingpage($section, $this->displayname, 'moodle/site:config', $this->is_enabled() === false); + include($this->full_path('settings.php')); // This may also set $settings to null. + + if ($settings) { + $ADMIN->add($parentnodename, $settings); + } + } + + /** + * Clamav antivirus can not be uninstalled. + */ + public function is_uninstall_allowed() { + if ($this->name === 'clamav') { + return false; + } else { + return true; + } + } + + /** + * Return URL used for management of plugins of this type. + * @return moodle_url + */ + public static function get_manage_url() { + return new moodle_url('/admin/settings.php', array('section'=>'manageantiviruses')); + } + + /** + * Pre-uninstall hook. + */ + public function uninstall_cleanup() { + global $CFG; + + if (!empty($CFG->antiviruses)) { + $antiviruses = explode(',', $CFG->antiviruses); + $antiviruses = array_unique($antiviruses); + } else { + $antiviruses = array(); + } + if (($key = array_search($this->name, $antiviruses)) !== false) { + unset($antiviruses[$key]); + set_config('antiviruses', implode(',', $antiviruses)); + } + parent::uninstall_cleanup(); + } +} diff --git a/lib/db/install.php b/lib/db/install.php index f8dbdce278a..f93ad10bebc 100644 --- a/lib/db/install.php +++ b/lib/db/install.php @@ -130,6 +130,7 @@ function xmldb_main_install() { 'stringfilters' => '', // These two are managed in a strange way by the filters 'filterall' => 0, // setting page, so have to be initialised here. 'texteditors' => 'atto,tinymce,textarea', + 'antiviruses' => '', 'upgrade_minmaxgradestepignored' => 1, // New installs should not run this upgrade step. 'upgrade_extracreditweightsstepignored' => 1, // New installs should not run this upgrade step. 'upgrade_calculatedgradeitemsignored' => 1, // New installs should not run this upgrade step. diff --git a/lib/setup.php b/lib/setup.php index 4c5930bc03f..4bb4e6ae6b8 100644 --- a/lib/setup.php +++ b/lib/setup.php @@ -607,6 +607,7 @@ require_once($CFG->libdir .'/editorlib.php'); // All text editor related f require_once($CFG->libdir .'/messagelib.php'); // Messagelib functions require_once($CFG->libdir .'/modinfolib.php'); // Cached information on course-module instances require_once($CFG->dirroot.'/cache/lib.php'); // Cache API +require_once($CFG->libdir .'/antiviruslib.php'); // Antivirus functions and classes. // make sure PHP is not severly misconfigured setup_validate_php_configuration();