Current Directory: /home/astoriaah/www/plugins/system/admintools/feature
Viewing File: /home/astoriaah/www/plugins/system/admintools/feature/criticalfiles.php
<?php
/**
* @package admintools
* @copyright Copyright (c)2010-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
use Joomla\CMS\Factory;
use Joomla\CMS\Filesystem\Folder;
defined('_JEXEC') || die;
class AtsystemFeatureCriticalfiles extends AtsystemFeatureAbstract
{
protected $loadOrder = 999;
/**
* Is this feature enabled?
*
* @return bool
*/
public function isEnabled()
{
return ($this->cparams->getValue('criticalfiles', 0) == 1);
}
public function onAfterRender()
{
$mustSaveData = false;
$criticalFiles = $this->getCriticalFiles();
$loadedFiles = $this->load();
$alteredFiles = [];
$filesToSave = [];
foreach ($criticalFiles as $relPath)
{
$curInfo = $this->getFileInfo($relPath);
if ($curInfo == false)
{
// Did that file exist? If so, we need to save the critical files list.
if (is_array($loadedFiles) && array_key_exists($relPath, $loadedFiles))
{
$mustSaveData = true;
}
continue;
}
$filesToSave[$relPath] = $curInfo;
// Did the file change?
$oldInfo = null;
if (isset($loadedFiles[$relPath]))
{
$oldInfo = $loadedFiles[$relPath];
}
if ($oldInfo !== $curInfo)
{
$mustSaveData = true;
$alteredFiles[$relPath] = [$oldInfo, $curInfo];
}
}
if ($mustSaveData)
{
$this->save($filesToSave);
}
// Send out the email only if we have some altered files AND we have some previous data (otherwise we will scary
// the user as soon as they enable the feature)
if (!empty($alteredFiles) && $loadedFiles)
{
$this->sendEmail($alteredFiles);
}
}
/**
* Get the critical files, i.e. the files which get most commonly hacked in Joomla: configuration.php, the index.php
* files in front- and backend and the index.php, error.php and component.php files of the installed templates.
*
* @return array The list of critical files (relative paths)
*/
protected function getCriticalFiles()
{
$criticalFiles = [
'configuration.php',
'index.php',
'administrator/index.php',
];
$templateFiles = ['index.php', 'error.php', 'component.php'];
$templates = Folder::folders(JPATH_SITE . '/templates');
if (is_array($templates) && !empty($templates))
{
foreach ($templates as $template)
{
foreach ($templateFiles as $templateFile)
{
$relPath = 'templates/' . $template . '/' . $templateFile;
if (file_exists(JPATH_SITE . '/' . $relPath))
{
$criticalFiles[] = $relPath;
}
}
}
}
$templates = Folder::folders(JPATH_ADMINISTRATOR . '/templates');
if (is_array($templates) && !empty($templates))
{
foreach ($templates as $template)
{
foreach ($templateFiles as $templateFile)
{
$relPath = 'templates/' . $template . '/' . $templateFile;
if (file_exists(JPATH_ADMINISTRATOR . '/' . $relPath))
{
$criticalFiles[] = $relPath;
}
}
}
}
return $criticalFiles;
}
/**
* Returns information about a file
*
* @param string $relPath The path to the file relative to the site's root
*
* @return null|array Null if the file is not there, object with information otherwise
*/
protected function getFileInfo($relPath)
{
$absolutePath = JPATH_SITE . '/' . $relPath;
if (!file_exists($absolutePath))
{
return null;
}
return [
'size' => @filesize($absolutePath),
'timestamp' => filemtime($absolutePath),
'md5' => @md5_file($absolutePath),
'sha1' => @sha1_file($absolutePath),
];
}
/**
* Save the critical file information to the database
*
* @param array $fileList The list of critical file information
*
* @return void
*/
protected function save(array $fileList)
{
$db = $this->container->db;
$data = json_encode($fileList);
$query = $db->getQuery(true)
->delete($db->quoteName('#__admintools_storage'))
->where($db->quoteName('key') . ' = ' . $db->quote('criticalfiles'));
$db->setQuery($query);
$db->execute();
$object = (object) [
'key' => 'criticalfiles',
'value' => $data,
];
$db->insertObject('#__admintools_storage', $object);
}
/**
* Load the critical file information from the database
*
* @return array
*/
protected function load()
{
$db = $this->container->db;
$query = $db->getQuery(true)
->select($db->quoteName('value'))
->from($db->quoteName('#__admintools_storage'))
->where($db->quoteName('key') . ' = ' . $db->quote('criticalfiles'));
$db->setQuery($query);
$error = 0;
try
{
$jsonData = $db->loadResult();
}
catch (Exception $e)
{
$error = $e->getCode();
}
if (method_exists($db, 'getErrorNum') && $db->getErrorNum())
{
$error = $db->getErrorNum();
}
if ($error)
{
$jsonData = null;
}
if (empty($jsonData))
{
return [];
}
return json_decode($jsonData, true);
}
/**
* Sends a warning email to the addresses set up to receive security exception emails
*
* @param array $alteredFiles The files which were modified
*
* @return void
*/
private function sendEmail($alteredFiles)
{
if (empty($alteredFiles))
{
// What are you doing here? There are no altered files.
return;
}
// Load the component's administrator translation files
$jlang = Factory::getLanguage();
$jlang->load('com_admintools', JPATH_ADMINISTRATOR, 'en-GB', true);
$jlang->load('com_admintools', JPATH_ADMINISTRATOR, $jlang->getDefault(), true);
$jlang->load('com_admintools', JPATH_ADMINISTRATOR, null, true);
// Convert the list of modified files to HTML
$htmlAlteredFiles = <<< HTML
<ul>
HTML;
foreach ($alteredFiles as $fileName => $fileSet)
{
if (empty($fileName))
{
continue;
}
$htmlAlteredFiles .= <<< HTML
<li>
$fileName
</li>
HTML;
}
$htmlAlteredFiles .= <<< HTML
</ul>
HTML;
// Construct the replacement table
$substitutions = $this->exceptionsHandler->getEmailVariables('', [
'[INFO]' => $htmlAlteredFiles,
]);
// Let's get the most suitable email template
$template = $this->exceptionsHandler->getEmailTemplate('criticalfiles', true);
// Got no template, the user didn't published any email template, or the template doesn't want us to
// send a notification email. Anyway, let's stop here.
if (!$template)
{
return;
}
$subject = $template[0];
$body = $template[1];
foreach ($substitutions as $k => $v)
{
$subject = str_replace($k, $v, $subject);
$body = str_replace($k, $v, $body);
}
try
{
$config = $this->container->platform->getConfig();
$mailer = Factory::getMailer();
$mailfrom = $config->get('mailfrom');
$fromname = $config->get('fromname');
$recipients = explode(',', $this->cparams->getValue('emailbreaches', ''));
$recipients = array_map('trim', $recipients);
foreach ($recipients as $recipient)
{
if (empty($recipient))
{
continue;
}
// This line is required because SpamAssassin is BROKEN
$mailer->Priority = 3;
$mailer->isHtml(true);
$mailer->setSender([$mailfrom, $fromname]);
// Resets the recipients, otherwise they will pile up
$mailer->clearAllRecipients();
if ($mailer->addRecipient($recipient) === false)
{
// Failed to add a recipient?
continue;
}
$mailer->setSubject($subject);
$mailer->setBody($body);
$mailer->Send();
}
}
catch (Exception $e)
{
// Joomla! 3.5 and later throw an exception when crap happens instead of suppressing it and returning false
}
}
}