Лучшее лечение — это профилактика.',
'offer_our_products' => TemplateList::OFFER_OUR_PRODUCTS_RU,
'offer.when_has_critical' => TemplateList::OFFER_RU,
'сaution.aibolit_file' => 'Не оставляйте файл отчета на сервере, и не давайте на него прямых ссылок с других сайтов. Информация из отчета может быть использована злоумышленниками для взлома сайта, так как содержит информацию о настройках сервера, файлах и каталогах.',
'сaution.scanner_set_password' => "Сканер AI-Bolit запускается с паролем. Если это первый запуск сканера, вам нужно придумать сложный пароль и вписать его в файле ai-bolit.php в строке №34.
После этого откройте сканер в браузере, указав пароль в параметре \"p\".
Для диагностического сканирования без ложных срабатываний мы разработали специальную версию сканера для хостинг-компаний.",
'script.vulnerable' => "Уязвимости в скриптах",
'path' => 'Путь',
'property_change' => 'Изменение свойств',
'content_change' => 'Изменение содержимого',
'size' => 'Размер',
'php_config' => 'Конфигурация PHP',
];
const EN = [
//variables for javascript
'data_table.length_menu' => 'Display _MENU_ records',
'data_table.zero_records' => 'Not found',
'data_table.info' => 'Display from _START_ to _END_ of _TOTAL_ files',
'data_table.info_empty' => 'No files',
'data_table.info_filtered' => '(total _MAX_)',
'data_table.search' => 'Filter/Search:',
'data_table.paginate.first' => 'First',
'data_table.paginate.previous' => 'Previous',
'data_table.paginate.next' => 'Next',
'data_table.paginate.last' => 'Last',
'data_table.aria.sort_ascending' => ': activate to sort row ascending order',
'data_table.aria.sort_descending' => ': activate to sort row descending order',
'header.scan_report_title' => 'AI-Bolit v@@VERSION@@ Scan Report:',
'offer.when_no_critical' => '',
'offer_our_products' => TemplateList::OFFER_OUR_PRODUCTS_EN,
'offer.when_has_critical' => TemplateList::OFFER_EN,
'сaution.aibolit_file' => 'Caution! Do not leave either ai-bolit.php or report file on server and do not provide direct links to the report file. Report file contains sensitive information about your website which could be used by hackers. So keep it in safe place and don\'t leave on website!',
'сaution.scanner_set_password' => "Open AI-BOLIT with password specified in the beggining of file in PASS variable.
E.g. http://you_website.com/ai-bolit.php?p=%s",
'сaution.quick_scanned' => '
Attention! Script has performed quick scan. It scans only .html/.js/.php files in quick scan mode so some of malicious scripts might not be detected.
Please launch script from a command line thru SSH to perform full scan.',
'warning.weak_password' => "Your password for AI-BOLIT is too weak. Password must be more than 8 character length, contain both latin letters in upper and lower case, and digits. E.g.
%s",
'warning.folder_read_permission' => 'Current folder is not readable. Please change permission for
rwxr-xr-x or using command line
chmod +r folder_name',
'warnings' => 'Warnings',
'warning.reading_error' => 'Reading error. Skipped.',
'info.time_elapsed' => "
%s malicious signatures known, %s virus signatures and other malicious code. Elapsed: %s.
Started: %s. Stopped: %s
",
'info.files_checked' => 'Scanned %s folders and %s files.',
'info.non_commercial_use' => 'For non-commercial use only.',
'critical.title' => '
Critical
',
'detected.shell_scripts' => 'Shell script signatures detected. Might be a malicious or hacker\'s scripts',
'not_detected.shell_scripts' => 'Shell scripts signatures not detected.',
'detected.javascript' => 'Javascript virus signatures detected:',
'detected.executables' => 'Unix executables signatures and odd scripts detected. They might be a malicious binaries or rootkits:',
'detected.bad_links' => 'This script has black-SEO links or linkfarm. Check if it was installed by yourself:',
'detected.phishing_pages' => 'Phishing pages detected:',
'file.not_found.more_than' => 'Files greater than %s not found',
'file.recommend_to_remove' => 'Files recommended to be remove due to security reason:',
'file.scanned_manual' => 'Quick scan through the files from %s. For full scan remove %s and launch scanner once again.',
'file.suspicion.heuristic_analyze' => 'Heuristic Analyzer has detected suspicious files. Check if they are malware.',
'file.encrypted' => 'Encrypted files',
'file.hidden' => 'Hidden files',
'files_may_not_malicious' => "Notice! Some of detected files may not contain malicious code. Scanner tries to minimize a number of false positives, but sometimes it's impossible, because same piece of code may be used either in malware or in normal scripts.",
'file.added' => "Added files",
'file.updated' => "Modified files",
'file.deleted' => "Deleted files",
'file_structure.updates' => "Integrity Check Report",
'hidden_files' => 'Hidden files:',
'doorway.might' => 'Files might be a part of doorway:',
'doorway.not_found' => 'Doorway folders not detected',
'suspicion.multiple' => 'Suspicious encoded strings, extra .php extention or external includes detected in PHP files. Might be a malicious or hacker\'s script:',
'suspicion.malicious' => 'Might be a malicious or hacker\'s script:',
'suspicion.htaccess' => 'Malicious code in .htaccess (redirect to external server, extention handler replacement or malicious code auto-append):',
'suspicion.non_php' => 'Non-PHP file has PHP signature. Check for malicious code:',
'suspicion.hidden_link' => 'These files have invisible links, might be black-seo stuff:',
'suspicion.doorway' => 'Folders contained too many .php or .html files. Might be a doorway:',
'suspicion.code' => 'Suspicious code detected. It\'s usually used in malicious scrips:',
'suspicion.obfuscated_variables' => 'Suspected for obfuscated variables',
'suspicion.global_array' => 'Suspected for $GLOBAL array usage',
'suspicion.file_time' => "Suspicious file mtime and ctime",
'suspicion.file_attributes' => "Suspicious file permissions",
'suspicion.file_location' => "Suspicious file location",
'symlinks' => 'Symlinks:',
'hidden_links' => 'List of invisible links:',
'link.symbolic' => 'Symbolic links',
'links.adware_spam' => 'Adware and spam links',
'links.empty' => 'Empty links',
'display_only_first' => 'Displayed first ',
'skipped.adirignore' => 'The following list of files specified in .adirignore has been skipped:',
'founded_CMS' => 'CMS found:',
'folder.unsafe_writable' => 'Potentially unsafe! Folders which are writable for scripts:',
'folder.unsafe_writable_not_found' => 'Writable folders not found',
'folder.added' => "Added directories",
'folder.deleted' => "Deleted directories",
'memory_used' => 'Memory used: ',
'notice.scan_express' => '
[!] Ai-BOLIT is working in quick scan mode, only .php, .html, .htaccess files will be checked. Change the following setting \'scan_all_files\' => 1 to perform full scanning..
',
'feedback_for_script' => "I'm sincerely appreciate reports for any bugs you may found in the script. Please email me:
audit@revisium.com.
Also I appriciate any reference to the script in your blog or forum posts. Thank you for the link to download page: https://revisium.com/aibo/",
'report_for' => 'Report for ',
'function.many_reference' => 'Function called by reference',
'str.abnormal_split' => 'Abnormal split of string',
'scan.offer_modes_after_express' => 'Scanning has been done in simple mode. It is strongly recommended to perform scanning in "Expert" mode. See readme.txt for details.',
'mobile_redirects' => 'Mobile redirects',
'skipped.large_file' => 'Large files (greater than %s! Skipped:',
'malware' => 'Malware',
'js_virused' => 'JS viruses',
'phishing_pages' => 'Phishing pages',
'executable_files' => 'Unix executables',
'iframe_injections' => 'IFRAME injections',
'skipped_large_file' => 'Skipped big files',
'critical.error_read_file' => 'Reading errors',
'suspicious' => 'Suspicious',
'report.summary' => 'Summary',
'footer' => TemplateList::FOOTER_EN,
'script.vulnerable' => "Vulnerable Scripts",
'path' => 'Path',
'property_change' => 'iNode Changed',
'content_change' => 'Modified',
'size' => 'Size',
'php_config' => 'PHP Info',
];
}
class UserList
{
private $users = [];
public function add($uid)
{
$this->users[$uid] = '';
}
public function getList()
{
ksort($this->users);
return array_keys($this->users);
}
public function setList($list)
{
$this->users = $list;
}
}
class OsReleaseInfo
{
private $prefix = '';
private $release_file = '';
const DEBIAN = ['debian'];
const RHEL_FEDORA_CENTOS = ['rhel', 'fedora', 'centos'];
const UNKNOWN = ['unknown'];
private $_supported_dists = [
'SuSE', 'debian', 'fedora', 'redhat', 'centos',
'mandrake', 'mandriva', 'rocks', 'slackware', 'yellowdog', 'gentoo',
'UnitedLinux', 'turbolinux'];
private $_release_filename = '(\w+)[-_](release|version)';
private $_lsb_release_version = '(.+)'
. ' release '
. '([\d.]+)'
. '[^(]*(?:\((.+)\))?';
private $_release_version = '([^0-9]+)'
. '(?: release )?'
. '([\d.]+)'
. '[^(]*(?:\((.+)\))?';
private $release = [];
public function __construct($prefix = '', $release_file = '/etc/os-release')
{
$this->prefix = $prefix;
$this->release_file = $this->prefix . $release_file;
$this->getRelease($this->release_file);
}
private function getOsReleaseAndVersion()
{
$ver = rtrim(@file_get_contents($this->prefix . '/etc/system-release'));
return $ver ?: $this->release['VERSION'];
}
public function getOsVersion($release_and_version = false)
{
$rv = $release_and_version ? $release_and_version : $this->getOsReleaseAndVersion();
if ($rv) {
if (preg_match('~\s*(\d+\.\d+\S*)(\s|$)~', $rv, $m)) {
return $m[1];
}
}
return $this->release['VERSION_ID'] ? $this->release['VERSION_ID'] : false;
}
private function linuxDistribution($distname = '', $version = '', $id = '')
{
$dists = array_flip($this->_supported_dists);
if (!file_exists($this->prefix . '/etc')) {
return [$distname, $version, $id];
}
$file = '';
$etc = scandir($this->prefix . '/etc');
foreach ($etc as $file) {
if (preg_match('~' . $this->_release_filename . '~', $file, $m)) {
if (isset($dists[$m[1]])) {
$_distname = $m[1];
$distname = $m[1];
break;
}
}
}
$f = fopen($this->prefix . '/etc/' . $file, 'r');
$firstline = fgets($f);
fclose($f);
list($_distname, $_version, $_id) = $this->_parseReleaseFile($firstline);
if ($_distname) {
$distname = $_distname;
}
if ($_version) {
$version = $_version;
}
if ($_id) {
$id = $_id;
}
return [$distname, $version, $id];
}
private function _parseReleaseFile($firstline)
{
$version = '';
$id = '';
if (preg_match('~' . $this->_lsb_release_version . '~', $firstline, $m)) {
return [$m[1], $m[2], $m[3]];
}
if (preg_match('~' . $this->_release_version . '~', $firstline, $m)) {
return [$m[1], $m[2], $m[3]];
}
$l = preg_split("~\s+~", trim($firstline), -1, PREG_SPLIT_NO_EMPTY);
if (!empty($l)) {
$version = $l[0];
if (count($l) > 1) {
$id = $l[1];
}
}
return ['', $version, $id];
}
private function getReleaseFromFile($release_file)
{
$lines = file($release_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
list($k, $v) = explode('=', rtrim($line), 2);
$this->release[$k] = trim($v, '"');
}
if (isset($this->release['ID_LIKE'])) {
$this->release['ID_LIKE'] = preg_split('~\s+~', $this->release['ID_LIKE'], -1, PREG_SPLIT_NO_EMPTY);
} else {
$this->release['ID_LIKE'] = [$this->release['ID']];
}
}
private function getRelease($release_file = '')
{
if (count($this->release) != 0) {
return $this->release;
}
if ($this->release_file) {
$release_file = $this->release_file;
}
$osid = '';
if (file_exists($release_file)) {
$this->getReleaseFromFile($release_file);
} else {
$d = $this->linuxDistribution();
if ($d && isset($d[0])) {
$osid = current(preg_split('~\s+~', strtolower($d[0]), -1, PREG_SPLIT_NO_EMPTY));
if ($osid == 'red' && strpos($d[0], 'Red Hat Enterprise Linux')) {
$osid = 'rhel';
}
$this->release['ID'] = $osid;
$this->release['PRETTY_NAME'] = "{$d[0]} {$d[1]} ({$d[2]})";
if (in_array($osid, ['cloudlinux', 'centos', 'rhel'])) {
$this->release['ID_LIKE'] = self::RHEL_FEDORA_CENTOS;
} else if (in_array($osid, ['ubuntu', 'debian'])) {
$this->release['ID_LIKE'] = self::DEBIAN;
} else {
$this->release['ID_LIKE'] = self::UNKNOWN;
}
} else {
$this->release['ID'] = 'unknown';
$this->release['ID_LIKE'] = self::UNKNOWN;
$this->release['PRETTY_NAME'] = 'unknown';
}
}
return $this->release;
}
public function getIdLike()
{
$rel = $this->getRelease();
return $rel['ID_LIKE'];
}
public function isIdLikeCentos()
{
$rel = $this->getRelease();
foreach (self::RHEL_FEDORA_CENTOS as $os) {
if (in_array($os, $rel['ID_LIKE'])) {
return true;
}
}
return false;
}
public function getPrettyName()
{
$rel = $this->getRelease();
return $rel['PRETTY_NAME'];
}
public function getOs()
{
$rel = $this->getRelease();
return $rel['ID'];
}
public function isRhel()
{
return $this->getOs() == 'rhel';
}
public function isCentos()
{
return $this->getOs() == 'centos';
}
public function isUbuntu()
{
return $this->getOs() == 'ubuntu';
}
public function isCloudlinux()
{
return $this->getOs() == 'cloudlinux';
}
public function isDebian()
{
return $this->getOs() == 'debian';
}
}
/**
* Class FileHashMemoryDb.
*
* Implements operations to load the file hash database into memory and work with it.
*/
class FileHashMemoryDb
{
const HEADER_SIZE = 1024;
const ROW_SIZE = 20;
/**
* @var int
*/
private $count;
/**
* @var array
*/
private $header;
/**
* @var resource
*/
private $fp;
/**
* @var array
*/
private $data;
/**
* Creates a new DB file and open it.
*
* @param $filepath
* @return FileHashMemoryDb
* @throws Exception
*/
public static function create($filepath)
{
if (file_exists($filepath)) {
throw new Exception('File \'' . $filepath . '\' already exists.');
}
$value = pack('V', 0);
$header = array_fill(0, 256, $value);
file_put_contents($filepath, implode($header));
return new self($filepath);
}
/**
* Opens a particular DB file.
*
* @param $filepath
* @return FileHashMemoryDb
* @throws Exception
*/
public static function open($filepath)
{
if (!file_exists($filepath)) {
throw new Exception('File \'' . $filepath . '\' does not exist.');
}
return new self($filepath);
}
/**
* FileHashMemoryDb constructor.
*
* @param mixed $filepath
* @throws Exception
*/
private function __construct($filepath)
{
$this->fp = fopen($filepath, 'rb');
if (false === $this->fp) {
throw new Exception('File \'' . $filepath . '\' can not be opened.');
}
try {
$this->header = unpack('V256', fread($this->fp, self::HEADER_SIZE));
$this->count = (int) (max(0, filesize($filepath) - self::HEADER_SIZE) / self::ROW_SIZE);
foreach ($this->header as $chunk_id => $chunk_size) {
if ($chunk_size > 0) {
$str = fread($this->fp, $chunk_size);
} else {
$str = '';
}
$this->data[$chunk_id] = $str;
}
} catch (Exception $e) {
throw new Exception('File \'' . $filepath . '\' is not a valid DB file. An original error: \'' . $e->getMessage() . '\'');
}
}
/**
* Calculates and returns number of hashes stored in a loaded database.
*
* @return int number of hashes stored in a DB
*/
public function count()
{
return $this->count;
}
/**
* Find hashes in a DB.
*
* @param array $list of hashes to find in a DB
* @return array list of hashes from the $list parameter that are found in a DB
*/
public function find($list)
{
sort($list);
$hash = reset($list);
$found = [];
foreach ($this->header as $chunk_id => $chunk_size) {
if ($chunk_size > 0) {
$str = $this->data[$chunk_id];
do {
$raw = pack("H*", $hash);
$id = ord($raw[0]) + 1;
if ($chunk_id == $id AND $this->binarySearch($str, $raw)) {
$found[] = (string)$hash;
}
} while ($chunk_id >= $id AND $hash = next($list));
if ($hash === false) {
break;
}
}
}
return $found;
}
/**
* Searches $item in the $str using an implementation of the binary search algorithm.
*
* @param $str
* @param $item
* @return bool
*/
private function binarySearch($str, $item) {
$item_size = strlen($item);
if ($item_size == 0) {
return false;
}
$first = 0;
$last = floor(strlen($str) / $item_size);
while ($first < $last) {
$mid = $first + (($last - $first) >> 1);
$b = substr($str, $mid * $item_size, $item_size);
if (strcmp($item, $b) <= 0) {
$last = $mid;
} else {
$first = $mid + 1;
}
}
$b = substr($str, $last * $item_size, $item_size);
if ($b == $item) {
return true;
} else {
return false;
}
}
/**
* FileHashDB destructor.
*/
public function __destruct()
{
fclose($this->fp);
}
}
class FilepathEscaper
{
public static function encodeFilepath($filepath)
{
return str_replace(['\\', "\n", "\r"], ['\\\\', '\\n', '\\r'], $filepath);
}
public static function decodeFilepath($filepath)
{
return preg_replace_callback('~(\\\\+)(.)~', function ($matches) {
$count = strlen($matches[1]);
if ($count % 2 === 0) {
return str_repeat('\\', $count/2) . $matches[2];
}
return str_repeat('\\', floor($count/2)) . stripcslashes('\\' . $matches[2]);
}, $filepath);
}
public static function encodeFilepathByBase64($filepath)
{
return base64_encode($filepath);
}
public static function decodeFilepathByBase64($filepath_base64)
{
return base64_decode($filepath_base64);
}
}
class StringToStreamWrapper {
const WRAPPER_NAME = 'var';
private static $_content;
private $_position;
/**
* Prepare a new memory stream with the specified content
* @return string
*/
public static function prepare($content)
{
if (!in_array(self::WRAPPER_NAME, stream_get_wrappers())) {
stream_wrapper_register(self::WRAPPER_NAME, get_class());
}
self::$_content = $content;
}
public function stream_open($path, $mode, $options, &$opened_path)
{
$this->_position = 0;
return true;
}
public function stream_read($count)
{
$ret = substr(self::$_content, $this->_position, $count);
$this->_position += strlen($ret);
return $ret;
}
public function stream_stat()
{
return [];
}
public function stream_eof()
{
return $this->_position >= strlen(self::$_content);
}
public function stream_set_option($option , $arg1, $arg2 )
{
return true;
}
}
class Normalization
{
const MAX_ITERATION = 10;
private static $confusables = "YToxNTY1OntzOjM6IuKAqCI7czoxOiIgIjtzOjM6IuKAqSI7czoxOiIgIjtzOjM6IuGagCI7czoxOiIgIjtzOjM6IuKAgCI7czoxOiIgIjtzOjM6IuKAgSI7czoxOiIgIjtzOjM6IuKAgiI7czoxOiIgIjtzOjM6IuKAgyI7czoxOiIgIjtzOjM6IuKAhCI7czoxOiIgIjtzOjM6IuKAhSI7czoxOiIgIjtzOjM6IuKAhiI7czoxOiIgIjtzOjM6IuKAiCI7czoxOiIgIjtzOjM6IuKAiSI7czoxOiIgIjtzOjM6IuKAiiI7czoxOiIgIjtzOjM6IuKBnyI7czoxOiIgIjtzOjI6IsKgIjtzOjE6IiAiO3M6Mzoi4oCHIjtzOjE6IiAiO3M6Mzoi4oCvIjtzOjE6IiAiO3M6Mjoiw4IiO3M6MToiICI7czoyOiLfuiI7czoxOiJfIjtzOjM6Iu+5jSI7czoxOiJfIjtzOjM6Iu+5jiI7czoxOiJfIjtzOjM6Iu+5jyI7czoxOiJfIjtzOjM6IuKAkCI7czoxOiItIjtzOjM6IuKAkSI7czoxOiItIjtzOjM6IuKAkiI7czoxOiItIjtzOjM6IuKAkyI7czoxOiItIjtzOjM6Iu+5mCI7czoxOiItIjtzOjI6ItuUIjtzOjE6Ii0iO3M6Mzoi4oGDIjtzOjE6Ii0iO3M6Mjoiy5ciO3M6MToiLSI7czozOiLiiJIiO3M6MToiLSI7czozOiLinpYiO3M6MToiLSI7czozOiLisroiO3M6MToiLSI7czoyOiLYjSI7czoxOiIsIjtzOjI6ItmrIjtzOjE6IiwiO3M6Mzoi4oCaIjtzOjE6IiwiO3M6MjoiwrgiO3M6MToiLCI7czozOiLqk7kiO3M6MToiLCI7czoyOiLNviI7czoxOiI7IjtzOjM6IuCkgyI7czoxOiI6IjtzOjM6IuCqgyI7czoxOiI6IjtzOjM6Iu+8miI7czoxOiI6IjtzOjI6ItaJIjtzOjE6IjoiO3M6Mjoi3IMiO3M6MToiOiI7czoyOiLchCI7czoxOiI6IjtzOjM6IuGbrCI7czoxOiI6IjtzOjM6Iu+4sCI7czoxOiI6IjtzOjM6IuGggyI7czoxOiI6IjtzOjM6IuGgiSI7czoxOiI6IjtzOjM6IuKBmiI7czoxOiI6IjtzOjI6IteDIjtzOjE6IjoiO3M6Mjoiy7giO3M6MToiOiI7czozOiLqnokiO3M6MToiOiI7czozOiLiiLYiO3M6MToiOiI7czoyOiLLkCI7czoxOiI6IjtzOjM6IuqTvSI7czoxOiI6IjtzOjM6Iu+8gSI7czoxOiIhIjtzOjI6IseDIjtzOjE6IiEiO3M6Mzoi4rWRIjtzOjE6IiEiO3M6MjoiypQiO3M6MToiPyI7czoyOiLJgSI7czoxOiI/IjtzOjM6IuClvSI7czoxOiI/IjtzOjM6IuGOriI7czoxOiI/IjtzOjM6IuqbqyI7czoxOiI/IjtzOjQ6IvCdha0iO3M6MToiLiI7czozOiLigKQiO3M6MToiLiI7czoyOiLcgSI7czoxOiIuIjtzOjI6ItyCIjtzOjE6Ii4iO3M6Mzoi6piOIjtzOjE6Ii4iO3M6NDoi8JCpkCI7czoxOiIuIjtzOjI6ItmgIjtzOjE6Ii4iO3M6Mjoi27AiO3M6MToiLiI7czozOiLqk7giO3M6MToiLiI7czozOiLjg7siO3M6MToityI7czozOiLvvaUiO3M6MToityI7czozOiLhm6siO3M6MToityI7czoyOiLOhyI7czoxOiK3IjtzOjM6IuK4sSI7czoxOiK3IjtzOjQ6IvCQhIEiO3M6MToityI7czozOiLigKIiO3M6MToityI7czozOiLigKciO3M6MToityI7czozOiLiiJkiO3M6MToityI7czozOiLii4UiO3M6MToityI7czozOiLqno8iO3M6MToityI7czozOiLhkKciO3M6MToityI7czoyOiLVnSI7czoxOiInIjtzOjM6Iu+8hyI7czoxOiInIjtzOjM6IuKAmCI7czoxOiInIjtzOjM6IuKAmSI7czoxOiInIjtzOjM6IuKAmyI7czoxOiInIjtzOjM6IuKAsiI7czoxOiInIjtzOjM6IuKAtSI7czoxOiInIjtzOjI6ItWaIjtzOjE6IiciO3M6Mjoi17MiO3M6MToiJyI7czoxOiJgIjtzOjE6IiciO3M6Mzoi4b+vIjtzOjE6IiciO3M6Mzoi772AIjtzOjE6IiciO3M6MjoiwrQiO3M6MToiJyI7czoyOiLOhCI7czoxOiInIjtzOjM6IuG/vSI7czoxOiInIjtzOjM6IuG+vSI7czoxOiInIjtzOjM6IuG+vyI7czoxOiInIjtzOjM6IuG/viI7czoxOiInIjtzOjI6Isq5IjtzOjE6IiciO3M6MjoizbQiO3M6MToiJyI7czoyOiLLiCI7czoxOiInIjtzOjI6IsuKIjtzOjE6IiciO3M6Mjoiy4siO3M6MToiJyI7czoyOiLLtCI7czoxOiInIjtzOjI6Isq7IjtzOjE6IiciO3M6Mjoiyr0iO3M6MToiJyI7czoyOiLKvCI7czoxOiInIjtzOjI6Isq+IjtzOjE6IiciO3M6Mzoi6p6MIjtzOjE6IiciO3M6Mjoi15kiO3M6MToiJyI7czoyOiLftCI7czoxOiInIjtzOjI6It+1IjtzOjE6IiciO3M6Mzoi4ZGKIjtzOjE6IiciO3M6Mzoi4ZuMIjtzOjE6IiciO3M6NDoi8Ja9kSI7czoxOiInIjtzOjQ6IvCWvZIiO3M6MToiJyI7czozOiLvvLsiO3M6MToiKCI7czozOiLinagiO3M6MToiKCI7czozOiLinbIiO3M6MToiKCI7czozOiLjgJQiO3M6MToiKCI7czozOiLvtL4iO3M6MToiKCI7czozOiLvvL0iO3M6MToiKSI7czozOiLinakiO3M6MToiKSI7czozOiLinbMiO3M6MToiKSI7czozOiLjgJUiO3M6MToiKSI7czozOiLvtL8iO3M6MToiKSI7czozOiLinbQiO3M6MToieyI7czo0OiLwnYSUIjtzOjE6InsiO3M6Mzoi4p21IjtzOjE6In0iO3M6Mzoi4ri/IjtzOjE6IrYiO3M6Mzoi4oGOIjtzOjE6IioiO3M6Mjoi2a0iO3M6MToiKiI7czozOiLiiJciO3M6MToiKiI7czo0OiLwkIyfIjtzOjE6IioiO3M6Mzoi4Zy1IjtzOjE6Ii8iO3M6Mzoi4oGBIjtzOjE6Ii8iO3M6Mzoi4oiVIjtzOjE6Ii8iO3M6Mzoi4oGEIjtzOjE6Ii8iO3M6Mzoi4pWxIjtzOjE6Ii8iO3M6Mzoi4p+LIjtzOjE6Ii8iO3M6Mzoi4qe4IjtzOjE6Ii8iO3M6NDoi8J2IuiI7czoxOiIvIjtzOjM6IuOHkyI7czoxOiIvIjtzOjM6IuOAsyI7czoxOiIvIjtzOjM6IuKzhiI7czoxOiIvIjtzOjM6IuODjiI7czoxOiIvIjtzOjM6IuS4vyI7czoxOiIvIjtzOjM6IuK8gyI7czoxOiIvIjtzOjM6Iu+8vCI7czoxOiJcIjtzOjM6Iu+5qCI7czoxOiJcIjtzOjM6IuKIliI7czoxOiJcIjtzOjM6IuKfjSI7czoxOiJcIjtzOjM6IuKntSI7czoxOiJcIjtzOjM6IuKnuSI7czoxOiJcIjtzOjQ6IvCdiI8iO3M6MToiXCI7czo0OiLwnYi7IjtzOjE6IlwiO3M6Mzoi44eUIjtzOjE6IlwiO3M6Mzoi5Li2IjtzOjE6IlwiO3M6Mzoi4ryCIjtzOjE6IlwiO3M6Mzoi6p24IjtzOjE6IiYiO3M6Mjoiy4QiO3M6MToiXiI7czoyOiLLhiI7czoxOiJeIjtzOjM6IuK4sCI7czoxOiKwIjtzOjI6IsuaIjtzOjE6IrAiO3M6Mzoi4oiYIjtzOjE6IrAiO3M6Mzoi4peLIjtzOjE6IrAiO3M6Mzoi4pemIjtzOjE6IrAiO3M6Mzoi4pK4IjtzOjE6IqkiO3M6Mzoi4pOHIjtzOjE6Iq4iO3M6Mzoi4ZutIjtzOjE6IisiO3M6Mzoi4p6VIjtzOjE6IisiO3M6NDoi8JCKmyI7czoxOiIrIjtzOjM6IuKelyI7czoxOiL3IjtzOjM6IuKAuSI7czoxOiI8IjtzOjM6IuKdriI7czoxOiI8IjtzOjI6IsuCIjtzOjE6IjwiO3M6NDoi8J2ItiI7czoxOiI8IjtzOjM6IuGQuCI7czoxOiI8IjtzOjM6IuGasiI7czoxOiI8IjtzOjM6IuGQgCI7czoxOiI9IjtzOjM6IuK5gCI7czoxOiI9IjtzOjM6IuOCoCI7czoxOiI9IjtzOjM6IuqTvyI7czoxOiI9IjtzOjM6IuKAuiI7czoxOiI+IjtzOjM6IuKdryI7czoxOiI+IjtzOjI6IsuDIjtzOjE6Ij4iO3M6NDoi8J2ItyI7czoxOiI+IjtzOjM6IuGQsyI7czoxOiI+IjtzOjQ6IvCWvL8iO3M6MToiPiI7czozOiLigZMiO3M6MToifiI7czoyOiLLnCI7czoxOiJ+IjtzOjM6IuG/gCI7czoxOiJ+IjtzOjM6IuKIvCI7czoxOiJ+IjtzOjM6IuKCpCI7czoxOiKjIjtzOjQ6IvCdn5AiO3M6MToiMiI7czo0OiLwnZ+aIjtzOjE6IjIiO3M6NDoi8J2fpCI7czoxOiIyIjtzOjQ6IvCdn64iO3M6MToiMiI7czo0OiLwnZ+4IjtzOjE6IjIiO3M6Mzoi6p2aIjtzOjE6IjIiO3M6MjoixqciO3M6MToiMiI7czoyOiLPqCI7czoxOiIyIjtzOjM6IuqZhCI7czoxOiIyIjtzOjM6IuGSvyI7czoxOiIyIjtzOjM6IuqbryI7czoxOiIyIjtzOjQ6IvCdiIYiO3M6MToiMyI7czo0OiLwnZ+RIjtzOjE6IjMiO3M6NDoi8J2fmyI7czoxOiIzIjtzOjQ6IvCdn6UiO3M6MToiMyI7czo0OiLwnZ+vIjtzOjE6IjMiO3M6NDoi8J2fuSI7czoxOiIzIjtzOjM6IuqeqyI7czoxOiIzIjtzOjI6IsicIjtzOjE6IjMiO3M6MjoixrciO3M6MToiMyI7czozOiLqnaoiO3M6MToiMyI7czozOiLis4wiO3M6MToiMyI7czoyOiLQlyI7czoxOiIzIjtzOjI6ItOgIjtzOjE6IjMiO3M6NDoi8Ja8uyI7czoxOiIzIjtzOjQ6IvCRo4oiO3M6MToiMyI7czo0OiLwnZ+SIjtzOjE6IjQiO3M6NDoi8J2fnCI7czoxOiI0IjtzOjQ6IvCdn6YiO3M6MToiNCI7czo0OiLwnZ+wIjtzOjE6IjQiO3M6NDoi8J2fuiI7czoxOiI0IjtzOjM6IuGPjiI7czoxOiI0IjtzOjQ6IvCRoq8iO3M6MToiNCI7czo0OiLwnZ+TIjtzOjE6IjUiO3M6NDoi8J2fnSI7czoxOiI1IjtzOjQ6IvCdn6ciO3M6MToiNSI7czo0OiLwnZ+xIjtzOjE6IjUiO3M6NDoi8J2fuyI7czoxOiI1IjtzOjI6Isa8IjtzOjE6IjUiO3M6NDoi8JGiuyI7czoxOiI1IjtzOjQ6IvCdn5QiO3M6MToiNiI7czo0OiLwnZ+eIjtzOjE6IjYiO3M6NDoi8J2fqCI7czoxOiI2IjtzOjQ6IvCdn7IiO3M6MToiNiI7czo0OiLwnZ+8IjtzOjE6IjYiO3M6Mzoi4rOSIjtzOjE6IjYiO3M6Mjoi0LEiO3M6MToiNiI7czozOiLhj64iO3M6MToiNiI7czo0OiLwkaOVIjtzOjE6IjYiO3M6NDoi8J2IkiI7czoxOiI3IjtzOjQ6IvCdn5UiO3M6MToiNyI7czo0OiLwnZ+fIjtzOjE6IjciO3M6NDoi8J2fqSI7czoxOiI3IjtzOjQ6IvCdn7MiO3M6MToiNyI7czo0OiLwnZ+9IjtzOjE6IjciO3M6NDoi8JCTkiI7czoxOiI3IjtzOjQ6IvCRo4YiO3M6MToiNyI7czozOiLgrIMiO3M6MToiOCI7czozOiLgp6oiO3M6MToiOCI7czozOiLgqaoiO3M6MToiOCI7czo0OiLwnqOLIjtzOjE6IjgiO3M6NDoi8J2fliI7czoxOiI4IjtzOjQ6IvCdn6AiO3M6MToiOCI7czo0OiLwnZ+qIjtzOjE6IjgiO3M6NDoi8J2ftCI7czoxOiI4IjtzOjQ6IvCdn74iO3M6MToiOCI7czoyOiLIoyI7czoxOiI4IjtzOjI6IsiiIjtzOjE6IjgiO3M6NDoi8JCMmiI7czoxOiI4IjtzOjM6IuCppyI7czoxOiI5IjtzOjM6IuCtqCI7czoxOiI5IjtzOjM6IuCnrSI7czoxOiI5IjtzOjM6IuC1rSI7czoxOiI5IjtzOjQ6IvCdn5ciO3M6MToiOSI7czo0OiLwnZ+hIjtzOjE6IjkiO3M6NDoi8J2fqyI7czoxOiI5IjtzOjQ6IvCdn7UiO3M6MToiOSI7czo0OiLwnZ+/IjtzOjE6IjkiO3M6Mzoi6p2uIjtzOjE6IjkiO3M6Mzoi4rOKIjtzOjE6IjkiO3M6NDoi8JGjjCI7czoxOiI5IjtzOjQ6IvCRoqwiO3M6MToiOSI7czo0OiLwkaOWIjtzOjE6IjkiO3M6Mzoi4o26IjtzOjE6ImEiO3M6Mzoi772BIjtzOjE6ImEiO3M6NDoi8J2QmiI7czoxOiJhIjtzOjQ6IvCdkY4iO3M6MToiYSI7czo0OiLwnZKCIjtzOjE6ImEiO3M6NDoi8J2StiI7czoxOiJhIjtzOjQ6IvCdk6oiO3M6MToiYSI7czo0OiLwnZSeIjtzOjE6ImEiO3M6NDoi8J2VkiI7czoxOiJhIjtzOjQ6IvCdloYiO3M6MToiYSI7czo0OiLwnZa6IjtzOjE6ImEiO3M6NDoi8J2XriI7czoxOiJhIjtzOjQ6IvCdmKIiO3M6MToiYSI7czo0OiLwnZmWIjtzOjE6ImEiO3M6NDoi8J2aiiI7czoxOiJhIjtzOjI6IsmRIjtzOjE6ImEiO3M6MjoizrEiO3M6MToiYSI7czo0OiLwnZuCIjtzOjE6ImEiO3M6NDoi8J2bvCI7czoxOiJhIjtzOjQ6IvCdnLYiO3M6MToiYSI7czo0OiLwnZ2wIjtzOjE6ImEiO3M6NDoi8J2eqiI7czoxOiJhIjtzOjI6ItCwIjtzOjE6ImEiO3M6Mzoi77yhIjtzOjE6IkEiO3M6NDoi8J2QgCI7czoxOiJBIjtzOjQ6IvCdkLQiO3M6MToiQSI7czo0OiLwnZGoIjtzOjE6IkEiO3M6NDoi8J2SnCI7czoxOiJBIjtzOjQ6IvCdk5AiO3M6MToiQSI7czo0OiLwnZSEIjtzOjE6IkEiO3M6NDoi8J2UuCI7czoxOiJBIjtzOjQ6IvCdlawiO3M6MToiQSI7czo0OiLwnZagIjtzOjE6IkEiO3M6NDoi8J2XlCI7czoxOiJBIjtzOjQ6IvCdmIgiO3M6MToiQSI7czo0OiLwnZi8IjtzOjE6IkEiO3M6NDoi8J2ZsCI7czoxOiJBIjtzOjI6Is6RIjtzOjE6IkEiO3M6NDoi8J2aqCI7czoxOiJBIjtzOjQ6IvCdm6IiO3M6MToiQSI7czo0OiLwnZycIjtzOjE6IkEiO3M6NDoi8J2dliI7czoxOiJBIjtzOjQ6IvCdnpAiO3M6MToiQSI7czoyOiLQkCI7czoxOiJBIjtzOjM6IuGOqiI7czoxOiJBIjtzOjM6IuGXhSI7czoxOiJBIjtzOjM6IuqTriI7czoxOiJBIjtzOjQ6IvCWvYAiO3M6MToiQSI7czo0OiLwkIqgIjtzOjE6IkEiO3M6MjoiyKciO3M6MToi5SI7czoyOiLIpiI7czoxOiLFIjtzOjQ6IvCdkJsiO3M6MToiYiI7czo0OiLwnZGPIjtzOjE6ImIiO3M6NDoi8J2SgyI7czoxOiJiIjtzOjQ6IvCdkrciO3M6MToiYiI7czo0OiLwnZOrIjtzOjE6ImIiO3M6NDoi8J2UnyI7czoxOiJiIjtzOjQ6IvCdlZMiO3M6MToiYiI7czo0OiLwnZaHIjtzOjE6ImIiO3M6NDoi8J2WuyI7czoxOiJiIjtzOjQ6IvCdl68iO3M6MToiYiI7czo0OiLwnZijIjtzOjE6ImIiO3M6NDoi8J2ZlyI7czoxOiJiIjtzOjQ6IvCdmosiO3M6MToiYiI7czoyOiLGhCI7czoxOiJiIjtzOjI6ItCsIjtzOjE6ImIiO3M6Mzoi4Y+PIjtzOjE6ImIiO3M6Mzoi4ZGyIjtzOjE6ImIiO3M6Mzoi4ZavIjtzOjE6ImIiO3M6Mzoi77yiIjtzOjE6IkIiO3M6Mzoi4oSsIjtzOjE6IkIiO3M6NDoi8J2QgSI7czoxOiJCIjtzOjQ6IvCdkLUiO3M6MToiQiI7czo0OiLwnZGpIjtzOjE6IkIiO3M6NDoi8J2TkSI7czoxOiJCIjtzOjQ6IvCdlIUiO3M6MToiQiI7czo0OiLwnZS5IjtzOjE6IkIiO3M6NDoi8J2VrSI7czoxOiJCIjtzOjQ6IvCdlqEiO3M6MToiQiI7czo0OiLwnZeVIjtzOjE6IkIiO3M6NDoi8J2YiSI7czoxOiJCIjtzOjQ6IvCdmL0iO3M6MToiQiI7czo0OiLwnZmxIjtzOjE6IkIiO3M6Mzoi6p60IjtzOjE6IkIiO3M6MjoizpIiO3M6MToiQiI7czo0OiLwnZqpIjtzOjE6IkIiO3M6NDoi8J2boyI7czoxOiJCIjtzOjQ6IvCdnJ0iO3M6MToiQiI7czo0OiLwnZ2XIjtzOjE6IkIiO3M6NDoi8J2ekSI7czoxOiJCIjtzOjI6ItCSIjtzOjE6IkIiO3M6Mzoi4Y+0IjtzOjE6IkIiO3M6Mzoi4Ze3IjtzOjE6IkIiO3M6Mzoi6pOQIjtzOjE6IkIiO3M6NDoi8JCKgiI7czoxOiJCIjtzOjQ6IvCQiqEiO3M6MToiQiI7czo0OiLwkIyBIjtzOjE6IkIiO3M6Mzoi772DIjtzOjE6ImMiO3M6Mzoi4oW9IjtzOjE6ImMiO3M6NDoi8J2QnCI7czoxOiJjIjtzOjQ6IvCdkZAiO3M6MToiYyI7czo0OiLwnZKEIjtzOjE6ImMiO3M6NDoi8J2SuCI7czoxOiJjIjtzOjQ6IvCdk6wiO3M6MToiYyI7czo0OiLwnZSgIjtzOjE6ImMiO3M6NDoi8J2VlCI7czoxOiJjIjtzOjQ6IvCdlogiO3M6MToiYyI7czo0OiLwnZa8IjtzOjE6ImMiO3M6NDoi8J2XsCI7czoxOiJjIjtzOjQ6IvCdmKQiO3M6MToiYyI7czo0OiLwnZmYIjtzOjE6ImMiO3M6NDoi8J2ajCI7czoxOiJjIjtzOjM6IuG0hCI7czoxOiJjIjtzOjI6Is+yIjtzOjE6ImMiO3M6Mzoi4rKlIjtzOjE6ImMiO3M6Mjoi0YEiO3M6MToiYyI7czozOiLqrq8iO3M6MToiYyI7czo0OiLwkJC9IjtzOjE6ImMiO3M6NDoi8J+djCI7czoxOiJDIjtzOjQ6IvCRo7IiO3M6MToiQyI7czo0OiLwkaOpIjtzOjE6IkMiO3M6Mzoi77yjIjtzOjE6IkMiO3M6Mzoi4oWtIjtzOjE6IkMiO3M6Mzoi4oSCIjtzOjE6IkMiO3M6Mzoi4oStIjtzOjE6IkMiO3M6NDoi8J2QgiI7czoxOiJDIjtzOjQ6IvCdkLYiO3M6MToiQyI7czo0OiLwnZGqIjtzOjE6IkMiO3M6NDoi8J2SniI7czoxOiJDIjtzOjQ6IvCdk5IiO3M6MToiQyI7czo0OiLwnZWuIjtzOjE6IkMiO3M6NDoi8J2WoiI7czoxOiJDIjtzOjQ6IvCdl5YiO3M6MToiQyI7czo0OiLwnZiKIjtzOjE6IkMiO3M6NDoi8J2YviI7czoxOiJDIjtzOjQ6IvCdmbIiO3M6MToiQyI7czoyOiLPuSI7czoxOiJDIjtzOjM6IuKypCI7czoxOiJDIjtzOjI6ItChIjtzOjE6IkMiO3M6Mzoi4Y+fIjtzOjE6IkMiO3M6Mzoi6pOaIjtzOjE6IkMiO3M6NDoi8JCKoiI7czoxOiJDIjtzOjQ6IvCQjIIiO3M6MToiQyI7czo0OiLwkJCVIjtzOjE6IkMiO3M6NDoi8JCUnCI7czoxOiJDIjtzOjM6IuKFviI7czoxOiJkIjtzOjM6IuKFhiI7czoxOiJkIjtzOjQ6IvCdkJ0iO3M6MToiZCI7czo0OiLwnZGRIjtzOjE6ImQiO3M6NDoi8J2ShSI7czoxOiJkIjtzOjQ6IvCdkrkiO3M6MToiZCI7czo0OiLwnZOtIjtzOjE6ImQiO3M6NDoi8J2UoSI7czoxOiJkIjtzOjQ6IvCdlZUiO3M6MToiZCI7czo0OiLwnZaJIjtzOjE6ImQiO3M6NDoi8J2WvSI7czoxOiJkIjtzOjQ6IvCdl7EiO3M6MToiZCI7czo0OiLwnZilIjtzOjE6ImQiO3M6NDoi8J2ZmSI7czoxOiJkIjtzOjQ6IvCdmo0iO3M6MToiZCI7czoyOiLUgSI7czoxOiJkIjtzOjM6IuGPpyI7czoxOiJkIjtzOjM6IuGRryI7czoxOiJkIjtzOjM6IuqTkiI7czoxOiJkIjtzOjM6IuKFriI7czoxOiJEIjtzOjM6IuKFhSI7czoxOiJEIjtzOjQ6IvCdkIMiO3M6MToiRCI7czo0OiLwnZC3IjtzOjE6IkQiO3M6NDoi8J2RqyI7czoxOiJEIjtzOjQ6IvCdkp8iO3M6MToiRCI7czo0OiLwnZOTIjtzOjE6IkQiO3M6NDoi8J2UhyI7czoxOiJEIjtzOjQ6IvCdlLsiO3M6MToiRCI7czo0OiLwnZWvIjtzOjE6IkQiO3M6NDoi8J2WoyI7czoxOiJEIjtzOjQ6IvCdl5ciO3M6MToiRCI7czo0OiLwnZiLIjtzOjE6IkQiO3M6NDoi8J2YvyI7czoxOiJEIjtzOjQ6IvCdmbMiO3M6MToiRCI7czozOiLhjqAiO3M6MToiRCI7czozOiLhl54iO3M6MToiRCI7czozOiLhl6oiO3M6MToiRCI7czozOiLqk5MiO3M6MToiRCI7czozOiLihK4iO3M6MToiZSI7czozOiLvvYUiO3M6MToiZSI7czozOiLihK8iO3M6MToiZSI7czozOiLihYciO3M6MToiZSI7czo0OiLwnZCeIjtzOjE6ImUiO3M6NDoi8J2RkiI7czoxOiJlIjtzOjQ6IvCdkoYiO3M6MToiZSI7czo0OiLwnZOuIjtzOjE6ImUiO3M6NDoi8J2UoiI7czoxOiJlIjtzOjQ6IvCdlZYiO3M6MToiZSI7czo0OiLwnZaKIjtzOjE6ImUiO3M6NDoi8J2WviI7czoxOiJlIjtzOjQ6IvCdl7IiO3M6MToiZSI7czo0OiLwnZimIjtzOjE6ImUiO3M6NDoi8J2ZmiI7czoxOiJlIjtzOjQ6IvCdmo4iO3M6MToiZSI7czozOiLqrLIiO3M6MToiZSI7czoyOiLQtSI7czoxOiJlIjtzOjI6ItK9IjtzOjE6ImUiO3M6Mjoiw6kiO3M6MToiZSI7czozOiLii78iO3M6MToiRSI7czozOiLvvKUiO3M6MToiRSI7czozOiLihLAiO3M6MToiRSI7czo0OiLwnZCEIjtzOjE6IkUiO3M6NDoi8J2QuCI7czoxOiJFIjtzOjQ6IvCdkawiO3M6MToiRSI7czo0OiLwnZOUIjtzOjE6IkUiO3M6NDoi8J2UiCI7czoxOiJFIjtzOjQ6IvCdlLwiO3M6MToiRSI7czo0OiLwnZWwIjtzOjE6IkUiO3M6NDoi8J2WpCI7czoxOiJFIjtzOjQ6IvCdl5giO3M6MToiRSI7czo0OiLwnZiMIjtzOjE6IkUiO3M6NDoi8J2ZgCI7czoxOiJFIjtzOjQ6IvCdmbQiO3M6MToiRSI7czoyOiLOlSI7czoxOiJFIjtzOjQ6IvCdmqwiO3M6MToiRSI7czo0OiLwnZumIjtzOjE6IkUiO3M6NDoi8J2coCI7czoxOiJFIjtzOjQ6IvCdnZoiO3M6MToiRSI7czo0OiLwnZ6UIjtzOjE6IkUiO3M6Mjoi0JUiO3M6MToiRSI7czozOiLitLkiO3M6MToiRSI7czozOiLhjqwiO3M6MToiRSI7czozOiLqk7AiO3M6MToiRSI7czo0OiLwkaKmIjtzOjE6IkUiO3M6NDoi8JGiriI7czoxOiJFIjtzOjQ6IvCQioYiO3M6MToiRSI7czo0OiLwnZCfIjtzOjE6ImYiO3M6NDoi8J2RkyI7czoxOiJmIjtzOjQ6IvCdkociO3M6MToiZiI7czo0OiLwnZK7IjtzOjE6ImYiO3M6NDoi8J2TryI7czoxOiJmIjtzOjQ6IvCdlKMiO3M6MToiZiI7czo0OiLwnZWXIjtzOjE6ImYiO3M6NDoi8J2WiyI7czoxOiJmIjtzOjQ6IvCdlr8iO3M6MToiZiI7czo0OiLwnZezIjtzOjE6ImYiO3M6NDoi8J2YpyI7czoxOiJmIjtzOjQ6IvCdmZsiO3M6MToiZiI7czo0OiLwnZqPIjtzOjE6ImYiO3M6Mzoi6qy1IjtzOjE6ImYiO3M6Mzoi6p6ZIjtzOjE6ImYiO3M6Mjoixb8iO3M6MToiZiI7czozOiLhup0iO3M6MToiZiI7czoyOiLWhCI7czoxOiJmIjtzOjQ6IvCdiJMiO3M6MToiRiI7czozOiLihLEiO3M6MToiRiI7czo0OiLwnZCFIjtzOjE6IkYiO3M6NDoi8J2QuSI7czoxOiJGIjtzOjQ6IvCdka0iO3M6MToiRiI7czo0OiLwnZOVIjtzOjE6IkYiO3M6NDoi8J2UiSI7czoxOiJGIjtzOjQ6IvCdlL0iO3M6MToiRiI7czo0OiLwnZWxIjtzOjE6IkYiO3M6NDoi8J2WpSI7czoxOiJGIjtzOjQ6IvCdl5kiO3M6MToiRiI7czo0OiLwnZiNIjtzOjE6IkYiO3M6NDoi8J2ZgSI7czoxOiJGIjtzOjQ6IvCdmbUiO3M6MToiRiI7czozOiLqnpgiO3M6MToiRiI7czoyOiLPnCI7czoxOiJGIjtzOjQ6IvCdn4oiO3M6MToiRiI7czozOiLhlrQiO3M6MToiRiI7czozOiLqk50iO3M6MToiRiI7czo0OiLwkaOCIjtzOjE6IkYiO3M6NDoi8JGioiI7czoxOiJGIjtzOjQ6IvCQiociO3M6MToiRiI7czo0OiLwkIqlIjtzOjE6IkYiO3M6NDoi8JCUpSI7czoxOiJGIjtzOjM6Iu+9hyI7czoxOiJnIjtzOjM6IuKEiiI7czoxOiJnIjtzOjQ6IvCdkKAiO3M6MToiZyI7czo0OiLwnZGUIjtzOjE6ImciO3M6NDoi8J2SiCI7czoxOiJnIjtzOjQ6IvCdk7AiO3M6MToiZyI7czo0OiLwnZSkIjtzOjE6ImciO3M6NDoi8J2VmCI7czoxOiJnIjtzOjQ6IvCdlowiO3M6MToiZyI7czo0OiLwnZeAIjtzOjE6ImciO3M6NDoi8J2XtCI7czoxOiJnIjtzOjQ6IvCdmKgiO3M6MToiZyI7czo0OiLwnZmcIjtzOjE6ImciO3M6NDoi8J2akCI7czoxOiJnIjtzOjI6IsmhIjtzOjE6ImciO3M6Mzoi4baDIjtzOjE6ImciO3M6Mjoixo0iO3M6MToiZyI7czoyOiLWgSI7czoxOiJnIjtzOjQ6IvCdkIYiO3M6MToiRyI7czo0OiLwnZC6IjtzOjE6IkciO3M6NDoi8J2RriI7czoxOiJHIjtzOjQ6IvCdkqIiO3M6MToiRyI7czo0OiLwnZOWIjtzOjE6IkciO3M6NDoi8J2UiiI7czoxOiJHIjtzOjQ6IvCdlL4iO3M6MToiRyI7czo0OiLwnZWyIjtzOjE6IkciO3M6NDoi8J2WpiI7czoxOiJHIjtzOjQ6IvCdl5oiO3M6MToiRyI7czo0OiLwnZiOIjtzOjE6IkciO3M6NDoi8J2ZgiI7czoxOiJHIjtzOjQ6IvCdmbYiO3M6MToiRyI7czoyOiLUjCI7czoxOiJHIjtzOjM6IuGPgCI7czoxOiJHIjtzOjM6IuGPsyI7czoxOiJHIjtzOjM6IuqTliI7czoxOiJHIjtzOjM6Iu+9iCI7czoxOiJoIjtzOjM6IuKEjiI7czoxOiJoIjtzOjQ6IvCdkKEiO3M6MToiaCI7czo0OiLwnZKJIjtzOjE6ImgiO3M6NDoi8J2SvSI7czoxOiJoIjtzOjQ6IvCdk7EiO3M6MToiaCI7czo0OiLwnZSlIjtzOjE6ImgiO3M6NDoi8J2VmSI7czoxOiJoIjtzOjQ6IvCdlo0iO3M6MToiaCI7czo0OiLwnZeBIjtzOjE6ImgiO3M6NDoi8J2XtSI7czoxOiJoIjtzOjQ6IvCdmKkiO3M6MToiaCI7czo0OiLwnZmdIjtzOjE6ImgiO3M6NDoi8J2akSI7czoxOiJoIjtzOjI6ItK7IjtzOjE6ImgiO3M6Mjoi1bAiO3M6MToiaCI7czozOiLhj4IiO3M6MToiaCI7czozOiLvvKgiO3M6MToiSCI7czozOiLihIsiO3M6MToiSCI7czozOiLihIwiO3M6MToiSCI7czozOiLihI0iO3M6MToiSCI7czo0OiLwnZCHIjtzOjE6IkgiO3M6NDoi8J2QuyI7czoxOiJIIjtzOjQ6IvCdka8iO3M6MToiSCI7czo0OiLwnZOXIjtzOjE6IkgiO3M6NDoi8J2VsyI7czoxOiJIIjtzOjQ6IvCdlqciO3M6MToiSCI7czo0OiLwnZebIjtzOjE6IkgiO3M6NDoi8J2YjyI7czoxOiJIIjtzOjQ6IvCdmYMiO3M6MToiSCI7czo0OiLwnZm3IjtzOjE6IkgiO3M6MjoizpciO3M6MToiSCI7czo0OiLwnZquIjtzOjE6IkgiO3M6NDoi8J2bqCI7czoxOiJIIjtzOjQ6IvCdnKIiO3M6MToiSCI7czo0OiLwnZ2cIjtzOjE6IkgiO3M6NDoi8J2eliI7czoxOiJIIjtzOjM6IuKyjiI7czoxOiJIIjtzOjI6ItCdIjtzOjE6IkgiO3M6Mzoi4Y67IjtzOjE6IkgiO3M6Mzoi4ZW8IjtzOjE6IkgiO3M6Mzoi6pOnIjtzOjE6IkgiO3M6NDoi8JCLjyI7czoxOiJIIjtzOjI6IsubIjtzOjE6ImkiO3M6Mzoi4o2zIjtzOjE6ImkiO3M6Mzoi772JIjtzOjE6ImkiO3M6Mzoi4oWwIjtzOjE6ImkiO3M6Mzoi4oS5IjtzOjE6ImkiO3M6Mzoi4oWIIjtzOjE6ImkiO3M6NDoi8J2QoiI7czoxOiJpIjtzOjQ6IvCdkZYiO3M6MToiaSI7czo0OiLwnZKKIjtzOjE6ImkiO3M6NDoi8J2SviI7czoxOiJpIjtzOjQ6IvCdk7IiO3M6MToiaSI7czo0OiLwnZSmIjtzOjE6ImkiO3M6NDoi8J2VmiI7czoxOiJpIjtzOjQ6IvCdlo4iO3M6MToiaSI7czo0OiLwnZeCIjtzOjE6ImkiO3M6NDoi8J2XtiI7czoxOiJpIjtzOjQ6IvCdmKoiO3M6MToiaSI7czo0OiLwnZmeIjtzOjE6ImkiO3M6NDoi8J2akiI7czoxOiJpIjtzOjI6IsSxIjtzOjE6ImkiO3M6NDoi8J2apCI7czoxOiJpIjtzOjI6IsmqIjtzOjE6ImkiO3M6MjoiyakiO3M6MToiaSI7czoyOiLOuSI7czoxOiJpIjtzOjM6IuG+viI7czoxOiJpIjtzOjI6Is26IjtzOjE6ImkiO3M6NDoi8J2biiI7czoxOiJpIjtzOjQ6IvCdnIQiO3M6MToiaSI7czo0OiLwnZy+IjtzOjE6ImkiO3M6NDoi8J2duCI7czoxOiJpIjtzOjQ6IvCdnrIiO3M6MToiaSI7czoyOiLRliI7czoxOiJpIjtzOjM6IuqZhyI7czoxOiJpIjtzOjI6ItOPIjtzOjE6ImkiO3M6Mzoi6q21IjtzOjE6ImkiO3M6Mzoi4Y6lIjtzOjE6ImkiO3M6NDoi8JGjgyI7czoxOiJpIjtzOjI6IsOtIjtzOjE6ImkiO3M6Mzoi772KIjtzOjE6ImoiO3M6Mzoi4oWJIjtzOjE6ImoiO3M6NDoi8J2QoyI7czoxOiJqIjtzOjQ6IvCdkZciO3M6MToiaiI7czo0OiLwnZKLIjtzOjE6ImoiO3M6NDoi8J2SvyI7czoxOiJqIjtzOjQ6IvCdk7MiO3M6MToiaiI7czo0OiLwnZSnIjtzOjE6ImoiO3M6NDoi8J2VmyI7czoxOiJqIjtzOjQ6IvCdlo8iO3M6MToiaiI7czo0OiLwnZeDIjtzOjE6ImoiO3M6NDoi8J2XtyI7czoxOiJqIjtzOjQ6IvCdmKsiO3M6MToiaiI7czo0OiLwnZmfIjtzOjE6ImoiO3M6NDoi8J2akyI7czoxOiJqIjtzOjI6Is+zIjtzOjE6ImoiO3M6Mjoi0ZgiO3M6MToiaiI7czozOiLvvKoiO3M6MToiSiI7czo0OiLwnZCJIjtzOjE6IkoiO3M6NDoi8J2QvSI7czoxOiJKIjtzOjQ6IvCdkbEiO3M6MToiSiI7czo0OiLwnZKlIjtzOjE6IkoiO3M6NDoi8J2TmSI7czoxOiJKIjtzOjQ6IvCdlI0iO3M6MToiSiI7czo0OiLwnZWBIjtzOjE6IkoiO3M6NDoi8J2VtSI7czoxOiJKIjtzOjQ6IvCdlqkiO3M6MToiSiI7czo0OiLwnZedIjtzOjE6IkoiO3M6NDoi8J2YkSI7czoxOiJKIjtzOjQ6IvCdmYUiO3M6MToiSiI7czo0OiLwnZm5IjtzOjE6IkoiO3M6Mzoi6p6yIjtzOjE6IkoiO3M6Mjoizb8iO3M6MToiSiI7czoyOiLQiCI7czoxOiJKIjtzOjM6IuGOqyI7czoxOiJKIjtzOjM6IuGSjSI7czoxOiJKIjtzOjM6IuqTmSI7czoxOiJKIjtzOjQ6IvCdkKQiO3M6MToiayI7czo0OiLwnZGYIjtzOjE6ImsiO3M6NDoi8J2SjCI7czoxOiJrIjtzOjQ6IvCdk4AiO3M6MToiayI7czo0OiLwnZO0IjtzOjE6ImsiO3M6NDoi8J2UqCI7czoxOiJrIjtzOjQ6IvCdlZwiO3M6MToiayI7czo0OiLwnZaQIjtzOjE6ImsiO3M6NDoi8J2XhCI7czoxOiJrIjtzOjQ6IvCdl7giO3M6MToiayI7czo0OiLwnZisIjtzOjE6ImsiO3M6NDoi8J2ZoCI7czoxOiJrIjtzOjQ6IvCdmpQiO3M6MToiayI7czozOiLihKoiO3M6MToiSyI7czozOiLvvKsiO3M6MToiSyI7czo0OiLwnZCKIjtzOjE6IksiO3M6NDoi8J2QviI7czoxOiJLIjtzOjQ6IvCdkbIiO3M6MToiSyI7czo0OiLwnZKmIjtzOjE6IksiO3M6NDoi8J2TmiI7czoxOiJLIjtzOjQ6IvCdlI4iO3M6MToiSyI7czo0OiLwnZWCIjtzOjE6IksiO3M6NDoi8J2VtiI7czoxOiJLIjtzOjQ6IvCdlqoiO3M6MToiSyI7czo0OiLwnZeeIjtzOjE6IksiO3M6NDoi8J2YkiI7czoxOiJLIjtzOjQ6IvCdmYYiO3M6MToiSyI7czo0OiLwnZm6IjtzOjE6IksiO3M6MjoizpoiO3M6MToiSyI7czo0OiLwnZqxIjtzOjE6IksiO3M6NDoi8J2bqyI7czoxOiJLIjtzOjQ6IvCdnKUiO3M6MToiSyI7czo0OiLwnZ2fIjtzOjE6IksiO3M6NDoi8J2emSI7czoxOiJLIjtzOjM6IuKylCI7czoxOiJLIjtzOjI6ItCaIjtzOjE6IksiO3M6Mzoi4Y+mIjtzOjE6IksiO3M6Mzoi4ZuVIjtzOjE6IksiO3M6Mzoi6pOXIjtzOjE6IksiO3M6NDoi8JCUmCI7czoxOiJLIjtzOjI6IteAIjtzOjE6ImwiO3M6Mzoi4oijIjtzOjE6ImwiO3M6Mzoi4o+9IjtzOjE6ImwiO3M6Mzoi77+oIjtzOjE6ImwiO2k6MTtzOjE6ImwiO3M6Mjoi2aEiO3M6MToibCI7czoyOiLbsSI7czoxOiJsIjtzOjQ6IvCQjKAiO3M6MToibCI7czo0OiLwnqOHIjtzOjE6ImwiO3M6NDoi8J2fjyI7czoxOiJsIjtzOjQ6IvCdn5kiO3M6MToibCI7czo0OiLwnZ+jIjtzOjE6ImwiO3M6NDoi8J2frSI7czoxOiJsIjtzOjQ6IvCdn7ciO3M6MToibCI7czozOiLvvKkiO3M6MToibCI7czozOiLihaAiO3M6MToibCI7czozOiLihJAiO3M6MToibCI7czozOiLihJEiO3M6MToibCI7czo0OiLwnZCIIjtzOjE6ImwiO3M6NDoi8J2QvCI7czoxOiJsIjtzOjQ6IvCdkbAiO3M6MToibCI7czo0OiLwnZOYIjtzOjE6ImwiO3M6NDoi8J2VgCI7czoxOiJsIjtzOjQ6IvCdlbQiO3M6MToibCI7czo0OiLwnZaoIjtzOjE6ImwiO3M6NDoi8J2XnCI7czoxOiJsIjtzOjQ6IvCdmJAiO3M6MToibCI7czo0OiLwnZmEIjtzOjE6ImwiO3M6NDoi8J2ZuCI7czoxOiJsIjtzOjI6IsaWIjtzOjE6ImwiO3M6Mzoi772MIjtzOjE6ImwiO3M6Mzoi4oW8IjtzOjE6ImwiO3M6Mzoi4oSTIjtzOjE6ImwiO3M6NDoi8J2QpSI7czoxOiJsIjtzOjQ6IvCdkZkiO3M6MToibCI7czo0OiLwnZKNIjtzOjE6ImwiO3M6NDoi8J2TgSI7czoxOiJsIjtzOjQ6IvCdk7UiO3M6MToibCI7czo0OiLwnZSpIjtzOjE6ImwiO3M6NDoi8J2VnSI7czoxOiJsIjtzOjQ6IvCdlpEiO3M6MToibCI7czo0OiLwnZeFIjtzOjE6ImwiO3M6NDoi8J2XuSI7czoxOiJsIjtzOjQ6IvCdmK0iO3M6MToibCI7czo0OiLwnZmhIjtzOjE6ImwiO3M6NDoi8J2alSI7czoxOiJsIjtzOjI6IseAIjtzOjE6ImwiO3M6MjoizpkiO3M6MToibCI7czo0OiLwnZqwIjtzOjE6ImwiO3M6NDoi8J2bqiI7czoxOiJsIjtzOjQ6IvCdnKQiO3M6MToibCI7czo0OiLwnZ2eIjtzOjE6ImwiO3M6NDoi8J2emCI7czoxOiJsIjtzOjM6IuKykiI7czoxOiJsIjtzOjI6ItCGIjtzOjE6ImwiO3M6Mjoi04AiO3M6MToibCI7czoyOiLXlSI7czoxOiJsIjtzOjI6ItefIjtzOjE6ImwiO3M6Mjoi2KciO3M6MToibCI7czo0OiLwnriAIjtzOjE6ImwiO3M6NDoi8J66gCI7czoxOiJsIjtzOjM6Iu+6jiI7czoxOiJsIjtzOjM6Iu+6jSI7czoxOiJsIjtzOjI6It+KIjtzOjE6ImwiO3M6Mzoi4rWPIjtzOjE6ImwiO3M6Mzoi4ZuBIjtzOjE6ImwiO3M6Mzoi6pOyIjtzOjE6ImwiO3M6NDoi8Ja8qCI7czoxOiJsIjtzOjQ6IvCQiooiO3M6MToibCI7czo0OiLwkIyJIjtzOjE6ImwiO3M6NDoi8J2IqiI7czoxOiJMIjtzOjM6IuKFrCI7czoxOiJMIjtzOjM6IuKEkiI7czoxOiJMIjtzOjQ6IvCdkIsiO3M6MToiTCI7czo0OiLwnZC/IjtzOjE6IkwiO3M6NDoi8J2RsyI7czoxOiJMIjtzOjQ6IvCdk5siO3M6MToiTCI7czo0OiLwnZSPIjtzOjE6IkwiO3M6NDoi8J2VgyI7czoxOiJMIjtzOjQ6IvCdlbciO3M6MToiTCI7czo0OiLwnZarIjtzOjE6IkwiO3M6NDoi8J2XnyI7czoxOiJMIjtzOjQ6IvCdmJMiO3M6MToiTCI7czo0OiLwnZmHIjtzOjE6IkwiO3M6NDoi8J2ZuyI7czoxOiJMIjtzOjM6IuKzkCI7czoxOiJMIjtzOjM6IuGPniI7czoxOiJMIjtzOjM6IuGSqiI7czoxOiJMIjtzOjM6IuqToSI7czoxOiJMIjtzOjQ6IvCWvJYiO3M6MToiTCI7czo0OiLwkaKjIjtzOjE6IkwiO3M6NDoi8JGisiI7czoxOiJMIjtzOjQ6IvCQkJsiO3M6MToiTCI7czo0OiLwkJSmIjtzOjE6IkwiO3M6Mzoi77ytIjtzOjE6Ik0iO3M6Mzoi4oWvIjtzOjE6Ik0iO3M6Mzoi4oSzIjtzOjE6Ik0iO3M6NDoi8J2QjCI7czoxOiJNIjtzOjQ6IvCdkYAiO3M6MToiTSI7czo0OiLwnZG0IjtzOjE6Ik0iO3M6NDoi8J2TnCI7czoxOiJNIjtzOjQ6IvCdlJAiO3M6MToiTSI7czo0OiLwnZWEIjtzOjE6Ik0iO3M6NDoi8J2VuCI7czoxOiJNIjtzOjQ6IvCdlqwiO3M6MToiTSI7czo0OiLwnZegIjtzOjE6Ik0iO3M6NDoi8J2YlCI7czoxOiJNIjtzOjQ6IvCdmYgiO3M6MToiTSI7czo0OiLwnZm8IjtzOjE6Ik0iO3M6MjoizpwiO3M6MToiTSI7czo0OiLwnZqzIjtzOjE6Ik0iO3M6NDoi8J2brSI7czoxOiJNIjtzOjQ6IvCdnKciO3M6MToiTSI7czo0OiLwnZ2hIjtzOjE6Ik0iO3M6NDoi8J2emyI7czoxOiJNIjtzOjI6Is+6IjtzOjE6Ik0iO3M6Mzoi4rKYIjtzOjE6Ik0iO3M6Mjoi0JwiO3M6MToiTSI7czozOiLhjrciO3M6MToiTSI7czozOiLhl7AiO3M6MToiTSI7czozOiLhm5YiO3M6MToiTSI7czozOiLqk58iO3M6MToiTSI7czo0OiLwkIqwIjtzOjE6Ik0iO3M6NDoi8JCMkSI7czoxOiJNIjtzOjQ6IvCdkKciO3M6MToibiI7czo0OiLwnZGbIjtzOjE6Im4iO3M6NDoi8J2SjyI7czoxOiJuIjtzOjQ6IvCdk4MiO3M6MToibiI7czo0OiLwnZO3IjtzOjE6Im4iO3M6NDoi8J2UqyI7czoxOiJuIjtzOjQ6IvCdlZ8iO3M6MToibiI7czo0OiLwnZaTIjtzOjE6Im4iO3M6NDoi8J2XhyI7czoxOiJuIjtzOjQ6IvCdl7siO3M6MToibiI7czo0OiLwnZivIjtzOjE6Im4iO3M6NDoi8J2ZoyI7czoxOiJuIjtzOjQ6IvCdmpciO3M6MToibiI7czoyOiLVuCI7czoxOiJuIjtzOjI6ItW8IjtzOjE6Im4iO3M6MjoiybQiO3M6MToibiI7czozOiLvvK4iO3M6MToiTiI7czozOiLihJUiO3M6MToiTiI7czo0OiLwnZCNIjtzOjE6Ik4iO3M6NDoi8J2RgSI7czoxOiJOIjtzOjQ6IvCdkbUiO3M6MToiTiI7czo0OiLwnZKpIjtzOjE6Ik4iO3M6NDoi8J2TnSI7czoxOiJOIjtzOjQ6IvCdlJEiO3M6MToiTiI7czo0OiLwnZW5IjtzOjE6Ik4iO3M6NDoi8J2WrSI7czoxOiJOIjtzOjQ6IvCdl6EiO3M6MToiTiI7czo0OiLwnZiVIjtzOjE6Ik4iO3M6NDoi8J2ZiSI7czoxOiJOIjtzOjQ6IvCdmb0iO3M6MToiTiI7czoyOiLOnSI7czoxOiJOIjtzOjQ6IvCdmrQiO3M6MToiTiI7czo0OiLwnZuuIjtzOjE6Ik4iO3M6NDoi8J2cqCI7czoxOiJOIjtzOjQ6IvCdnaIiO3M6MToiTiI7czo0OiLwnZ6cIjtzOjE6Ik4iO3M6Mzoi4rKaIjtzOjE6Ik4iO3M6Mzoi6pOgIjtzOjE6Ik4iO3M6NDoi8JCUkyI7czoxOiJOIjtzOjM6IuCwgiI7czoxOiJvIjtzOjM6IuCygiI7czoxOiJvIjtzOjM6IuC0giI7czoxOiJvIjtzOjM6IuC2giI7czoxOiJvIjtzOjM6IuClpiI7czoxOiJvIjtzOjM6IuCppiI7czoxOiJvIjtzOjM6IuCrpiI7czoxOiJvIjtzOjM6IuCvpiI7czoxOiJvIjtzOjM6IuCxpiI7czoxOiJvIjtzOjM6IuCzpiI7czoxOiJvIjtzOjM6IuC1piI7czoxOiJvIjtzOjM6IuC5kCI7czoxOiJvIjtzOjM6IuC7kCI7czoxOiJvIjtzOjM6IuGBgCI7czoxOiJvIjtzOjI6ItmlIjtzOjE6Im8iO3M6Mjoi27UiO3M6MToibyI7czozOiLvvY8iO3M6MToibyI7czozOiLihLQiO3M6MToibyI7czo0OiLwnZCoIjtzOjE6Im8iO3M6NDoi8J2RnCI7czoxOiJvIjtzOjQ6IvCdkpAiO3M6MToibyI7czo0OiLwnZO4IjtzOjE6Im8iO3M6NDoi8J2UrCI7czoxOiJvIjtzOjQ6IvCdlaAiO3M6MToibyI7czo0OiLwnZaUIjtzOjE6Im8iO3M6NDoi8J2XiCI7czoxOiJvIjtzOjQ6IvCdl7wiO3M6MToibyI7czo0OiLwnZiwIjtzOjE6Im8iO3M6NDoi8J2ZpCI7czoxOiJvIjtzOjQ6IvCdmpgiO3M6MToibyI7czozOiLhtI8iO3M6MToibyI7czozOiLhtJEiO3M6MToibyI7czozOiLqrL0iO3M6MToibyI7czoyOiLOvyI7czoxOiJvIjtzOjQ6IvCdm5AiO3M6MToibyI7czo0OiLwnZyKIjtzOjE6Im8iO3M6NDoi8J2dhCI7czoxOiJvIjtzOjQ6IvCdnb4iO3M6MToibyI7czo0OiLwnZ64IjtzOjE6Im8iO3M6Mjoiz4MiO3M6MToibyI7czo0OiLwnZuUIjtzOjE6Im8iO3M6NDoi8J2cjiI7czoxOiJvIjtzOjQ6IvCdnYgiO3M6MToibyI7czo0OiLwnZ6CIjtzOjE6Im8iO3M6NDoi8J2evCI7czoxOiJvIjtzOjM6IuKynyI7czoxOiJvIjtzOjI6ItC+IjtzOjE6Im8iO3M6Mzoi4YO/IjtzOjE6Im8iO3M6Mjoi1oUiO3M6MToibyI7czoyOiLXoSI7czoxOiJvIjtzOjI6ItmHIjtzOjE6Im8iO3M6NDoi8J64pCI7czoxOiJvIjtzOjQ6IvCeuaQiO3M6MToibyI7czo0OiLwnrqEIjtzOjE6Im8iO3M6Mzoi77urIjtzOjE6Im8iO3M6Mzoi77usIjtzOjE6Im8iO3M6Mzoi77uqIjtzOjE6Im8iO3M6Mzoi77upIjtzOjE6Im8iO3M6Mjoi2r4iO3M6MToibyI7czozOiLvrqwiO3M6MToibyI7czozOiLvrq0iO3M6MToibyI7czozOiLvrqsiO3M6MToibyI7czozOiLvrqoiO3M6MToibyI7czoyOiLbgSI7czoxOiJvIjtzOjM6Iu+uqCI7czoxOiJvIjtzOjM6Iu+uqSI7czoxOiJvIjtzOjM6Iu+upyI7czoxOiJvIjtzOjM6Iu+upiI7czoxOiJvIjtzOjI6ItuVIjtzOjE6Im8iO3M6Mzoi4LSgIjtzOjE6Im8iO3M6Mzoi4YCdIjtzOjE6Im8iO3M6NDoi8JCTqiI7czoxOiJvIjtzOjQ6IvCRo4giO3M6MToibyI7czo0OiLwkaOXIjtzOjE6Im8iO3M6NDoi8JCQrCI7czoxOiJvIjtpOjA7czoxOiJPIjtzOjI6It+AIjtzOjE6Ik8iO3M6Mzoi4KemIjtzOjE6Ik8iO3M6Mzoi4K2mIjtzOjE6Ik8iO3M6Mzoi44CHIjtzOjE6Ik8iO3M6NDoi8JGTkCI7czoxOiJPIjtzOjQ6IvCRo6AiO3M6MToiTyI7czo0OiLwnZ+OIjtzOjE6Ik8iO3M6NDoi8J2fmCI7czoxOiJPIjtzOjQ6IvCdn6IiO3M6MToiTyI7czo0OiLwnZ+sIjtzOjE6Ik8iO3M6NDoi8J2ftiI7czoxOiJPIjtzOjM6Iu+8ryI7czoxOiJPIjtzOjQ6IvCdkI4iO3M6MToiTyI7czo0OiLwnZGCIjtzOjE6Ik8iO3M6NDoi8J2RtiI7czoxOiJPIjtzOjQ6IvCdkqoiO3M6MToiTyI7czo0OiLwnZOeIjtzOjE6Ik8iO3M6NDoi8J2UkiI7czoxOiJPIjtzOjQ6IvCdlYYiO3M6MToiTyI7czo0OiLwnZW6IjtzOjE6Ik8iO3M6NDoi8J2WriI7czoxOiJPIjtzOjQ6IvCdl6IiO3M6MToiTyI7czo0OiLwnZiWIjtzOjE6Ik8iO3M6NDoi8J2ZiiI7czoxOiJPIjtzOjQ6IvCdmb4iO3M6MToiTyI7czoyOiLOnyI7czoxOiJPIjtzOjQ6IvCdmrYiO3M6MToiTyI7czo0OiLwnZuwIjtzOjE6Ik8iO3M6NDoi8J2cqiI7czoxOiJPIjtzOjQ6IvCdnaQiO3M6MToiTyI7czo0OiLwnZ6eIjtzOjE6Ik8iO3M6Mzoi4rKeIjtzOjE6Ik8iO3M6Mjoi0J4iO3M6MToiTyI7czoyOiLVlSI7czoxOiJPIjtzOjM6IuK1lCI7czoxOiJPIjtzOjM6IuGLkCI7czoxOiJPIjtzOjM6IuCsoCI7czoxOiJPIjtzOjQ6IvCQk4IiO3M6MToiTyI7czozOiLqk7MiO3M6MToiTyI7czo0OiLwkaK1IjtzOjE6Ik8iO3M6NDoi8JCKkiI7czoxOiJPIjtzOjQ6IvCQiqsiO3M6MToiTyI7czo0OiLwkJCEIjtzOjE6Ik8iO3M6NDoi8JCUliI7czoxOiJPIjtzOjM6IuKBsCI7czoxOiK6IjtzOjM6IuG1kiI7czoxOiK6IjtzOjI6IsWQIjtzOjE6ItYiO3M6Mzoi4o20IjtzOjE6InAiO3M6Mzoi772QIjtzOjE6InAiO3M6NDoi8J2QqSI7czoxOiJwIjtzOjQ6IvCdkZ0iO3M6MToicCI7czo0OiLwnZKRIjtzOjE6InAiO3M6NDoi8J2ThSI7czoxOiJwIjtzOjQ6IvCdk7kiO3M6MToicCI7czo0OiLwnZStIjtzOjE6InAiO3M6NDoi8J2VoSI7czoxOiJwIjtzOjQ6IvCdlpUiO3M6MToicCI7czo0OiLwnZeJIjtzOjE6InAiO3M6NDoi8J2XvSI7czoxOiJwIjtzOjQ6IvCdmLEiO3M6MToicCI7czo0OiLwnZmlIjtzOjE6InAiO3M6NDoi8J2amSI7czoxOiJwIjtzOjI6Is+BIjtzOjE6InAiO3M6Mjoiz7EiO3M6MToicCI7czo0OiLwnZuSIjtzOjE6InAiO3M6NDoi8J2boCI7czoxOiJwIjtzOjQ6IvCdnIwiO3M6MToicCI7czo0OiLwnZyaIjtzOjE6InAiO3M6NDoi8J2dhiI7czoxOiJwIjtzOjQ6IvCdnZQiO3M6MToicCI7czo0OiLwnZ6AIjtzOjE6InAiO3M6NDoi8J2ejiI7czoxOiJwIjtzOjQ6IvCdnroiO3M6MToicCI7czo0OiLwnZ+IIjtzOjE6InAiO3M6Mzoi4rKjIjtzOjE6InAiO3M6Mjoi0YAiO3M6MToicCI7czozOiLvvLAiO3M6MToiUCI7czozOiLihJkiO3M6MToiUCI7czo0OiLwnZCPIjtzOjE6IlAiO3M6NDoi8J2RgyI7czoxOiJQIjtzOjQ6IvCdkbciO3M6MToiUCI7czo0OiLwnZKrIjtzOjE6IlAiO3M6NDoi8J2TnyI7czoxOiJQIjtzOjQ6IvCdlJMiO3M6MToiUCI7czo0OiLwnZW7IjtzOjE6IlAiO3M6NDoi8J2WryI7czoxOiJQIjtzOjQ6IvCdl6MiO3M6MToiUCI7czo0OiLwnZiXIjtzOjE6IlAiO3M6NDoi8J2ZiyI7czoxOiJQIjtzOjQ6IvCdmb8iO3M6MToiUCI7czoyOiLOoSI7czoxOiJQIjtzOjQ6IvCdmrgiO3M6MToiUCI7czo0OiLwnZuyIjtzOjE6IlAiO3M6NDoi8J2crCI7czoxOiJQIjtzOjQ6IvCdnaYiO3M6MToiUCI7czo0OiLwnZ6gIjtzOjE6IlAiO3M6Mzoi4rKiIjtzOjE6IlAiO3M6Mjoi0KAiO3M6MToiUCI7czozOiLhj6IiO3M6MToiUCI7czozOiLhka0iO3M6MToiUCI7czozOiLqk5EiO3M6MToiUCI7czo0OiLwkIqVIjtzOjE6IlAiO3M6NDoi8J2QqiI7czoxOiJxIjtzOjQ6IvCdkZ4iO3M6MToicSI7czo0OiLwnZKSIjtzOjE6InEiO3M6NDoi8J2ThiI7czoxOiJxIjtzOjQ6IvCdk7oiO3M6MToicSI7czo0OiLwnZSuIjtzOjE6InEiO3M6NDoi8J2VoiI7czoxOiJxIjtzOjQ6IvCdlpYiO3M6MToicSI7czo0OiLwnZeKIjtzOjE6InEiO3M6NDoi8J2XviI7czoxOiJxIjtzOjQ6IvCdmLIiO3M6MToicSI7czo0OiLwnZmmIjtzOjE6InEiO3M6NDoi8J2amiI7czoxOiJxIjtzOjI6ItSbIjtzOjE6InEiO3M6Mjoi1aMiO3M6MToicSI7czoyOiLVpiI7czoxOiJxIjtzOjM6IuKEmiI7czoxOiJRIjtzOjQ6IvCdkJAiO3M6MToiUSI7czo0OiLwnZGEIjtzOjE6IlEiO3M6NDoi8J2RuCI7czoxOiJRIjtzOjQ6IvCdkqwiO3M6MToiUSI7czo0OiLwnZOgIjtzOjE6IlEiO3M6NDoi8J2UlCI7czoxOiJRIjtzOjQ6IvCdlbwiO3M6MToiUSI7czo0OiLwnZawIjtzOjE6IlEiO3M6NDoi8J2XpCI7czoxOiJRIjtzOjQ6IvCdmJgiO3M6MToiUSI7czo0OiLwnZmMIjtzOjE6IlEiO3M6NDoi8J2agCI7czoxOiJRIjtzOjM6IuK1lSI7czoxOiJRIjtzOjQ6IvCdkKsiO3M6MToiciI7czo0OiLwnZGfIjtzOjE6InIiO3M6NDoi8J2SkyI7czoxOiJyIjtzOjQ6IvCdk4ciO3M6MToiciI7czo0OiLwnZO7IjtzOjE6InIiO3M6NDoi8J2UryI7czoxOiJyIjtzOjQ6IvCdlaMiO3M6MToiciI7czo0OiLwnZaXIjtzOjE6InIiO3M6NDoi8J2XiyI7czoxOiJyIjtzOjQ6IvCdl78iO3M6MToiciI7czo0OiLwnZizIjtzOjE6InIiO3M6NDoi8J2ZpyI7czoxOiJyIjtzOjQ6IvCdmpsiO3M6MToiciI7czozOiLqrYciO3M6MToiciI7czozOiLqrYgiO3M6MToiciI7czozOiLhtKYiO3M6MToiciI7czozOiLisoUiO3M6MToiciI7czoyOiLQsyI7czoxOiJyIjtzOjM6IuqugSI7czoxOiJyIjtzOjI6IsqAIjtzOjE6InIiO3M6NDoi8J2IliI7czoxOiJSIjtzOjM6IuKEmyI7czoxOiJSIjtzOjM6IuKEnCI7czoxOiJSIjtzOjM6IuKEnSI7czoxOiJSIjtzOjQ6IvCdkJEiO3M6MToiUiI7czo0OiLwnZGFIjtzOjE6IlIiO3M6NDoi8J2RuSI7czoxOiJSIjtzOjQ6IvCdk6EiO3M6MToiUiI7czo0OiLwnZW9IjtzOjE6IlIiO3M6NDoi8J2WsSI7czoxOiJSIjtzOjQ6IvCdl6UiO3M6MToiUiI7czo0OiLwnZiZIjtzOjE6IlIiO3M6NDoi8J2ZjSI7czoxOiJSIjtzOjQ6IvCdmoEiO3M6MToiUiI7czoyOiLGpiI7czoxOiJSIjtzOjM6IuGOoSI7czoxOiJSIjtzOjM6IuGPkiI7czoxOiJSIjtzOjQ6IvCQkrQiO3M6MToiUiI7czozOiLhlociO3M6MToiUiI7czozOiLqk6MiO3M6MToiUiI7czo0OiLwlry1IjtzOjE6IlIiO3M6Mzoi772TIjtzOjE6InMiO3M6NDoi8J2QrCI7czoxOiJzIjtzOjQ6IvCdkaAiO3M6MToicyI7czo0OiLwnZKUIjtzOjE6InMiO3M6NDoi8J2TiCI7czoxOiJzIjtzOjQ6IvCdk7wiO3M6MToicyI7czo0OiLwnZSwIjtzOjE6InMiO3M6NDoi8J2VpCI7czoxOiJzIjtzOjQ6IvCdlpgiO3M6MToicyI7czo0OiLwnZeMIjtzOjE6InMiO3M6NDoi8J2YgCI7czoxOiJzIjtzOjQ6IvCdmLQiO3M6MToicyI7czo0OiLwnZmoIjtzOjE6InMiO3M6NDoi8J2anCI7czoxOiJzIjtzOjM6IuqcsSI7czoxOiJzIjtzOjI6Isa9IjtzOjE6InMiO3M6Mjoi0ZUiO3M6MToicyI7czozOiLqrqoiO3M6MToicyI7czo0OiLwkaOBIjtzOjE6InMiO3M6NDoi8JCRiCI7czoxOiJzIjtzOjM6Iu+8syI7czoxOiJTIjtzOjQ6IvCdkJIiO3M6MToiUyI7czo0OiLwnZGGIjtzOjE6IlMiO3M6NDoi8J2RuiI7czoxOiJTIjtzOjQ6IvCdkq4iO3M6MToiUyI7czo0OiLwnZOiIjtzOjE6IlMiO3M6NDoi8J2UliI7czoxOiJTIjtzOjQ6IvCdlYoiO3M6MToiUyI7czo0OiLwnZW+IjtzOjE6IlMiO3M6NDoi8J2WsiI7czoxOiJTIjtzOjQ6IvCdl6YiO3M6MToiUyI7czo0OiLwnZiaIjtzOjE6IlMiO3M6NDoi8J2ZjiI7czoxOiJTIjtzOjQ6IvCdmoIiO3M6MToiUyI7czoyOiLQhSI7czoxOiJTIjtzOjI6ItWPIjtzOjE6IlMiO3M6Mzoi4Y+VIjtzOjE6IlMiO3M6Mzoi4Y+aIjtzOjE6IlMiO3M6Mzoi6pOiIjtzOjE6IlMiO3M6NDoi8Ja8uiI7czoxOiJTIjtzOjQ6IvCQipYiO3M6MToiUyI7czo0OiLwkJCgIjtzOjE6IlMiO3M6Mzoi6p61IjtzOjE6It8iO3M6MjoizrIiO3M6MToi3yI7czoyOiLPkCI7czoxOiLfIjtzOjQ6IvCdm4MiO3M6MToi3yI7czo0OiLwnZu9IjtzOjE6It8iO3M6NDoi8J2ctyI7czoxOiLfIjtzOjQ6IvCdnbEiO3M6MToi3yI7czo0OiLwnZ6rIjtzOjE6It8iO3M6Mzoi4Y+wIjtzOjE6It8iO3M6NDoi8J2QrSI7czoxOiJ0IjtzOjQ6IvCdkaEiO3M6MToidCI7czo0OiLwnZKVIjtzOjE6InQiO3M6NDoi8J2TiSI7czoxOiJ0IjtzOjQ6IvCdk70iO3M6MToidCI7czo0OiLwnZSxIjtzOjE6InQiO3M6NDoi8J2VpSI7czoxOiJ0IjtzOjQ6IvCdlpkiO3M6MToidCI7czo0OiLwnZeNIjtzOjE6InQiO3M6NDoi8J2YgSI7czoxOiJ0IjtzOjQ6IvCdmLUiO3M6MToidCI7czo0OiLwnZmpIjtzOjE6InQiO3M6NDoi8J2anSI7czoxOiJ0IjtzOjM6IuG0myI7czoxOiJ0IjtzOjM6IuKKpCI7czoxOiJUIjtzOjM6IuKfmSI7czoxOiJUIjtzOjQ6IvCfnagiO3M6MToiVCI7czozOiLvvLQiO3M6MToiVCI7czo0OiLwnZCTIjtzOjE6IlQiO3M6NDoi8J2RhyI7czoxOiJUIjtzOjQ6IvCdkbsiO3M6MToiVCI7czo0OiLwnZKvIjtzOjE6IlQiO3M6NDoi8J2ToyI7czoxOiJUIjtzOjQ6IvCdlJciO3M6MToiVCI7czo0OiLwnZWLIjtzOjE6IlQiO3M6NDoi8J2VvyI7czoxOiJUIjtzOjQ6IvCdlrMiO3M6MToiVCI7czo0OiLwnZenIjtzOjE6IlQiO3M6NDoi8J2YmyI7czoxOiJUIjtzOjQ6IvCdmY8iO3M6MToiVCI7czo0OiLwnZqDIjtzOjE6IlQiO3M6MjoizqQiO3M6MToiVCI7czo0OiLwnZq7IjtzOjE6IlQiO3M6NDoi8J2btSI7czoxOiJUIjtzOjQ6IvCdnK8iO3M6MToiVCI7czo0OiLwnZ2pIjtzOjE6IlQiO3M6NDoi8J2eoyI7czoxOiJUIjtzOjM6IuKypiI7czoxOiJUIjtzOjI6ItCiIjtzOjE6IlQiO3M6Mzoi4Y6iIjtzOjE6IlQiO3M6Mzoi6pOUIjtzOjE6IlQiO3M6NDoi8Ja8iiI7czoxOiJUIjtzOjQ6IvCRorwiO3M6MToiVCI7czo0OiLwkIqXIjtzOjE6IlQiO3M6NDoi8JCKsSI7czoxOiJUIjtzOjQ6IvCQjJUiO3M6MToiVCI7czo0OiLwnZCuIjtzOjE6InUiO3M6NDoi8J2RoiI7czoxOiJ1IjtzOjQ6IvCdkpYiO3M6MToidSI7czo0OiLwnZOKIjtzOjE6InUiO3M6NDoi8J2TviI7czoxOiJ1IjtzOjQ6IvCdlLIiO3M6MToidSI7czo0OiLwnZWmIjtzOjE6InUiO3M6NDoi8J2WmiI7czoxOiJ1IjtzOjQ6IvCdl44iO3M6MToidSI7czo0OiLwnZiCIjtzOjE6InUiO3M6NDoi8J2YtiI7czoxOiJ1IjtzOjQ6IvCdmaoiO3M6MToidSI7czo0OiLwnZqeIjtzOjE6InUiO3M6Mzoi6p6fIjtzOjE6InUiO3M6Mzoi4bScIjtzOjE6InUiO3M6Mzoi6q2OIjtzOjE6InUiO3M6Mzoi6q2SIjtzOjE6InUiO3M6MjoiyosiO3M6MToidSI7czoyOiLPhSI7czoxOiJ1IjtzOjQ6IvCdm5YiO3M6MToidSI7czo0OiLwnZyQIjtzOjE6InUiO3M6NDoi8J2diiI7czoxOiJ1IjtzOjQ6IvCdnoQiO3M6MToidSI7czo0OiLwnZ6+IjtzOjE6InUiO3M6Mjoi1b0iO3M6MToidSI7czo0OiLwkJO2IjtzOjE6InUiO3M6NDoi8JGjmCI7czoxOiJ1IjtzOjM6IuKIqiI7czoxOiJVIjtzOjM6IuKLgyI7czoxOiJVIjtzOjQ6IvCdkJQiO3M6MToiVSI7czo0OiLwnZGIIjtzOjE6IlUiO3M6NDoi8J2RvCI7czoxOiJVIjtzOjQ6IvCdkrAiO3M6MToiVSI7czo0OiLwnZOkIjtzOjE6IlUiO3M6NDoi8J2UmCI7czoxOiJVIjtzOjQ6IvCdlYwiO3M6MToiVSI7czo0OiLwnZaAIjtzOjE6IlUiO3M6NDoi8J2WtCI7czoxOiJVIjtzOjQ6IvCdl6giO3M6MToiVSI7czo0OiLwnZicIjtzOjE6IlUiO3M6NDoi8J2ZkCI7czoxOiJVIjtzOjQ6IvCdmoQiO3M6MToiVSI7czoyOiLVjSI7czoxOiJVIjtzOjM6IuGIgCI7czoxOiJVIjtzOjQ6IvCQk44iO3M6MToiVSI7czozOiLhkYwiO3M6MToiVSI7czozOiLqk7QiO3M6MToiVSI7czo0OiLwlr2CIjtzOjE6IlUiO3M6NDoi8JGiuCI7czoxOiJVIjtzOjM6IuKIqCI7czoxOiJ2IjtzOjM6IuKLgSI7czoxOiJ2IjtzOjM6Iu+9liI7czoxOiJ2IjtzOjM6IuKFtCI7czoxOiJ2IjtzOjQ6IvCdkK8iO3M6MToidiI7czo0OiLwnZGjIjtzOjE6InYiO3M6NDoi8J2SlyI7czoxOiJ2IjtzOjQ6IvCdk4siO3M6MToidiI7czo0OiLwnZO/IjtzOjE6InYiO3M6NDoi8J2UsyI7czoxOiJ2IjtzOjQ6IvCdlaciO3M6MToidiI7czo0OiLwnZabIjtzOjE6InYiO3M6NDoi8J2XjyI7czoxOiJ2IjtzOjQ6IvCdmIMiO3M6MToidiI7czo0OiLwnZi3IjtzOjE6InYiO3M6NDoi8J2ZqyI7czoxOiJ2IjtzOjQ6IvCdmp8iO3M6MToidiI7czozOiLhtKAiO3M6MToidiI7czoyOiLOvSI7czoxOiJ2IjtzOjQ6IvCdm44iO3M6MToidiI7czo0OiLwnZyIIjtzOjE6InYiO3M6NDoi8J2dgiI7czoxOiJ2IjtzOjQ6IvCdnbwiO3M6MToidiI7czo0OiLwnZ62IjtzOjE6InYiO3M6Mjoi0bUiO3M6MToidiI7czoyOiLXmCI7czoxOiJ2IjtzOjQ6IvCRnIYiO3M6MToidiI7czozOiLqrqkiO3M6MToidiI7czo0OiLwkaOAIjtzOjE6InYiO3M6NDoi8J2IjSI7czoxOiJWIjtzOjI6ItmnIjtzOjE6IlYiO3M6Mjoi27ciO3M6MToiViI7czozOiLihaQiO3M6MToiViI7czo0OiLwnZCVIjtzOjE6IlYiO3M6NDoi8J2RiSI7czoxOiJWIjtzOjQ6IvCdkb0iO3M6MToiViI7czo0OiLwnZKxIjtzOjE6IlYiO3M6NDoi8J2TpSI7czoxOiJWIjtzOjQ6IvCdlJkiO3M6MToiViI7czo0OiLwnZWNIjtzOjE6IlYiO3M6NDoi8J2WgSI7czoxOiJWIjtzOjQ6IvCdlrUiO3M6MToiViI7czo0OiLwnZepIjtzOjE6IlYiO3M6NDoi8J2YnSI7czoxOiJWIjtzOjQ6IvCdmZEiO3M6MToiViI7czo0OiLwnZqFIjtzOjE6IlYiO3M6Mjoi0bQiO3M6MToiViI7czozOiLitLgiO3M6MToiViI7czozOiLhj5kiO3M6MToiViI7czozOiLhkK8iO3M6MToiViI7czozOiLqm58iO3M6MToiViI7czozOiLqk6YiO3M6MToiViI7czo0OiLwlryIIjtzOjE6IlYiO3M6NDoi8JGioCI7czoxOiJWIjtzOjQ6IvCQlJ0iO3M6MToiViI7czoyOiLJryI7czoxOiJ3IjtzOjQ6IvCdkLAiO3M6MToidyI7czo0OiLwnZGkIjtzOjE6InciO3M6NDoi8J2SmCI7czoxOiJ3IjtzOjQ6IvCdk4wiO3M6MToidyI7czo0OiLwnZSAIjtzOjE6InciO3M6NDoi8J2UtCI7czoxOiJ3IjtzOjQ6IvCdlagiO3M6MToidyI7czo0OiLwnZacIjtzOjE6InciO3M6NDoi8J2XkCI7czoxOiJ3IjtzOjQ6IvCdmIQiO3M6MToidyI7czo0OiLwnZi4IjtzOjE6InciO3M6NDoi8J2ZrCI7czoxOiJ3IjtzOjQ6IvCdmqAiO3M6MToidyI7czozOiLhtKEiO3M6MToidyI7czoyOiLRoSI7czoxOiJ3IjtzOjI6ItSdIjtzOjE6InciO3M6Mjoi1aEiO3M6MToidyI7czo0OiLwkZyKIjtzOjE6InciO3M6NDoi8JGcjiI7czoxOiJ3IjtzOjQ6IvCRnI8iO3M6MToidyI7czozOiLqroMiO3M6MToidyI7czo0OiLwkaOvIjtzOjE6IlciO3M6NDoi8JGjpiI7czoxOiJXIjtzOjQ6IvCdkJYiO3M6MToiVyI7czo0OiLwnZGKIjtzOjE6IlciO3M6NDoi8J2RviI7czoxOiJXIjtzOjQ6IvCdkrIiO3M6MToiVyI7czo0OiLwnZOmIjtzOjE6IlciO3M6NDoi8J2UmiI7czoxOiJXIjtzOjQ6IvCdlY4iO3M6MToiVyI7czo0OiLwnZaCIjtzOjE6IlciO3M6NDoi8J2WtiI7czoxOiJXIjtzOjQ6IvCdl6oiO3M6MToiVyI7czo0OiLwnZieIjtzOjE6IlciO3M6NDoi8J2ZkiI7czoxOiJXIjtzOjQ6IvCdmoYiO3M6MToiVyI7czoyOiLUnCI7czoxOiJXIjtzOjM6IuGOsyI7czoxOiJXIjtzOjM6IuGPlCI7czoxOiJXIjtzOjM6IuqTqiI7czoxOiJXIjtzOjM6IuGZriI7czoxOiJ4IjtzOjI6IsOXIjtzOjE6IngiO3M6Mzoi4qSrIjtzOjE6IngiO3M6Mzoi4qSsIjtzOjE6IngiO3M6Mzoi4qivIjtzOjE6IngiO3M6Mzoi772YIjtzOjE6IngiO3M6Mzoi4oW5IjtzOjE6IngiO3M6NDoi8J2QsSI7czoxOiJ4IjtzOjQ6IvCdkaUiO3M6MToieCI7czo0OiLwnZKZIjtzOjE6IngiO3M6NDoi8J2TjSI7czoxOiJ4IjtzOjQ6IvCdlIEiO3M6MToieCI7czo0OiLwnZS1IjtzOjE6IngiO3M6NDoi8J2VqSI7czoxOiJ4IjtzOjQ6IvCdlp0iO3M6MToieCI7czo0OiLwnZeRIjtzOjE6IngiO3M6NDoi8J2YhSI7czoxOiJ4IjtzOjQ6IvCdmLkiO3M6MToieCI7czo0OiLwnZmtIjtzOjE6IngiO3M6NDoi8J2aoSI7czoxOiJ4IjtzOjI6ItGFIjtzOjE6IngiO3M6Mzoi4ZWBIjtzOjE6IngiO3M6Mzoi4ZW9IjtzOjE6IngiO3M6Mzoi4ZmtIjtzOjE6IlgiO3M6Mzoi4pWzIjtzOjE6IlgiO3M6NDoi8JCMoiI7czoxOiJYIjtzOjQ6IvCRo6wiO3M6MToiWCI7czozOiLvvLgiO3M6MToiWCI7czozOiLihakiO3M6MToiWCI7czo0OiLwnZCXIjtzOjE6IlgiO3M6NDoi8J2RiyI7czoxOiJYIjtzOjQ6IvCdkb8iO3M6MToiWCI7czo0OiLwnZKzIjtzOjE6IlgiO3M6NDoi8J2TpyI7czoxOiJYIjtzOjQ6IvCdlJsiO3M6MToiWCI7czo0OiLwnZWPIjtzOjE6IlgiO3M6NDoi8J2WgyI7czoxOiJYIjtzOjQ6IvCdlrciO3M6MToiWCI7czo0OiLwnZerIjtzOjE6IlgiO3M6NDoi8J2YnyI7czoxOiJYIjtzOjQ6IvCdmZMiO3M6MToiWCI7czo0OiLwnZqHIjtzOjE6IlgiO3M6Mzoi6p6zIjtzOjE6IlgiO3M6MjoizqciO3M6MToiWCI7czo0OiLwnZq+IjtzOjE6IlgiO3M6NDoi8J2buCI7czoxOiJYIjtzOjQ6IvCdnLIiO3M6MToiWCI7czo0OiLwnZ2sIjtzOjE6IlgiO3M6NDoi8J2epiI7czoxOiJYIjtzOjM6IuKyrCI7czoxOiJYIjtzOjI6ItClIjtzOjE6IlgiO3M6Mzoi4rWdIjtzOjE6IlgiO3M6Mzoi4Zq3IjtzOjE6IlgiO3M6Mzoi6pOrIjtzOjE6IlgiO3M6NDoi8JCKkCI7czoxOiJYIjtzOjQ6IvCQirQiO3M6MToiWCI7czo0OiLwkIyXIjtzOjE6IlgiO3M6NDoi8JCUpyI7czoxOiJYIjtzOjI6IsmjIjtzOjE6InkiO3M6Mzoi4baMIjtzOjE6InkiO3M6Mzoi772ZIjtzOjE6InkiO3M6NDoi8J2QsiI7czoxOiJ5IjtzOjQ6IvCdkaYiO3M6MToieSI7czo0OiLwnZKaIjtzOjE6InkiO3M6NDoi8J2TjiI7czoxOiJ5IjtzOjQ6IvCdlIIiO3M6MToieSI7czo0OiLwnZS2IjtzOjE6InkiO3M6NDoi8J2VqiI7czoxOiJ5IjtzOjQ6IvCdlp4iO3M6MToieSI7czo0OiLwnZeSIjtzOjE6InkiO3M6NDoi8J2YhiI7czoxOiJ5IjtzOjQ6IvCdmLoiO3M6MToieSI7czo0OiLwnZmuIjtzOjE6InkiO3M6NDoi8J2aoiI7czoxOiJ5IjtzOjI6IsqPIjtzOjE6InkiO3M6Mzoi4bu/IjtzOjE6InkiO3M6Mzoi6q2aIjtzOjE6InkiO3M6MjoizrMiO3M6MToieSI7czozOiLihL0iO3M6MToieSI7czo0OiLwnZuEIjtzOjE6InkiO3M6NDoi8J2bviI7czoxOiJ5IjtzOjQ6IvCdnLgiO3M6MToieSI7czo0OiLwnZ2yIjtzOjE6InkiO3M6NDoi8J2erCI7czoxOiJ5IjtzOjI6ItGDIjtzOjE6InkiO3M6Mjoi0q8iO3M6MToieSI7czozOiLhg6ciO3M6MToieSI7czo0OiLwkaOcIjtzOjE6InkiO3M6Mzoi77y5IjtzOjE6IlkiO3M6NDoi8J2QmCI7czoxOiJZIjtzOjQ6IvCdkYwiO3M6MToiWSI7czo0OiLwnZKAIjtzOjE6IlkiO3M6NDoi8J2StCI7czoxOiJZIjtzOjQ6IvCdk6giO3M6MToiWSI7czo0OiLwnZScIjtzOjE6IlkiO3M6NDoi8J2VkCI7czoxOiJZIjtzOjQ6IvCdloQiO3M6MToiWSI7czo0OiLwnZa4IjtzOjE6IlkiO3M6NDoi8J2XrCI7czoxOiJZIjtzOjQ6IvCdmKAiO3M6MToiWSI7czo0OiLwnZmUIjtzOjE6IlkiO3M6NDoi8J2aiCI7czoxOiJZIjtzOjI6Is6lIjtzOjE6IlkiO3M6Mjoiz5IiO3M6MToiWSI7czo0OiLwnZq8IjtzOjE6IlkiO3M6NDoi8J2btiI7czoxOiJZIjtzOjQ6IvCdnLAiO3M6MToiWSI7czo0OiLwnZ2qIjtzOjE6IlkiO3M6NDoi8J2epCI7czoxOiJZIjtzOjM6IuKyqCI7czoxOiJZIjtzOjI6ItCjIjtzOjE6IlkiO3M6Mjoi0q4iO3M6MToiWSI7czozOiLhjqkiO3M6MToiWSI7czozOiLhjr0iO3M6MToiWSI7czozOiLqk6wiO3M6MToiWSI7czo0OiLwlr2DIjtzOjE6IlkiO3M6NDoi8JGipCI7czoxOiJZIjtzOjQ6IvCQirIiO3M6MToiWSI7czo0OiLwnZCzIjtzOjE6InoiO3M6NDoi8J2RpyI7czoxOiJ6IjtzOjQ6IvCdkpsiO3M6MToieiI7czo0OiLwnZOPIjtzOjE6InoiO3M6NDoi8J2UgyI7czoxOiJ6IjtzOjQ6IvCdlLciO3M6MToieiI7czo0OiLwnZWrIjtzOjE6InoiO3M6NDoi8J2WnyI7czoxOiJ6IjtzOjQ6IvCdl5MiO3M6MToieiI7czo0OiLwnZiHIjtzOjE6InoiO3M6NDoi8J2YuyI7czoxOiJ6IjtzOjQ6IvCdma8iO3M6MToieiI7czo0OiLwnZqjIjtzOjE6InoiO3M6Mzoi4bSiIjtzOjE6InoiO3M6Mzoi6q6TIjtzOjE6InoiO3M6NDoi8JGjhCI7czoxOiJ6IjtzOjQ6IvCQi7UiO3M6MToiWiI7czo0OiLwkaOlIjtzOjE6IloiO3M6Mzoi77y6IjtzOjE6IloiO3M6Mzoi4oSkIjtzOjE6IloiO3M6Mzoi4oSoIjtzOjE6IloiO3M6NDoi8J2QmSI7czoxOiJaIjtzOjQ6IvCdkY0iO3M6MToiWiI7czo0OiLwnZKBIjtzOjE6IloiO3M6NDoi8J2StSI7czoxOiJaIjtzOjQ6IvCdk6kiO3M6MToiWiI7czo0OiLwnZaFIjtzOjE6IloiO3M6NDoi8J2WuSI7czoxOiJaIjtzOjQ6IvCdl60iO3M6MToiWiI7czo0OiLwnZihIjtzOjE6IloiO3M6NDoi8J2ZlSI7czoxOiJaIjtzOjQ6IvCdmokiO3M6MToiWiI7czoyOiLOliI7czoxOiJaIjtzOjQ6IvCdmq0iO3M6MToiWiI7czo0OiLwnZunIjtzOjE6IloiO3M6NDoi8J2coSI7czoxOiJaIjtzOjQ6IvCdnZsiO3M6MToiWiI7czo0OiLwnZ6VIjtzOjE6IloiO3M6Mzoi4Y+DIjtzOjE6IloiO3M6Mzoi6pOcIjtzOjE6IloiO3M6NDoi8JGiqSI7czoxOiJaIjtzOjI6Isa/IjtzOjE6Iv4iO3M6Mjoiz7giO3M6MToi/iI7czoyOiLPtyI7czoxOiLeIjtzOjQ6IvCQk4QiO3M6MToi3iI7fQ==";
private static function need_skip($string, $i)
{
$chars = " @\r\n\t";
if (isset($string[$i]) && strpos($chars, $string[$i]) !== false) {
$i++;
return $i;
}
return false;
}
private static function match_shortopen_tag($string, $i, $needle, $j)
{
$pos_needle = false;
$pos_string = false;
if ((isset($needle[$j - 2]) && isset($string[$i - 2]))
&& (($needle[$j - 2] == '<') && ($string[$i - 2] == '<'))
&& (isset($needle[$j - 1]) && isset($string[$i - 1]))
&& ($needle[$j - 1] == '?' && $string[$i - 1] == '?')
) {
$pos_needle = $j;
$pos_string = $i;
}
if ($pos_needle && (isset($needle[$pos_needle]) && $needle[$pos_needle] == 'p')
&& (isset($needle[$pos_needle + 1]) && $needle[$pos_needle + 1] == 'h')
&& (isset($needle[$pos_needle + 2]) && $needle[$pos_needle + 2] == 'p')
) {
$pos_needle = $pos_needle + 3;
}
if ($pos_string && (isset($string[$pos_string]) && $string[$pos_string] == 'p')
&& (isset($string[$pos_string + 1]) && $string[$pos_string + 1] == 'h')
&& (isset($string[$pos_string + 2]) && $string[$pos_string + 2] == 'p')
) {
$pos_string = $pos_string + 3;
}
return [$pos_needle, $pos_string];
}
public static function strip_whitespace($string, $save_length = false)
{
StringToStreamWrapper::prepare($string);
$strippedStr = @php_strip_whitespace(StringToStreamWrapper::WRAPPER_NAME . '://');
if (!$save_length) {
return $strippedStr;
} else {
$iMax = strlen($string);
$jMax = strlen($strippedStr);
if ($iMax != $jMax) {
$newStr = '';
$j = 0;
for ($i = 0; $i < $iMax; $i++) {
if (isset($strippedStr[$j]) && trim($string[$i]) === trim($strippedStr[$j])) {
$newStr .= $string[$i];
$j++;
} else {
$newStr .= ' ';
}
}
return $newStr;
}
return $strippedStr;
}
}
public static function normalize($string, $save_length = false)
{
$search = [ ' ;', ' =', ' ,', ' .', ' (', ' )', ' {', ' }', '; ', '= ', ', ', '. '
, '( ', '( ', '{ ', '} ', ' !', ' >', ' <', ' _', '_ ', '< ', '> ', ' $', ' %', '% '
, '# ', ' #', '^ ', ' ^', ' &', '& ', ' ?', '? '];
$replace = [ ';', '=', ',', '.', '(', ')', '{', '}', ';', '=', ',', '.'
, '(', ')', '{', '}', '!', '>', '<', '_', '_', '<', '>', '$', '%', '%'
, '#', '#', '^', '^', '&', '&', '?', '?'];
if (!$save_length) {
$string = str_replace('@', '', $string);
$string = preg_replace('~\s+~smi', ' ', $string);
$string = str_replace($search, $replace, $string);
}
$string = preg_replace_callback('~\bchr\(\s*([0-9a-fA-FxX]+)\s*\)~', function($m) use ($save_length) {
if ($save_length) {
return str_pad("'" . @chr(intval($m[1], 0)) . "'", strlen($m[0]), ' ');
} else {
return "'" . @chr(intval($m[1], 0)) . "'";
}
}, $string);
for ($i = 0; $i < 2; $i++) {
$string = preg_replace_callback('~%([0-9a-fA-F]{2})~', function($m) use ($save_length) {
if ($save_length) {
return str_pad(chr(@hexdec($m[1])), strlen($m[0]), ' ');
} else {
return @chr(hexdec($m[1]));
}
}, $string);
}
$iter = 0;
$regexpHtmlAmp = '/\&[#\w]{2,20};/i';
while ($iter < self::MAX_ITERATION && preg_match($regexpHtmlAmp, $string)) {
$string = preg_replace_callback($regexpHtmlAmp, function ($m) use ($save_length) {
if ($save_length) {
return str_pad(@html_entity_decode($m[0], ENT_QUOTES | ENT_HTML5), strlen($m[0]), ' ', STR_PAD_LEFT);
} else {
return @html_entity_decode($m[0], ENT_QUOTES | ENT_HTML5);
}
}, $string);
$iter++;
}
$string = preg_replace_callback('/\\\\x([a-fA-F0-9]{1,2})/i', function($m) use ($save_length) {
if ($save_length) {
return str_pad(chr(@hexdec($m[1])), strlen($m[0]), ' ');
} else {
return @chr(hexdec($m[1]));
}
}, $string);
$string = preg_replace_callback('/\\\\([0-9]{1,3})/i', function($m) use ($save_length) {
if ($save_length) {
return str_pad(@chr(octdec($m[1])), strlen($m[0]), ' ');
} else {
return @chr(octdec($m[1]));
}
}, $string);
$string = preg_replace_callback('/[\'"]\s*?\.+\s*?[\'"]/smi', function($m) use ($save_length) {
if ($save_length) {
return str_repeat(' ', strlen($m[0]));
} else {
return '';
}
}, $string);
$string = preg_replace_callback('/[\'"]\s*?\++\s*?[\'"]/smi', function($m) use ($save_length) {
if ($save_length) {
return str_repeat(' ', strlen($m[0]));
} else {
return '';
}
}, $string);
$string = preg_replace_callback('~
]{0,99}>\s*\K(.{0,300}?)(?=<\/title>)~mis', function($m) use ($save_length) {
if(preg_match('~(?:\w[^\x00-\x7F]{1,9}|[^\x00-\x7F]{1,9}\w)~', $m[1])) {
return self::HomoglyphNormalize($m[1]);
}
return $m[1];
}, $string);
if (!$save_length) {
$string = str_replace(' file not listed in cloud db
const HEURISTIC = 4; // detected as heuristic
const CONFLICT = 5; // we have filename hashing conflict for this file
const NEWFILE = 0; // this is a new file (or content changed)
const RX_MALWARE = 7; // detected as malware by rx scan
const RX_SUSPICIOUS = 8; // detected as suspicious by rx scan
const RX_GOOD = 9; // detected as good by rx scan
const RX_SKIPPED_SMART = 10; // skipped by smart scan
/**
* @var string;
*/
private $filename;
/**
* @var int
*/
private $key;
/**
* @var int
*/
private $scanned_ts;
/**
* @var int
*/
private $updated_ts;
/**
* @var int
*/
private $verdict;
/**
* @var string
*/
private $sha2;
/**
* @var string
*/
private $signature = '';
/**
* @var string
*/
private $snippet = '';
/**
* RapidScanStorageRecord constructor.
* @param $key
* @param $scanned_ts
* @param int $verdict
* @param $sha2
* @param string $signature
*/
private function __construct($key, $scanned_ts, $verdict, $sha2, $signature, $filename, $snippet, $updated_ts = 0)
{
$this->filename = $filename;
$this->key = $key;
$this->scanned_ts = $scanned_ts;
$this->verdict = $verdict;
$this->sha2 = $sha2;
$this->snippet = $snippet;
$this->updated_ts = $updated_ts;
if ($signature !== '') {
$this->signature = self::padTo10Bytes($signature);
}
}
/**
* Create db storage record from file
* @param $filename
* @param string $signature
* @param int $verdict
* @return RapidScanStorageRecord
* @throws Exception
*/
public static function fromFile($file, $signature = '', $verdict = self::UNKNOWN, $snippet = '')
{
$filename = '';
$stat = [];
$inode = 0;
$ctime = 0;
$mtime = 0;
if (is_string($file) && file_exists($file)) {
$filename = $file;
$stat = stat($filename);
$inode = $stat['ino'];
$ctime = $stat['ctime'];
$mtime = $stat['mtime'];
} else if ($file instanceof FileInfo && file_exists($file->getFilename())){
$filename = $file->getFilename();
$inode = $file->getInode();
$ctime = $file->getCreated();
$mtime = $file->getModified();
}
if (!file_exists($filename)) {
throw new Exception('File \'' . $filename . '\' doesn\'t exists.');
}
$key = (int)((string)self::fileNameHash($filename) . (string)$inode);
$scanned_ts = time();
$updated_ts = max($mtime, $ctime);
$sha2 = '';
if (!$verdict) {
$verdict = self::NEWFILE;
}
if ($signature!=='') {
$signature = self::padTo10Bytes($signature);
}
return new self($key, $scanned_ts, $verdict, $sha2, $signature, $filename, $snippet, $updated_ts);
}
/**
* @param $array
* @return RapidScanStorageRecord
*/
public static function fromArray($array)
{
$key = $array['key'];
$scanned_ts = $array['scanned_ts'];
$sha2 = hex2bin($array['sha2']);
$verdict = $array['verdict'];
$signature = $array['signature'];
return new self($key, $scanned_ts, $verdict, $sha2, $signature, '', '');
}
/**
* @return array
*/
public function toArray()
{
$array['key'] = $this->key;
$array['scanned_ts'] = $this->scanned_ts;
$array['verdict'] = $this->verdict;
$array['sha2'] = bin2hex($this->sha2);
$array['signature'] = $this->signature;
return $array;
}
/**
* @param $value
* @return int
*/
public static function getTsFromValue($value)
{
$timestamp = unpack("l", substr($value, 0, 8));
$scanned_ts = array_pop($timestamp);
return $scanned_ts;
}
/**
* @param $value
* @return int
*/
public static function getVerdictFromValue($value)
{
return (int)ord($value[8]);
}
/**
* @return array
*/
public function calcSha2()
{
$this->sha2 = hash('sha256', file_get_contents($this->filename), true);
}
/**
* @param $verdict
*/
public function setVerdict($verdict)
{
$this->verdict = $verdict;
}
/**
* @return int
*/
public function getKey()
{
return $this->key;
}
/**
* @param $signature
*/
public function setSignature($signature)
{
if ($signature!=='') {
$this->signature = self::padTo10Bytes($signature);
}
}
/**
* @param $ts
*/
public function setScannedTs($ts)
{
$this->scanned_ts = $ts;
}
/**
* Unpack bytestring $value to RapidScanStorageRecord
* @param $hash
* @param $value
* @return RapidScanStorageRecord
*/
public static function unpack($hash, $value)
{
// pack format
// 8 bytes timestamp
// 1 byte verdict
// 32 bytes sha2
// 10 bytes signature (only for BLACK, DUAL_USE, RX_MALWARE, RX_SUSPICIOUS)
// note - we will hold bloomfilter for file later on for those that are WHITE
// it will be used to detect installed apps
$signature = '';
$timestamp = unpack("l", substr($value, 0, 8));
$scanned_ts = array_pop($timestamp);
$verdict = $value[8];
$verdict = (int)ord($verdict);
$sha2 = substr($value, 9, 32);
if (in_array($verdict, array(self::BLACK, self::DUAL_USE, self::RX_MALWARE, self::RX_SUSPICIOUS))) {
$signature = substr($value, 41, 10); # 10 bytes signature string
}
if (strlen($value) > 51) {
$snippet = substr($value, 51);
} else {
$snippet = '';
}
return new self($hash, $scanned_ts, $verdict, $sha2, $signature, '', $snippet);
}
/**
* Pack RapidScanStorageRecord to bytestring to save in db
* @return string
*/
public function pack()
{
$signature = '';
if (strlen($this->signature) > 0) {
$signature = $this->signature;
}
return (($this->scanned_ts < 0) ? str_pad(pack("l", $this->scanned_ts), 8, "\xff") : str_pad(pack("l", $this->scanned_ts), 8, "\x00")) . pack("c", $this->verdict) . $this->sha2 . $signature . $this->snippet;
}
/**
* Hash function for create hash of full filename to store in db as key
* @param $str
* @return int
*/
private static function fileNameHash($str)
{
for ($i = 0, $h = 5381, $len = strlen($str); $i < $len; $i++) {
$h = (($h << 5) + $h + ord($str[$i])) & 0x7FFFFFFF;
}
return $h;
}
/**
* Convert string to utf-8 and fitting/padding it to 10 bytes
* @param $str
* @return string
*/
private static function padTo10Bytes($str)
{
# convert string to bytes in UTF8, and add 0 bytes to pad it to 10
# cut to 10 bytes of necessary
$str = utf8_encode($str);
$len = strlen($str);
if ($len < 10) {
$str = str_pad($str, 10, "\x00");
} elseif ($len > 10) {
$str = substr($str, 0, 10);
}
return $str;
}
/**
* @return int
*/
public function getScannedTs()
{
return $this->scanned_ts;
}
/**
* @return int
*/
public function getUpdatedTs()
{
return $this->updated_ts;
}
/**
* @return int
*/
public function getVerdict()
{
return $this->verdict;
}
/**
* @return string
*/
public function getSha2()
{
return $this->sha2;
}
/**
* @return string
*/
public function getSignature()
{
return $this->signature;
}
/**
* @return string
*/
public function getFilename()
{
return $this->filename;
}
/**
* @param $filename
*/
public function setFilename($filename)
{
$this->filename = $filename;
$stat = stat($filename);
$this->updated_ts = max($stat['mtime'], $stat['ctime']);
}
/**
* @return string
*/
public function getSnippet()
{
return $this->snippet;
}
/**
* @param $filename
*/
public function setSnippet($snippet)
{
$this->snippet = $snippet;
}
}
/**
* Interface RapidScanStorage implements class to work with RapidScan db
* @package Aibolit\Lib\Scantrack
*/
class RapidScanStorage
{
const DB_VERSION = '1.0';
protected $db_ver = '0';
/**
* @var string
*/
protected $old_dir;
/**
* @var string
*/
protected $new_dir;
/**
* @var resource
*/
protected $new_db;
/**
* @var resource
*/
protected $old_db;
/**
* @var resource
*/
private $wb;
/**
* @var int
*/
public $batch_count;
/**
* RapidScanStorage constructor.
* @param $base - folder where db located
*/
public function __construct($base)
{
if(!is_dir($base) && !mkdir($base) && !is_dir($base)) {
throw new Exception(sprintf('Directory "%s" was not created', $base));
}
$this->old_dir = $base . '/current';
$this->new_dir = $base . '/new';
$options = [
'create_if_missing' => true,
'compression' => LEVELDB_NO_COMPRESSION
];
$this->db_ver = $this->getDbVersion();
if ($this->needMigrate()) {
$this->migrateDb();
}
$this->new_db = new LevelDB($this->new_dir, $options);
$this->old_db = new LevelDB($this->old_dir, $options);
$this->storeVersion();
$this->wb = NULL; // will be use to track writing to batch
$this->batch_count = 0;
}
/**
* @param RapidScanStorageRecord $record
* @return bool
*/
public function put(RapidScanStorageRecord $record)
{
$this->startBatch();
$this->batch_count++;
$value = $this->wb->put(pack('P', $record->getKey()), $record->pack());
return $value;
}
/**
* @param $hash
* @return bool|RapidScanStorageRecord
*/
public function getNew($hash)
{
$value = $this->new_db->get(pack('P', $hash));
if($value) {
$return = RapidScanStorageRecord::unpack($hash, $value);
return $return;
}
return false;
}
/**
* @param $hash
* @return bool|RapidScanStorageRecord
*/
public function getOld($hash)
{
$value = $this->old_db->get(pack('P', $hash));
if($value) {
$return = RapidScanStorageRecord::unpack($hash, $value);
return $return;
}
return false;
}
/**
* @param $hash
* @return bool
*/
public function delete($hash)
{
$return = $this->new_db->delete(pack('P', $hash));
return $return;
}
/**
* Close db, remove old db, move new to a new space
*/
public function finish()
{
$this->old_db->close();
$this->flushBatch();
$this->new_db->close();
self::rmtree($this->old_dir);
rename($this->new_dir, $this->old_dir);
}
/**
* Start batch operations
*/
private function startBatch()
{
if(!$this->wb) {
$this->wb = new LevelDBWriteBatch();
$this->batch_count = 0;
}
}
/**
* write all data in a batch, reset batch
*/
public function flushBatch()
{
if ($this->wb) {
$this->new_db->write($this->wb);
$this->batch_count = 0;
$this->wb = NULL;
}
}
/**
* Helper function to remove folder tree
* @param $path
*/
public static function rmTree($path)
{
if (is_dir($path)) {
foreach (scandir($path) as $name) {
if (in_array($name, ['.', '..'])) {
continue;
}
$subpath = $path.DIRECTORY_SEPARATOR . $name;
self::rmTree($subpath);
}
rmdir($path);
} else {
unlink($path);
}
}
public function getOldTsForRescan($freq, $limit)
{
$freq = $freq == 0 ? 1 : $freq;
$i = 0;
$k = 0;
$timestamps = [];
$first_key = false;
$it = $this->old_db->getIterator();
$rewind_cnt = 0;
for ($it->seek(random_bytes(1)), $i = 0; $i <= $limit; $it->next()) {
if (!$it->valid()) {
if ($rewind_cnt > 2) { // Empty base
break;
}
$it->rewind();
$rewind_cnt++;
}
$key = $it->key();
if ($first_key === false) {
$first_key = $key;
} elseif ($first_key === $key) {
break;
}
$value = $it->current();
if (!$value) {
continue;
}
if (RapidScanStorageRecord::getVerdictFromValue($value) === RapidScanStorageRecord::RX_GOOD) {
$i++;
$timestamps[] = RapidScanStorageRecord::getTsFromValue($value);
}
}
$it->destroy();
unset($it);
sort($timestamps, SORT_NUMERIC);
if ($i < $limit) {
$limit = $i;
$freq = 1;
}
$k = (int)($limit / $freq) - 1;
return isset($timestamps[$k]) ? $timestamps[$k] : false;
}
public function getVersion()
{
return $this->db_ver;
}
private function getDbVersion()
{
if (!file_exists($this->old_dir . '/version.txt')) {
return '0';
}
return trim(file_get_contents($this->old_dir . '/version.txt'));
}
private function needMigrate()
{
if ($this->db_ver === '0') {
return true;
}
return false;
}
private function migrateDb()
{
$options = [
'create_if_missing' => true,
'compression' => LEVELDB_NO_COMPRESSION
];
$this->new_db = new LevelDB($this->new_dir, $options);
$this->old_db = new LevelDB($this->old_dir, $options);
$this->wb = NULL; // will be use to track writing to batch
$this->batch_count = 0;
$this->startBatch();
foreach($this->old_db->getIterator() as $key => $value) {
$this->new_db->put(pack('P', (int)$key), $value);
$this->batch_count++;
if ($this->batch_count > 1000) {
$this->flushBatch();
}
}
$this->flushBatch();
$this->storeVersion();
$this->finish();
}
private function storeVersion()
{
file_put_contents($this->new_dir . '/version.txt', RapidScanStorage::DB_VERSION);
}
}
/**
* For work with Cloud Assisted Storage
* @package Aibolit\Lib\Scantrack
*/
class CloudAssistedStorage
{
private $db_filepath = '';
public function __construct($folder)
{
if(!is_dir($folder) && !mkdir($folder) && !is_dir($folder)) {
throw new Exception(sprintf('Directory "%s" was not created', $folder));
}
$this->db_filepath = $folder . DIRECTORY_SEPARATOR . 'cloud_assisted_verdicts.json';
}
public function getList()
{
if (!file_exists($this->db_filepath)) {
return [];
}
$content = file_get_contents($this->db_filepath);
if (!$content) {
return [];
}
$list = json_decode($content, true);
if (!$list || !is_array($list)) {
return [];
}
return $list;
}
public function putList($list)
{
file_put_contents($this->db_filepath, json_encode($list));
}
public function delete()
{
if (!file_exists($this->db_filepath)) {
return;
}
unlink($this->db_filepath);
}
}
/**
* This is actual class that does account level scan
* and remembers the state of scan
* Class RapidAccountScan
* @package Aibolit\Lib\Scantrack
*/
class RapidAccountScan
{
const RESCAN_ALL = 0; // mode of operation --> rescan all files that are not white/black/dual_use using cloud scanner & regex scanner
const RESCAN_NONE = 1; // don't re-scan any files that we already scanned
const RESCAN_SUSPICIOUS = 2; // only re-scan suspicious files using only regex scanner
const MAX_BATCH = 1000; // max files to write in a db batch write
const MAX_TO_SCAN = 1000; // max files to scan using cloud/rx scanner at a time
private $db;
private $cas_db;
private $cas_list = [];
private $vars = null;
private $scanlist;
private $collisions;
private $processedFiles;
private $rescan_count = 0;
private $rescan_rx_good_count = 0;
private $counter = 0;
private $str_error = false;
private $scanner = null;
private $freq;
private $old_rescan_ts = false;
/**
* RapidAccountScan constructor.
* @param RapidScanStorage $rapidScanStorage
*/
public function __construct($scanner, $rapidScanStorage, $cloudAssistedStorage, &$vars, $counter = 0)
{
$this->db = $rapidScanStorage;
$this->cas_db = $cloudAssistedStorage;
$this->vars = $vars;
$this->scanlist = [];
$this->collisions = [];
$this->processedFiles = 0;
$this->counter = $counter;
$this->scanner = $scanner;
$this->freq = $vars->options['rapid-scan-rescan-frequency'] ?? false;
if ($this->freq !== false) {
$this->old_rescan_ts = $this->db->getOldTsForRescan($this->freq, 1000);
}
}
/**
* Get str error
*/
public function getStrError()
{
return $this->str_error;
}
/**
* Get count of rescan(regexp) files
*/
public function getRescanCount()
{
return $this->rescan_count;
}
/**
* Get count of rescan(regexp) RX_GOOD files
*/
public function getRescanRxGoodCount()
{
return $this->rescan_rx_good_count;
}
/**
* placeholder for actual regex scan
* return RX_GOOD, RX_MALWARE, RX_SUSPICIOUS and signature from regex scaner
* if we got one
*/
private function regexScan($filename, $i, $vars)
{
$this->rescan_count++;
printProgress(++$this->processedFiles, $filename, $vars);
$return = $this->scanner->QCR_ScanFile($filename, $vars, null, $i, false);
return $return;
}
/**
* we will have batch of new files that we will scan
* here we will write them into db once we scanned them
* we need to check that there is no conflicts/collisions
* in names, for that we check for data in db if such filename_hash
* already exists, but we also keep set of filename_hashes of given
* batch, to rule out conflicts in current batch as well
*/
private function writeNew()
{
$this->collisions = [];
foreach ($this->scanlist as $fileinfo) {
if (in_array($fileinfo->getKey(), $this->collisions) || $this->db->getNew($fileinfo->getKey())) {
$fileinfo->setVerdict(RapidScanStorageRecord::CONFLICT);
}
$this->collisions [] = $fileinfo->getKey();
$this->db->put($fileinfo);
}
}
/**
* given a batch do cloudscan
* @throws \Exception
*/
private function doCloudScan()
{
if (count($this->scanlist) <= 0) {
return;
}
$index_table = [];
$blackfiles = [];
$sha_list = [];
foreach ($this->scanlist as $i => $fileinfo) {
$sha_list[] = bin2hex($fileinfo->getSha2());
$index_table[] = $i;
$fileinfo->setVerdict(RapidScanStorageRecord::UNKNOWN);
}
$ca = Factory::instance()->create(CloudAssistedRequest::class, [CLOUD_ASSIST_TOKEN]);
$white_raw = [];
$black_raw = [];
$verdicts_black_raw = [];
try {
list($white_raw, $black_raw, $verdicts_black_raw) = $ca->checkFilesByHash($sha_list);
} catch (\Exception $e) {
$this->str_error = $e->getMessage();
}
$dual = array_intersect($white_raw, $black_raw);
$black_raw = array_diff($black_raw, $white_raw);
foreach ($white_raw as $index) {
$this->scanlist[$index_table[$index]]->setVerdict(RapidScanStorageRecord::WHITE);
}
$signatures_db = [];
foreach ($black_raw as $i => $index) {
$this->scanlist[$index_table[$index]]->setVerdict(RapidScanStorageRecord::BLACK);
$signature = isset($verdicts_black_raw[$i]) ? $verdicts_black_raw[$i] : '';
$signature_id = 'c_' . hash('crc32', $signature);
$signatures_db[$signature_id] = $signature;
$this->scanlist[$index_table[$index]]->setSignature($signature_id);
$blackfiles[$this->scanlist[$index_table[$index]]->getFilename()] = [
'h' => $sha_list[$index],
'ts' => time(),
'sn' => $signature,
'ras_sigid' => $signature_id,
];
}
$signatures_list = $this->cas_db->getList();
foreach ($signatures_db as $hash => $sig) {
$this->cas_list[$hash] = $sig;
if (isset($signatures_list[$hash])) {
continue;
}
$signatures_list[$hash] = $sig;
}
$this->cas_db->putList($signatures_list);
foreach ($dual as $index) {
$this->scanlist[$index_table[$index]]->setVerdict(RapidScanStorageRecord::DUAL_USE);
$this->scanlist[$index_table[$index]]->setSignature('DUAL'); //later on we will get sig info from cloud
}
// we can now update verdicts in batch for those that we know
//add entries to report, when needed
$this->vars->blackFiles = array_merge($this->vars->blackFiles, $blackfiles);
unset($white_raw, $black_raw, $dual, $sha_list, $index_table);
}
/**
* regex scan a single file, add entry to report if needed
* @param $fileInfo
* @param $i
*/
private function _regexScan($fileInfo, $i, $vars)
{
$regex_res = $this->regexScan($fileInfo->getFilename(), $i, $vars);
if (!is_array($regex_res)) {
return;
}
list($result, $sigId, $snippet) = $regex_res;
$fileInfo->setVerdict($result);
$fileInfo->setScannedTs(time());
if ($result !== RapidScanStorageRecord::RX_GOOD || $result !== RapidScanStorageRecord::RX_SKIPPED_SMART) {
$fileInfo->setSignature($sigId);
$fileInfo->setSnippet($snippet);
}
}
/**
* regex scan batch of files.
*/
private function doRegexScan($vars)
{
foreach ($this->scanlist as $i => $fileinfo) {
if (!in_array($fileinfo->getVerdict(), [
RapidScanStorageRecord::WHITE,
RapidScanStorageRecord::BLACK,
RapidScanStorageRecord::DUAL_USE
])
) {
$this->_regexScan($fileinfo, $i, $vars);
}
}
}
private function processScanList($vars)
{
$this->doCloudScan();
$this->doRegexScan($vars);
$this->writeNew();
$this->scanlist = [];
}
private function scanFile($filename, $rescan, $i, $vars)
{
$rxgood_rescan = false;
if ($vars->fileinfo !== null) {
if($filename !== $vars->fileinfo->getFilename()) {
unset($file);
$vars->fileinfo = null;
}
}
if ($vars->fileinfo == null) {
$file = new FileInfo($filename, $i);
$vars->fileinfo = $file;
}
$file = $vars->fileinfo;
if (!file_exists($filename)) {
return false;
}
$file = RapidScanStorageRecord::fromFile($file);
$old_value = $this->db->getOld($file->getKey());
$old_scanned = 0;
if ($old_value) {
$old_scanned = $old_value->getScannedTs();
if ($file->getUpdatedTs() <= $old_scanned) {
$file = $old_value;
$file->setFilename($filename);
}
}
if ($file->getVerdict() === RapidScanStorageRecord::UNKNOWN
|| $file->getVerdict() === RapidScanStorageRecord::CONFLICT
|| $file->getUpdatedTs() > $old_scanned
) {
// these files has changed or we know nothing about them, lets re-calculate sha2
// and do full scan
$file->calcSha2();
$file->setVerdict(RapidScanStorageRecord::NEWFILE);
$this->scanlist[$i] = $file;
} elseif ($file->getVerdict() === RapidScanStorageRecord::BLACK) {
//these files hasn't changed, but need to be reported as they are on one of the lists
$signature_id = $file->getSignature();
$signature = isset($this->cas_list[$signature_id]) ? $this->cas_list[$signature_id] : '';
$this->vars->blackFiles[$filename] = [
'h' => bin2hex($file->getSha2()),
'ts' => time(),
'sn' => $signature,
'ras_sigid' => $signature_id,
];
$this->db->put($file);
} elseif ($file->getVerdict() === RapidScanStorageRecord::DUAL_USE) {
$this->db->put($file);
} elseif (($rescan === self::RESCAN_SUSPICIOUS || $rescan === self::RESCAN_NONE)
&& $file->getVerdict() === RapidScanStorageRecord::RX_MALWARE
) {
//this files were detected as rx malware before, let's report them
$sigId = trim($file->getSignature(), "\0");
if (isset($sigId) && isset($vars->signs->_Mnemo[$sigId])) {
$snippet = $file->getSnippet();
if (strtolower(pathinfo($filename, PATHINFO_EXTENSION)) == 'js') {
$vars->criticalJS[] = $i;
$vars->criticalJSFragment[] = $snippet;
$vars->criticalJSSig[] = $sigId;
} else {
$vars->criticalPHP[] = $i;
$vars->criticalPHPFragment[] = $snippet;
$vars->criticalPHPSig[] = $sigId;
}
$this->scanner->AddResult($vars->fileinfo, $i, $vars);
$this->db->put($file);
} else {
$this->scanlist[$i] = $file;
}
} elseif ($rescan === self::RESCAN_NONE && AI_EXTRA_WARN
&& $file->getVerdict() === RapidScanStorageRecord::RX_SUSPICIOUS
) {
//this files were detected as rx suspicious before, let's report them
$sigId = trim($file->getSignature(), "\0");
if (isset($sigId) && isset($vars->signs->_Mnemo[$sigId])) {
$snippet = $file->getSnippet();
$vars->warningPHP[] = $i;
$vars->warningPHPFragment[] = $snippet;
$vars->warningPHPSig[] = $sigId;
$this->scanner->AddResult($vars->fileinfo, $i, $vars);
$this->db->put($file);
} else {
$this->scanlist[$i] = $file;
}
} elseif ((
$rescan === self::RESCAN_ALL
&& in_array($file->getVerdict(), [
RapidScanStorageRecord::RX_SUSPICIOUS,
RapidScanStorageRecord::RX_GOOD,
RapidScanStorageRecord::RX_MALWARE
])
)
|| (
$rescan === self::RESCAN_SUSPICIOUS
&& $file->getVerdict() === RapidScanStorageRecord::RX_SUSPICIOUS
)
|| (
$this->freq !== false && $this->old_rescan_ts !== false
&& $rescan === self::RESCAN_SUSPICIOUS
&& $file->getVerdict() === RapidScanStorageRecord::RX_GOOD
&& $old_scanned <= $this->old_rescan_ts
&& $rxgood_rescan = true
)
) {
//rescan all mode, all none white/black/dual listed files need to be re-scanned fully
if ($rxgood_rescan) {
$this->rescan_rx_good_count++;
}
$this->scanlist[$i] = $file;
} elseif (defined('USE_HEURISTICS')
&& $file->getVerdict() === RapidScanStorageRecord::HEURISTIC
) {
//this files were detected as HEURISTIC before, let's report them as malware
$sigId = trim($file->getSignature(), "\0");
$snippet = $file->getSnippet();
$vars->criticalPHP[] = $i;
$vars->criticalPHPFragment[] = $snippet;
$vars->criticalPHPSig[] = $sigId;
$this->scanner->AddResult($vars->fileinfo, $i, $vars);
$this->db->put($file);
} elseif (defined('USE_HEURISTICS_SUSPICIOUS')
&& $file->getVerdict() === RapidScanStorageRecord::HEURISTIC
) {
//this files were detected as HEURISTIC, let's report them as suspicious
$sigId = trim($file->getSignature(), "\0");
$snippet = $file->getSnippet();
$vars->warningPHP[] = $i;
$vars->warningPHPFragment[] = $snippet;
$vars->warningPHPSig[] = $sigId;
$this->scanner->AddResult($vars->fileinfo, $i, $vars);
$this->db->put($file);
} else {
//in theory -- we should have only white files here...
$this->db->put($file);
}
if (count($this->scanlist) >= self::MAX_TO_SCAN) {
// our scan list is big enough
// let's flush db, and scan the list
$this->db->flushBatch();
$this->processScanList($vars);
}
if ($this->db->batch_count >= self::MAX_BATCH) {
//we have added many entries to db, time to flush it
$this->db->flushBatch();
$this->processScanList($vars);
}
unset($file);
$vars->fileinfo = null;
}
public function scan($files, $vars, $rescan = self::RESCAN_SUSPICIOUS)
{
$i = 0;
$this->cas_list = $this->cas_db->getList();
foreach ($files as $filepath) {
$counter = $this->counter + $i;
$vars->totalFiles++;
$this->processedFiles = $counter - $vars->totalFolder - count($this->scanlist);
printProgress($this->processedFiles, $filepath, $vars);
$this->scanFile($filepath, $rescan, $counter, $vars);
$i++;
}
if ($rescan == self::RESCAN_ALL) {
$this->cas_db->delete();
$this->cas_list = [];
foreach ($this->vars->blackFiles as $blackfile) {
$this->cas_list[$blackfile['ras_sigid']] = $blackfile['sn'];
}
$this->cas_db->putList($this->cas_list);
}
//let's flush db again
$this->db->flushBatch();
//process whatever is left in our scan list
if (count($this->scanlist) > 0) {
$this->processScanList($vars);
}
// whitelist
$snum = 0;
$list = $this->scanner->check_whitelist($vars->structure['crc'], $snum);
$keys = [
'criticalPHP',
'criticalJS',
'phishing',
'adwareList',
'warningPHP'
];
foreach ($keys as $p) {
if (empty($vars->{$p})) {
continue;
}
$p_Fragment = $p . 'Fragment';
$p_Sig = $p . 'Sig';
if ($p == 'phishing') {
$p_Sig = $p . 'SigFragment';
}
$count = count($vars->{$p});
for ($i = 0; $i < $count; $i++) {
$id = $vars->{$p}[$i];
if ($vars->structure['crc'][$id] !== 0 && in_array($vars->structure['crc'][$id], $list)) {
$rec = RapidScanStorageRecord::fromFile($vars->structure['n'][$id]);
$rec->calcSha2();
$rec->setVerdict(RapidScanStorageRecord::RX_GOOD);
$this->db->put($rec);
unset($vars->{$p}[$i], $vars->{$p_Sig}[$i], $vars->{$p_Fragment}[$i]);
}
}
$vars->{$p} = array_values($vars->{$p});
$vars->{$p_Fragment} = array_values($vars->{$p_Fragment});
if (!empty($vars->{$p_Sig})) {
$vars->{$p_Sig} = array_values($vars->{$p_Sig});
}
}
//close databases and rename new into 'current'
$this->db->finish();
}
}
/**
* DbFolderSpecification class file.
*/
/**
* Class DbFolderSpecification.
*
* It can be use for checking requirements for a folder that is used for storing a RapidScan DB.
*/
class DbFolderSpecification
{
/**
* Check whether a particular folder satisfies requirements.
*
* @param string $folder
* @return bool
*/
public function satisfiedBy($folder)
{
if (!file_exists($folder) || !is_dir($folder)) {
return false;
}
$owner_id = (int)fileowner($folder);
if (function_exists('posix_getpwuid')) {
$owner = posix_getpwuid($owner_id);
if (!isset($owner['name']) || $owner['name'] !== 'root') {
return false;
}
}
elseif ($owner_id != 0) {
return false;
}
$perms = fileperms($folder);
if (($perms & 0x0100) // owner r
&& ($perms & 0x0080) // owner w
&& ($perms & 0x0040) && !($perms & 0x0800) // owner x
&& !($perms & 0x0020) // group without r
&& !($perms & 0x0010) // group without w
&& (!($perms & 0x0008) || ($perms & 0x0400))// group without x
&& !($perms & 0x0004) // other without r
&& !($perms & 0x0002) // other without w
&& (!($perms & 0x0001) || ($perms & 0x0200))// other without x
) {
return true;
}
return false;
}
}
/**
* CriticalFileSpecification class file.
*/
/**
* Class CriticalFileSpecification.
*/
class CriticalFileSpecification
{
/**
* @var array list of extension
*/
private $extensions = [
'php',
'htaccess',
'cgi',
'pl',
'o',
'so',
'py',
'sh',
'phtml',
'php3',
'php4',
'php5',
'php6',
'php7',
'pht',
'shtml',
'susp',
'suspected',
'infected',
'vir',
'ico',
'js',
'json',
'com',
''
];
private $js_extensions = [
'js',
'json',
'html',
'htm',
'suspicious'
];
private $phish_extensions = [
'js',
'html',
'htm',
'suspected',
'php',
'phtml',
'pht',
'php7'
];
private $critical_content = '^\s*<\?php'
. '|^\s*<\?='
. '|^#!/usr'
. '|^#!/bin'
. '|\beval'
. '|\briny'
. '|assert'
. '|base64_decode'
. '|\bsystem'
. '|create_function'
. '|\bexec'
. '|\bpopen'
. '|\bfwrite'
. '|\bfputs'
. '|file_get_'
. '|call_user_func'
. '|file_put_'
. '|\$_REQUEST'
. '|ob_start'
. '|\$_GET'
. '|\$_POST'
. '|\$_SERVER'
. '|\$_FILES'
. '|\bmove'
. '|\bcopy'
. '|\barray_'
. '|reg_replace'
. '|\bmysql_'
. '|\bchr'
. '|fsockopen'
. '|\$GLOBALS'
. '|sqliteCreateFunction'
. '|EICAR-STANDARD-ANTIVIRUS-TEST-FILE'
. '|RewriteCond';
private $critical_js_content = '