| Current Path : /home/poneycluc/www/plugins/system/bfnetwork/bfnetwork/ |
| Current File : /home/poneycluc/www/plugins/system/bfnetwork/bfnetwork/bfTools.php |
<?php
/*
* @package bfNetwork
* @copyright Copyright (C) 2011,2012,2013,2014,2015,2016,2017,2018,2019,2020,2021,2022,2023,2024,2025,2026 Blue Flame Digital Solutions Ltd. All rights reserved.
* @license GNU General Public License version 3 or later
*
* @see https://mySites.guru/
* @see https://www.phil-taylor.com/
*
* @author Phil Taylor / Blue Flame Digital Solutions Limited.
*
* bfNetwork is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* bfNetwork is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this package. If not, see http://www.gnu.org/licenses/
*
* If you have any questions regarding this code, please contact phil@phil-taylor.com
*/
use Akeeba\AdminTools\Admin\Model\AdminPassword;
use Akeeba\Engine\Platform;
use FOF30\Container\Container;
use Joomla\CMS\Component\ComponentHelper;
use Joomla\CMS\Factory;
use Joomla\CMS\Language\Text;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\CMS\Schema\ChangeSet;
use Joomla\CMS\Table\Table;
use Joomla\CMS\Uri\Uri;
use Joomla\CMS\User\User;
use Joomla\CMS\User\UserFactoryInterface;
use Joomla\CMS\User\UserHelper;
use Joomla\Component\Actionlogs\Administrator\Helper\ActionlogsHelper;
use Joomla\Component\Actionlogs\Administrator\Model\ActionlogsModel;
use Joomla\Component\Installer\Administrator\Model\DatabaseModel;
use Joomla\Database\Exception\PrepareStatementFailureException;
require 'bfEncrypt.php';
/**
* If we have got here then we have already passed through decrypting the encrypted header and so we are sure we are now
* secure and no one else cannot run the code below.
*
* I'M NOT PROUD OF THIS FILE - it has grown a lot over the years and there are a lot of workarounds so that we can be
* fully compatible from Joomla 1.5.0 to the latest Joomla version, and on all the crazy configurations of webservers.
*/
final class bfTools
{
/**
* We pass the command to run as a simple integer in our encrypted request this is mainly to speed up the decryption
* process, plus its a single digit(or 2) rather than a huge string to remember :-).
*/
private $_methods = [
1 => 'getCoreHashFailedFileList',
2 => 'downloadfile',
3 => 'restorefile',
4 => 'getSuspectContentFileList',
5 => 'deleteFile',
6 => 'checkFTPLayer',
7 => 'disableFTPLayer',
8 => 'checkNewDBCredentials',
9 => 'testDbCredentials',
10 => 'getFolderPermissions',
11 => 'setFolderPermissions',
12 => 'getHiddenFolders',
13 => 'deleteFolder',
14 => 'getInstallationFolders',
15 => 'getRecentlyModified',
16 => 'getFilePermissions',
17 => 'setFilePermissions',
18 => 'getErrorLogs',
19 => 'getEncrypted',
20 => 'getUser',
21 => 'setUser',
22 => 'setDbPrefix',
23 => 'setDbCredentials',
24 => 'getBakTables',
25 => 'deleteBakTables',
26 => 'getHtaccessFiles',
27 => 'setHtaccess',
28 => 'getUpdatesCount',
29 => 'getUpdatesDetail',
30 => 'getDotfiles',
31 => 'getArchivefiles',
32 => 'getLargefiles',
33 => 'fixDbSchema',
34 => 'getDbSchemaVersion',
35 => 'checkGoogleFile',
36 => 'toggleOnline',
37 => 'getOfflineStatus',
38 => 'getRobotsFile',
39 => 'saveRobotsFile',
40 => 'getTmpfiles',
41 => 'clearTmpFiles',
42 => 'getFlufffiles',
43 => 'clearFlufffiles',
44 => 'getRenamedToHide',
45 => 'getPhpinwrongplace',
46 => 'doExtensionUpgrade',
47 => 'toggleCache',
48 => 'getCacheStatus',
49 => 'checkAkeebaOutputDirectory',
50 => '', // @deprecated function "eolsecuritystatus" Not Needed in Joomla 4
51 => '', // @deprecated function "applyeolpatch" Not Needed in Joomla 4
52 => 'getMailerFileList',
53 => 'getUploaderFileList',
54 => 'getNonCoreFileList',
55 => 'saveFile',
56 => 'getZerobyteFiles',
57 => 'deleteZerobyteFiles',
58 => 'getMissingCoreFiles',
59 => 'restoreAllMissingFiles',
60 => 'getJoomlaLogTmpConfig',
61 => 'getActivityLog',
62 => 'getBFPluginStatus',
63 => 'getMD5PasswordUsers',
64 => 'getSessionGCStatus',
65 => 'setSessionGCStatus',
66 => 'get2FAPlugins',
67 => 'enable2FAPlugins',
68 => 'setLogTmpPaths',
69 => 'removeLiveSite',
70 => 'getConfiguredLiveSite',
71 => 'getSEFConfig',
72 => 'setSEFConfig',
73 => 'getAdminFilterFixed',
74 => 'setAdminFilterFixed',
75 => 'getPlaintextpasswords',
76 => 'setPlaintextpasswords',
77 => 'getUploadsettingsfixed',
78 => 'setUploadsettingsfixed',
79 => 'getMailtofrienddisabled',
80 => 'setMailtofrienddisabled',
81 => 'getDebugMode',
82 => 'setDebugMode',
83 => 'getErrorReporting',
84 => 'setErrorReporting',
85 => 'getTemplatePositionDisplay',
86 => 'setTemplatePositionDisplay',
87 => 'getCookieSettings',
88 => 'setCookieSettings',
89 => 'getSQLFiles',
90 => 'getCaptchaConfig',
91 => 'setCaptchaConfig',
92 => 'doExtensionInstallFromUrl',
93 => 'getSuperAdmins',
94 => 'getGroups',
95 => 'getUseractionlogenabled',
96 => 'setUseractionlogenabled',
97 => 'getPrivacyConsentPluginEnabled',
98 => 'setPrivacyConsentPluginEnabled',
99 => 'getUseractionlogiplogenabled',
100 => 'setUseractionlogiplogenabled',
101 => 'getSystemLogRotationEnabled',
102 => 'setSystemLogRotationEnabled',
103 => 'getPurge30Days',
104 => 'setPurge30Days',
105 => 'getGzip',
106 => 'setGzip',
107 => 'getSessionlifetime',
108 => 'setSessionlifetime',
109 => 'getPHPinifiles',
110 => 'getModifiedfilessincelastaudit',
111 => 'setAdminHtaccess',
112 => 'getAdminHtaccess',
113 => 'getUserRegistration',
114 => 'setUserRegistration',
115 => 'getPostInstallMessages',
116 => 'getUserFilterFixed',
117 => 'setUserFilterFixed',
118 => 'getHasrootuser',
119 => 'setHasrootuser',
120 => 'getDebuglanguage',
121 => 'setDebuglanguage',
122 => 'setPostInstallMessages',
123 => 'getSkipped',
124 => 'getSendcopytosubmitter',
125 => 'setSendcopytosubmitter',
126 => 'getGuidedTours',
127 => 'setGuidedTours',
128 => 'getFileinfoModuleEnabled',
129 => 'getThumbnails',
130 => 'setThumbnails',
131 => 'getNeedsCompact',
132 => 'getDefaultUpdateChannel',
133 => 'setDefaultUpdateChannel',
136 => 'getSendUpdateEmails',
137 => 'setSendUpdateEmails',
138 => 'getLogEverything',
158 => 'getLockedTasks',
159 => 'unlockLockedTask',
139 => 'setLogEverything',
140 => 'getLogDeprecated',
141 => 'setLogDeprecated',
144 => 'getInactiveUsers',
145 => 'blockUser',
146 => 'deleteUser',
147 => 'blockAllUsers',
148 => 'deleteAllUsers',
149 => 'getUnactivatedUsers',
150 => 'activateUser',
151 => 'deleteUnactivatedUser',
152 => 'activateAllUsers',
153 => 'deleteAllUnactivatedUsers',
154 => 'resendActivation',
155 => 'getBlockedUsers',
156 => 'unblockUser',
157 => 'unblockAllUsers',
223 => 'getJceRogueDetection',
224 => 'removeJceRogueProfiles',
225 => 'removeJceMaliciousFiles',
226 => 'getRogueAdmins',
227 => 'removeRogueAdmins',
228 => 'getAdmintoolsIpStatus',
229 => 'unblockAdmintoolsIp',
239 => 'getHackedFileList',
998 => 'setRealtimeActivate',
999 => 'getDebugLog',
];
private $fluffFiles = [
'/.drone.yml',
'/robots.txt.dist',
'/web.config.txt',
'/joomla.xml',
'/build.xml',
'/LICENSE.txt',
'/README.txt',
'/htaccess.txt',
'/LICENSES.php',
'/configuration.php-dist',
'/CHANGELOG.php',
'/COPYRIGHT.php',
'/CREDITS.php',
'/INSTALL.php',
'/LICENSE.php',
'/CONTRIBUTING.md',
'/phpunit.xml.dist',
'/README.md',
'/.travis.yml',
'/travisci-phpunit.xml',
'/images/banners/osmbanner1.png',
'/images/banners/osmbanner2.png',
'/images/banners/shop-ad-books.jpg',
'/images/banners/shop-ad.jpg',
'/images/banners/white.png',
'/images/headers/blue-flower.jpg',
'/images/headers/maple.jpg',
'/images/headers/raindrops.jpg',
'/images/headers/walden-pond.jpg',
'/images/headers/windows.jpg',
'/images/joomla_black.gif',
'/images/joomla_black.png',
'/images/joomla_green.gif',
'/images/joomla_logo_black.jpg',
'/images/powered_by.png',
'/images/sampledata/fruitshop/apple.jpg',
'/images/sampledata/fruitshop/bananas_2.jpg',
'/images/sampledata/fruitshop/fruits.gif',
'/images/sampledata/fruitshop/tamarind.jpg',
'/images/sampledata/parks/animals/180px_koala_ag1.jpg',
'/images/sampledata/parks/animals/180px_wobbegong.jpg',
'/images/sampledata/parks/animals/200px_phyllopteryx_taeniolatus1.jpg',
'/images/sampledata/parks/animals/220px_spottedquoll_2005_seanmcclean.jpg',
'/images/sampledata/parks/animals/789px_spottedquoll_2005_seanmcclean.jpg',
'/images/sampledata/parks/animals/800px_koala_ag1.jpg',
'/images/sampledata/parks/animals/800px_phyllopteryx_taeniolatus1.jpg',
'/images/sampledata/parks/animals/800px_wobbegong.jpg',
'/images/sampledata/parks/banner_cradle.jpg',
'/images/sampledata/parks/landscape/120px_pinnacles_western_australia.jpg',
'/images/sampledata/parks/landscape/120px_rainforest_bluemountainsnsw.jpg',
'/images/sampledata/parks/landscape/180px_ormiston_pound.jpg',
'/images/sampledata/parks/landscape/250px_cradle_mountain_seen_from_barn_bluff.jpg',
'/images/sampledata/parks/landscape/727px_rainforest_bluemountainsnsw.jpg',
'/images/sampledata/parks/landscape/800px_cradle_mountain_seen_from_barn_bluff.jpg',
'/images/sampledata/parks/landscape/800px_ormiston_pound.jpg',
'/images/sampledata/parks/landscape/800px_pinnacles_western_australia.jpg',
'/images/sampledata/parks/parks.gif',
];
/**
* Pointer to the Joomla Database Object.
*
* @var \Joomla\Database\Mysqli\MysqliDriver
*/
private $_db;
private $_container;
private $_app;
/**
* Incoming decrypted vars from the request.
*
* @var stdClass
*/
private $_dataObj;
/**
* I inject the request to the object.
*
* @param stdClass $dataObj
*/
public function __construct($dataObj)
{
// init Joomla
if (! defined('BF_JOOMLA_INIT_DONE')) {
require_once 'bfInitJoomla.php';
}
// Set the request vars
$this->_dataObj = $dataObj;
// set the db object
$this->_container = Factory::getContainer();
$this->_app = Factory::getApplication();
$this->_db = $this->_container->get('DatabaseDriver');
}
/**
* I'm the controller - I run methods based on the request integer.
*/
public function run()
{
if (property_exists($this->_dataObj, 'c')) {
$c = (int) $this->_dataObj->c;
if (array_key_exists($c, $this->_methods)) {
bfLog::log('Calling method ' . $this->_methods[$c]);
// call the right method
$this->{$this->_methods[$c]} ();
} else {
// Die if an unknown function
bfEncrypt::reply('error', 'No Such method #err1 - ' . $c);
}
} else {
// Die if an unknown function
bfEncrypt::reply('error', 'No Such method #err2');
}
}
public function getDebugLog()
{
bfEncrypt::reply('success', [
'data' => bfLog::getLog(),
]);
}
public function setThumbnails()
{
$this->_db->setQuery("SELECT params FROM `#__extensions` WHERE `name` = 'plg_filesystem_local'");
$params = $this->_db->LoadResult();
if ('{}' == $params || null == $params || '' == $params) {
$params = '{"directories":{"directories0":{"directory":"images","thumbs":1}}}';
}
$params = json_decode($params, JSON_OBJECT_AS_ARRAY);
if (! array_key_exists('directories', $params)) {
$params = json_decode('{"directories":{"directories0":{"directory":"images","thumbs":1}}}', JSON_OBJECT_AS_ARRAY);
}
foreach ($params['directories'] as $k => $directory) {
$params['directories'][$k]['thumbs'] = ($this->_dataObj->s == 'true' ? 1 : 0);
}
$sql = sprintf("UPDATE `#__extensions` SET `params` = '%s' WHERE `name` = 'plg_filesystem_local'", json_encode($params));
$this->_db->setQuery($sql);
$this->_db->execute();
bfEncrypt::reply('success', [
'sql' => $sql,
]);
return $this->getThumbnails();
}
public function getThumbnails()
{
$this->_db->setQuery("SELECT params FROM `#__extensions` WHERE `name` = 'plg_filesystem_local'");
$params = $this->_db->LoadResult();
if ('{}' == $params || null == $params || '' == $params) {
return;
}
$params = json_decode($params, JSON_OBJECT_AS_ARRAY);
if (! array_key_exists('directories', $params)) {
return;
}
$thumbsCount = 0;
foreach ($params['directories'] as $directory) {
if (! array_key_exists('thumbs', $directory)) {
continue;
}
if ($directory['thumbs'] == 1) {
$thumbsCount++;
}
}
bfEncrypt::reply('success', [
'enabled' => (int) $thumbsCount === \count($params['directories']),
]);
}
public function setSendcopytosubmitter()
{
// saving params to database
$component = ComponentHelper::getComponent('com_contact');
$params = $component->getParams();
$params->set('show_email_copy', 'true' == $this->_dataObj->s ? 0 : 1);
$table = Table::getInstance('extension');
$table->load($component->id);
$table->bind([
'params' => $params->toString(),
]);
$table->store();
bfEncrypt::reply('success', $this->getSendcopytosubmitter());
}
public function getSendcopytosubmitter()
{
$params = ComponentHelper::getComponent('com_contact')->getParams();
return (int) $params->get('show_email_copy', 0);
}
public function setPostInstallMessages()
{
$db = Factory::getDbo();
$query = $db->getQuery(true)
->update($db->quoteName('#__postinstall_messages'))
->set($db->quoteName('enabled') . ' = 0');
$db->setQuery($query);
$db->execute();
bfEncrypt::reply('success', $this->getPostInstallMessages());
}
public function getPostInstallMessages()
{
require_once 'bfInitJoomla.php';
require_once JPATH_BASE . '/administrator/components/com_postinstall/src/Helper/PostinstallHelper.php';
require_once JPATH_BASE . '/administrator/components/com_postinstall/src/Model/MessagesModel.php';
$model = Factory::getApplication()
->bootComponent('com_postinstall')
->getMVCFactory()
->createModel('Messages', 'Administrator', [
'ignore_request' => true,
]);
$messages = $model->getItems();
$data = [];
foreach ($messages as $k => $item) {
$data[] = [
'title' => Text::_($item->title_key),
'desc' => Text::_($item->description_key),
];
}
bfEncrypt::reply('success', $data);
}
/**
* 114 Enable User Registration.
*/
public function setUserRegistration()
{
$this->_db->setQuery("SELECT params FROM `#__extensions` WHERE `name` = 'com_users'");
$params = json_decode($this->_db->LoadResult());
if ('true' == $this->_dataObj->s) {// true means, set to the OK value
// enabled
$params->allowUserRegistration = 0;
} else {
// disabled
$params->allowUserRegistration = 1;
}
$this->_db->setQuery("UPDATE `#__extensions` set params = '" . json_encode($params) . "' WHERE `name` = 'com_users'");
$this->_db->execute();
return $this->getUserRegistration();
}
/**
* 113 Get User Registration Enable/Disable status.
*/
public function getUserRegistration()
{
bfEncrypt::reply('success', [
'enabled' => (int) ComponentHelper::getParams('com_users')->get('allowUserRegistration'),
]);
}
/**
* 111 Enable /administrator/.htaccess restriction on apache.
*/
public function setAdminHtaccess()
{
require 'lib/AdminTools/Model/AdminPassword/AdminPassword.php';
$p = new AdminPassword();
$p->username = $this->_dataObj->u;
$p->password = $this->_dataObj->p;
if (! $p->protect()) {
bfEncrypt::reply('error', 'Could not enable administrator .htaccess for some unknown reason :-( ');
}
bfEncrypt::reply('success', [
'enabled' => 1,
'username' => $this->_dataObj->u,
'password' => $this->_dataObj->p,
]);
}
/**
* 112 Enable /administrator/.htaccess restriction on apache.
*/
public function getAdminHtaccess()
{
require 'lib/AdminTools/Model/AdminPassword/AdminPassword.php';
$obj = new AdminPassword();
bfEncrypt::reply('success', [
'enabled' => $obj->isLocked(),
]);
}
/**
* Get the value of $gzip from /configuration.php.
*/
public function getGzip()
{
bfEncrypt::reply('success', [
'enabled' => (int) Factory::getApplication()->getConfig()->get('gzip', '0'),
]);
}
/**
* set the value of $gzip in /configuration.php.
*/
public function setGzip()
{
if ('true' == $this->_dataObj->s) {// true means, set to the OK value
return $this->_setConfigParam('gzip', true, 'bool');
} else {
return $this->_setConfigParam('gzip', false, 'bool');
}
}
/**
* Generic function for updating the configuration.php file.
*
* @param string $param
* @param string|int $value
*/
private function _setConfigParam($param, $value, $type = 'int', $return = false)
{
// Require more complex methods for dealing with files
require_once 'bfFilesystem.php';
if ('int' == $type && ! is_int($value)) {
if ('true' == $value) {
$value = 1;
} elseif ('false' == $value) {
$value = 0;
} else {
$value = 0;
}
}
$config = Factory::getApplication()->getConfig();
$config->set($param, $value);
$newConfig = $config->toString('PHP', [
'class' => 'JConfig',
]);
/**
* On some occasions, Joomla! 1.6+ ignores the configuration and produces "class c". Let's fix this!
*/
$newConfig = str_replace('class c {', 'class JConfig {', $newConfig);
$newConfig = str_replace('namespace c;', '', $newConfig);
// Set the correct location of the file
$filename = JPATH_ROOT . \DIRECTORY_SEPARATOR . 'configuration.php';
// Try to write out the configuration.php
$result = Bf_Filesystem::_write($filename, $newConfig);
if (true === $return) {
return $result;
}
if (false !== $result) {
bfEncrypt::reply('success', [
$param => $value,
]);
} else {
bfEncrypt::reply(bfReply::ERROR, [
'msg' => 'Could Not Save Config value for ' . $param,
]);
}
}
public function getFileinfoModuleEnabled()
{
bfEncrypt::reply('success', [
'enabled' => \extension_loaded('fileinfo'),
]);
}
/**
* Get the config for session time.
*/
public function getSessionlifetime()
{
bfEncrypt::reply('success', [
'lifetime' => Factory::getApplication()->getConfig()->get('lifetime', 0),
]);
}
/**
* set the session time to a sensibel recommend default.
*/
public function setSessionlifetime()
{
$this->_setConfigParam('lifetime', 15, 'int');
}
/**
* Get the number of days to delete logs after from the System - User Actions Log.
*
* @return int
*/
public function setPurge30Days()
{
$this->_db->setQuery("SELECT params FROM `#__extensions` WHERE `name` = 'PLG_SYSTEM_ACTIONLOGS'");
$params = json_decode($this->_db->LoadResult());
// enabled
$params->logDeletePeriod = 30;
$this->_db->setQuery("UPDATE `#__extensions` set params = '" . json_encode($params) . "' WHERE `name` = 'PLG_SYSTEM_ACTIONLOGS'");
$this->_db->execute();
return $this->getPurge30Days();
}
/**
* Get the number of days to delete logs after from the System - User Actions Log.
*
* @return int
*/
public function getPurge30Days()
{
if (version_compare(JVERSION, '3.9.0', '<')) {
return false;
}
$this->_db->setQuery("SELECT params FROM `#__extensions` WHERE `name` = 'PLG_SYSTEM_ACTIONLOGS'");
$params = $this->_db->LoadResult();
if ('{}' == $params) {
bfEncrypt::reply('success', [
'days' => null,
]);
}
$params = json_decode($params);
if (! $params) {
return;
}
bfEncrypt::reply('success', [
'days' => $params->logDeletePeriod,
]);
}
/**
* Joomla 3.9.0+ enable system log rotation.
*/
public function setSystemLogRotationEnabled()
{
$this->_db->setQuery("UPDATE `#__extensions` set enabled = 1 WHERE `name` = 'plg_system_logrotation'");
$this->_db->execute();
return $this->getSystemLogRotationEnabled();
}
/**
* Joomla 3.9.0+ check for system log rotation.
*/
public function getSystemLogRotationEnabled()
{
$this->_db->setQuery("SELECT count(*) FROM `#__extensions` WHERE `name` = 'plg_system_logrotation' and enabled = 1");
bfEncrypt::reply('success', [
'enabled' => $this->_db->LoadResult(),
]);
}
public function setGuidedTours()
{
// inverse.
if ("true" === $this->_dataObj->s) {
$enabled = 0;
} else {
$enabled = 1;
}
$sql = "UPDATE `#__extensions` set enabled = " . $enabled . " WHERE `name` = 'plg_system_guidedtours'";
$this->_db->setQuery($sql);
$this->_db->execute();
bfEncrypt::reply('success', [
'enabled' => $this->getGuidedTours(),
]);
}
public function getGuidedTours()
{
$this->_db->setQuery("SELECT count(*) FROM `#__extensions` WHERE `name` = 'plg_system_guidedtours' and enabled = 1");
bfEncrypt::reply('success', [
'enabled' => $this->_db->LoadResult(),
]);
}
/**
* Joomla 3.9.0+ enable IP logging in user action logging.
*/
public function setUseractionlogiplogenabled()
{
$this->_db->setQuery("SELECT params FROM `#__extensions` WHERE `name` = 'com_actionlogs'");
$params = json_decode($this->_db->LoadResult());
// enabled
$params->ip_logging = 1;
$this->_db->setQuery("UPDATE `#__extensions` set params = '" . json_encode($params) . "' WHERE `name` = 'com_actionlogs'");
$this->_db->execute();
return $this->getUseractionlogiplogenabled();
}
/**
* Joomla 3.9.0+ Check for plg_privacy_actionlogs enabled.
*/
public function getUseractionlogiplogenabled()
{
$this->_db->setQuery("SELECT params FROM `#__extensions` WHERE `name` = 'com_actionlogs'");
$params = json_decode($this->_db->LoadResult());
bfEncrypt::reply('success', [
'enabled' => $params->ip_logging,
]);
}
/**
* Joomla 3.9.0+ Check for plg_privacy_actionlogs enabled.
*/
public function setUseractionlogenabled()
{
$this->_db->setQuery("UPDATE `#__extensions` set enabled = 1 WHERE `name` = 'PLG_ACTIONLOG_JOOMLA'");
$this->_db->execute();
$this->_db->setQuery("UPDATE `#__extensions` set enabled = 1 WHERE `name` = 'PLG_SYSTEM_ACTIONLOGS'");
$this->_db->execute();
return $this->getUseractionlogenabled();
}
/**
* Joomla 3.9.0+ Check for plg_privacy_actionlogs enabled.
*/
public function getUseractionlogenabled()
{
$this->_db->setQuery(
"SELECT count(*) FROM `#__extensions` WHERE (`name` = 'PLG_ACTIONLOG_JOOMLA' or `name` = 'PLG_SYSTEM_ACTIONLOGS') and enabled = 1"
);
bfEncrypt::reply('success', [
'enabled' => 2 == $this->_db->LoadResult() ? 1 : 0,
]);
}
/**
* Joomla 3.9.0+ Check for plg_system_privacyconsent enabled.
*/
public function setPrivacyConsentPluginEnabled()
{
$this->_db->setQuery("UPDATE `#__extensions` set enabled = 1 WHERE `name` = 'plg_system_privacyconsent'");
$this->_db->execute();
return $this->getUseractionlogenabled();
}
/**
* Joomla 3.9.0+ Check for plg_system_privacyconsent enabled.
*/
public function getPrivacyConsentPluginEnabled()
{
$this->_db->setQuery("SELECT count(*) FROM `#__extensions` WHERE `name` = 'plg_system_privacyconsent' and enabled = 1");
bfEncrypt::reply('success', [
'enabled' => $this->_db->LoadResult(),
]);
}
/**
* Load Flash Upload Settings from params from com_media without using a helper. and then remove swf and
* application/x-shockwave-flash.
*/
public function setUploadsettingsfixed()
{
$this->_db->setQuery("select params from #__extensions where element = 'com_media'");
$params = json_decode($this->_db->LoadResult());
$items = explode(',', $params->upload_extensions);
if ('true' == $this->_dataObj->s) {// true means, set to the OK value
foreach ($items as $k => $item) {
if ('swf' == strtolower(trim($item))) {
unset($items[$k]);
}
}
} else {
$items[] = 'swf';
}
$params->upload_extensions = implode(',', $items);
$items = explode(',', $params->upload_mime);
if ('true' == $this->_dataObj->s) {// true means, set to the OK value
foreach ($items as $k => $item) {
if ('application/x-shockwave-flash' == strtolower(trim($item))) {
unset($items[$k]);
}
}
} else {
$items[] = 'application/x-shockwave-flash';
}
$params->upload_mime = implode(',', $items);
$sql = sprintf("UPDATE #__extensions set `params` = '%s' WHERE `element` = 'com_media'", json_encode($params));
$this->_db->setQuery($sql);
$this->_db->execute();
$this->getUploadsettingsfixed();
}
/**
* Load Flash Upload Settings from params from com_media without using a helper.
*/
public function getUploadsettingsfixed()
{
$this->_db->setQuery("select params from #__extensions where element = 'com_media'");
$params = json_decode($this->_db->LoadResult());
if (
! preg_match('/swf/ism', $params->upload_extensions)
&&
! preg_match('/application\/x-shockwave-flash/ism', $params->upload_mime)
) {
bfEncrypt::reply('success', [
'uploadsettingsfixed' => 1,
]);
} else {
bfEncrypt::reply('success', [
'uploadsettingsfixed' => 0,
]);
}
}
public function setDefaultUpdateChannel()
{
$component = ComponentHelper::getComponent('com_joomlaupdate');
$params = $component->getParams();
$params->set('updatesource', 'true' === $this->_dataObj->s ? 'default' : 'next');
$table = Table::getInstance('extension');
$table->load($component->id);
$table->bind([
'params' => $params->toString(),
]);
$table->store();
$this->getDefaultUpdateChannel();
}
public function getDefaultUpdateChannel()
{
bfEncrypt::reply('success', ComponentHelper::getComponent('com_joomlaupdate') ->getParams() ->get('updatesource', 'default'));
}
/**
* @param bool $internal
*
* @return array|mixed
*/
private function getSkipped($internal = false)
{
$limitstart = (int) $this->_dataObj->ls;
$limit = (int) $this->_dataObj->limit;
if (! $limitstart) {
$limitstart = 0;
}
if (! $limit) {
$limit = '9999999999999999';
}
$sql = 'SELECT * FROM bf_files WHERE skipped = 1 ORDER BY filemtime DESC LIMIT ' . (int) $limitstart . ', ' . $limit;
$this->_db->setQuery($sql);
$files = $this->_db->LoadObjectList();
if (true === $internal) {
return $files;
}
$this->_db->setQuery('SELECT count(*) FROM bf_files WHERE skipped = 1');
$count = $this->_db->loadResult();
bfEncrypt::reply('success', [
'files' => $files,
'total' => $count,
]);
}
/**
* 109 Gets php.ini and .user.ini files.
*/
private function getPHPinifiles()
{
// make sure we only retrieve a small dataset
$limitstart = (int) $this->_dataObj->ls;
$sort = $this->_dataObj->s;
if (! $sort) {
$sort = 'filewithpath';
}
if (! in_array($sort, ['filewithpath', 'filemtime'])) {
exit('Invalid Sort');
}
if ('filemtime' == $sort) {
$sort = 'filemtime DESC';
}
$limit = (int) $this->_dataObj->limit;
// Set the query
$this->_db->setQuery('SELECT id, iscorefile, filewithpath, filemtime, fileperms, `size`, iscorefile from bf_files
WHERE filewithpath LIKE "%php.ini%" OR filewithpath LIKE "%.user.ini%"
ORDER BY ' . $sort . '
LIMIT ' . (int) $limitstart . ', ' . $limit);
// Get an object list of files
$files = $this->_db->loadObjectList();
// see how many files there are in total without a limit
$this->_db->setQuery('SELECT count(*) from bf_files WHERE filewithpath LIKE "%php.ini%" OR filewithpath LIKE "%.user.ini%"');
$count = $this->_db->loadResult();
// Only show files that still exist on the hard drive
$existingFiles = [];
foreach ($files as $k => $file) {
if (file_exists(JPATH_BASE . $file->filewithpath)) {
$existingFiles[] = $file;
} else {
$this->_db->setQuery(sprintf('DELETE FROM bf_files WHERE filewithpath = "%s"', $file->filewithpath));
$this->_db->execute();
--$count;
}
}
// return an encrypted reply
bfEncrypt::reply('success', [
'files' => $existingFiles,
'total' => $count,
]);
}
/**
* Method to delete a named file when we know its id.
*/
private function deleteFile()
{
// Get the filewithpath based on the id
$this->_db->setQuery('SELECT filewithpath from bf_files WHERE id = ' . (int) $this->_dataObj->file_id);
$filewithpath = $this->_db->loadResult();
// check that the file we got form the database matches to the path we think it should be
if ($this->_dataObj->filewithpath != $filewithpath) {
bfEncrypt::reply('failure', [
'msg' => 'File Not matching: ' . $this->_dataObj->filewithpath . ' !== ' . $filewithpath,
]);
}
// If the file doesnt exist then remove from cache and reply
if (! file_exists(JPATH_BASE . $filewithpath)) {
$this->_db->setQuery('DELETE FROM bf_files WHERE id = ' . (int) $this->_dataObj->file_id);
$this->_db->execute();
bfEncrypt::reply('failure', [
'msg' => 'File doesn\'t exist: ' . $filewithpath,
]);
}
// Attempt to force deletion
if (! is_writable(JPATH_BASE . $filewithpath)) {
@chmod(JPATH_BASE . $filewithpath, 0777);
}
// delete the file, making sure we prefix with a path
if (@unlink(JPATH_BASE . $filewithpath)) {
$this->_db->setQuery('DELETE FROM bf_files WHERE id = ' . (int) $this->_dataObj->file_id);
$this->_db->execute();
// File deleted - say yes
bfEncrypt::reply('success', [
'msg' => 'File deleted: ' . $filewithpath,
]);
} else {
// File deleted - say no
bfEncrypt::reply('failure', [
'msg' => 'File Not Deleted: ' . $filewithpath,
]);
}
}
/**
* I delete a folder.
*/
private function deleteFolder()
{
// Require more complex methods for dealing with files
require 'bfFilesystem.php';
// init our return msg
$msg = [];
// hidden or normal - needed for ALL deletes
$type = $this->_dataObj->type;
// switch on type
if ('hidden' == $type) {
// get the folders cache id
$folder_id = $this->_dataObj->fid;
// init
$msgToReturn = [];
$msgToReturn['deleted_files'] = 0;
$msgToReturn['deleted_folders'] = 0;
$msgToReturn['left'] = 0;
// Do we want to delete all hidden folders?
if ('ALL' == $folder_id) { // All meaning all hidden folders, not ALL folders in our db!!
$this->_dataObj->ls = 0;
$this->_dataObj->limit = 999999999;
// get all the hidden folders
$folders = $this->getHiddenFolders(true);
bfLog::log('Deleting this many folders : ' . count($folders));
// foreach hidden folder, delete that hidden folder recursivly
foreach ($folders as $folder) {
// delete recursive
bfLog::log('Deleting folder: ' . JPATH_BASE . $folder->folderwithpath);
$msg = Bf_Filesystem::deleteRecursive(JPATH_BASE . $folder->folderwithpath, true, $msg);
$this->_db->setQuery('DELETE FROM bf_folders WHERE folderwithpath LIKE "' . $folder->folderwithpath . '%"');
$this->_db->loadResult();
$this->_db->setQuery('DELETE FROM bf_files WHERE filewithpath LIKE "' . $folder->folderwithpath . '%"');
$this->_db->loadResult();
// oh dear we failed
if ('failure' == $msg['result']) {
$msgToReturn = [];
$msgToReturn['deleted_files'] = count(@$msg['deleted_files']);
$msgToReturn['deleted_folders'] = count(@$msg['deleted_folders']);
$msgToReturn['left'] = $this->getHiddenFolders(true);
// send back the error message
bfEncrypt::reply('failure', [
'msg' => 'Problem!: ' . json_encode($msgToReturn),
]);
}
}
} else {
// select the folder to delete
$this->_db->setQuery('SELECT folderwithpath FROM bf_folders WHERE id = ' . (int) $folder_id);
$folderwithpath = $this->_db->loadResult();
// if the folder is not there
if (! $folderwithpath) {
bfEncrypt::reply('failure', [
'msg' => 'Folder Not Found #msg2#: ' . $folderwithpath,
]);
}
$msg = Bf_Filesystem::deleteRecursive(JPATH_BASE . $folderwithpath, true, $msg);
}
// if we deleted some folders
if (count($msg['deleted_folders'])) {
foreach ($msg['deleted_folders'] as $folder) {
$fwp = str_replace('//', '/', str_replace(JPATH_BASE, '', $folder));
$sql = "DELETE FROM bf_folders where folderwithpath = '" . $fwp . "'";
$this->_db->setQuery($sql);
$this->_db->execute();
}
}
// if we deleted some files
if (count($msg['deleted_files'])) {
foreach ($msg['deleted_files'] as $file) {
$fwp = str_replace('//', '/', str_replace(JPATH_BASE, '', $file));
$sql = "DELETE FROM bf_files where filewithpath = '" . $fwp . "'";
$this->_db->setQuery($sql);
$this->_db->execute();
}
}
// reply back with our warning or success message
$msgToReturn = [];
$msgToReturn['deleted_files'] = count($msg['deleted_files']);
$msgToReturn['deleted_folders'] = count($msg['deleted_folders']);
$msgToReturn['left'] = count($this->getHiddenFolders(true));
bfEncrypt::reply('success', [
'msg' => json_encode($msgToReturn),
]);
}
if ('deleteinstallation' === $type) {
$folders = $this->getFolders(JPATH_BASE);
foreach ($folders as $folder) {
if (preg_match(
'/installation|installation.old|docs\/installation|install|installation.bak|installation.old|installation.backup|installation.delete/i',
$folder
)) {
$installationFolders[] = $folder;
}
}
foreach ($installationFolders as $folderwithpath) {
bfLog::log('Deleting folder: ' . $folderwithpath);
$msg = Bf_Filesystem::deleteRecursive(JPATH_BASE . $folderwithpath, true, $msg);
}
bfEncrypt::reply('success', [
'msg' => 'ok',
]);
}
}
/**
* @param bool $internal
*
* @return array|mixed
*/
private function getHiddenFolders($internal = false)
{
$limitstart = (int) $this->_dataObj->ls;
$limit = (int) $this->_dataObj->limit;
if (! $limitstart) {
$limitstart = 0;
}
if (! $limit) {
$limit = '9999999999999999';
}
$this->_db->setQuery('SELECT * FROM bf_folders WHERE folderwithpath LIKE "%/.%" LIMIT ' . (int) $limitstart . ', ' . $limit);
$folders = $this->_db->loadObjectList();
if (true === $internal) {
return $folders;
}
$this->_db->setQuery('SELECT count(*) FROM bf_folders WHERE folderwithpath LIKE "%/.%"');
$count = $this->_db->loadResult();
bfEncrypt::reply('success', [
'files' => $folders,
'total' => $count,
]);
}
/**
* Function taken from Akeeba filesystem.php.
*
* Akeeba Engine The modular PHP5 site backup engine
*
* @copyright Copyright (c)2009 Nicholas K. Dionysopoulos
* @license GNU GPL version 3 or, at your option, any later version
*
* @version Id: scanner.php 158 2010-06-10 08:46:49Z nikosdion
*/
private function getFolders($folder)
{
// Initialize variables
$arr = [];
$false = false;
$folder = trim($folder);
if (! is_dir($folder) && ! is_dir($folder . \DIRECTORY_SEPARATOR) || is_link($folder . \DIRECTORY_SEPARATOR) || is_link(
$folder
) || ! $folder) {
return $false;
}
if (@file_exists($folder . \DIRECTORY_SEPARATOR . '.myjoomla.ignore.folder')) {
return [];
}
$handle = @opendir($folder);
if (false === $handle) {
$handle = @opendir($folder . \DIRECTORY_SEPARATOR);
}
// If directory is not accessible, just return FALSE
if (false === $handle) {
return $false;
}
while ((false !== ($file = @readdir($handle)))) {
if (('.' != $file) && ('..' != $file) && (null != trim($file))) {
$ds = ('' == $folder) || (\DIRECTORY_SEPARATOR == $folder) || (\DIRECTORY_SEPARATOR == @substr(
$folder,
-1
)) || (\DIRECTORY_SEPARATOR == @substr($folder, -1)) ? '' : \DIRECTORY_SEPARATOR;
$dir = trim($folder . $ds . $file);
$isDir = @is_dir($dir);
if ($isDir) {
$arr[] = $this->cleanupFileFolderName(str_replace(JPATH_BASE, '', $folder . \DIRECTORY_SEPARATOR . $file));
}
}
}
@closedir($handle);
return $arr;
}
/**
* Clean up a string, a path name.
*
* @param string $str
*
* @return string
*/
private function cleanupFileFolderName($str)
{
$str = str_replace('////', '/', $str);
$str = str_replace('///', '/', $str);
$str = str_replace('//', '/', $str);
$str = str_replace('\\/', '/', $str);
$str = str_replace('\\t', '/t', $str);
$str = str_replace("\/", '/', $str);
return addslashes($str);
}
/**
* I get the number of core files that failed the hash checking.
*/
private function getCoreHashFailedFileList()
{
// set up the limit and limit start for the SQL
$limitstart = (int) $this->_dataObj->ls;
$limit = (int) $this->_dataObj->limit;
$this->_db->setQuery(
'SELECT id, filewithpath, filemtime, fileperms FROM bf_files WHERE hashfailed = 1 LIMIT ' . $limitstart . ', ' . $limit
);
// Get the files from the cache
$files = $this->_db->loadObjectList();
// get the count as well, for pagination
$this->_db->setQuery('SELECT count(*) from bf_files WHERE hashfailed = 1');
$count = $this->_db->loadResult();
// send back the totals
bfEncrypt::reply('success', [
'files' => $files,
'total' => $count,
]);
}
/**
* I get list of database tables that begin with bak_.
*/
private function deleteBakTables()
{
$tables = $this->getBakTables(true);
// for all the bak tables
foreach ($tables as $table) {
// compose the sql query
$this->_db->setQuery('DROP TABLE ' . $table[0]);
// delete the bak_tables
$this->_db->execute();
}
$count = count($tables);
// send back the totals
bfEncrypt::reply('success', [
'tables' => $tables,
'total' => $count,
]);
}
/**
* I get list of database tables that begin with bak_.
*/
private function getBakTables($internal = false)
{
// Get the database name
$config = JFactory::getApplication()->getConfig();
$dbname = $config->get('db', '');
// compose the sql query
$this->_db->setQuery("SHOW TABLES WHERE `Tables_in_{$dbname}` like 'bak_%'");
// Get the bak_tables
$tables = $this->_db->loadRowList();
// return array if we are internally calling this method
if (true === $internal) {
return $tables;
}
// count them
$count = count($tables);
// send back the totals
bfEncrypt::reply('success', [
'tables' => $tables,
'total' => $count,
]);
}
/**
* get the value of the $live_site var from configuration.php.
*/
private function getConfiguredLiveSite()
{
// send back the totals
bfEncrypt::reply('success', [
'live_site' => Factory::getApplication()->getConfig()->get('live_site'),
]);
}
/**
* Get a list of folders with 777 permissions.
*/
private function getFolderPermissions()
{
// set up the limit and the limitstart SQL
$limitstart = (int) $this->_dataObj->ls;
$limit = (int) $this->_dataObj->limit;
$this->_db->setQuery(
'SELECT `id`, `folderwithpath`, `folderinfo` from bf_folders WHERE folderinfo IN ("777", "351", "311") LIMIT ' . $limitstart . ', ' . $limit
);
// get the files
$files = $this->_db->loadObjectList();
// get the count for pagination
$this->_db->setQuery('SELECT count(*) from bf_folders WHERE `folderinfo` IN ("777", "351", "311")');
$count = $this->_db->loadResult();
// send back the totals
bfEncrypt::reply('success', [
'files' => $files,
'total' => $count,
]);
}
/**
* Get a list of files with 777 permissions.
*/
private function getFilePermissions()
{
// set up the limit and the limitstart SQL
$limitstart = (int) $this->_dataObj->ls;
$limit = (int) $this->_dataObj->limit;
$this->_db->setQuery(
'SELECT id, filewithpath, fileperms from bf_files WHERE fileperms = "0777" OR fileperms = "777" LIMIT ' . (int) $limitstart . ', ' . $limit
);
// get the files
$files = $this->_db->loadObjectList();
// get the count for pagination
$this->_db->setQuery('SELECT count(*) from bf_files WHERE fileperms = "0777" OR fileperms = "777"');
$count = $this->_db->loadResult();
// send back the totals
bfEncrypt::reply('success', [
'files' => $files,
'total' => $count,
]);
}
/**
* Set the permissions on files that have 777 perms to be 644.
*/
private function setFilePermissions()
{
$fixed = 0;
$errors = 0;
$this->_db->setQuery('SELECT id, filewithpath from bf_files WHERE fileperms = "0777" OR fileperms = "777"');
$files = $this->_db->loadObjectList();
foreach ($files as $file) {
if (@chmod(JPATH_BASE . $file->filewithpath, 0644)) {
++$fixed;
$this->_db->setQuery('UPDATE bf_files SET fileperms = "0644" WHERE id = "' . (int) $file->id . '"');
$this->_db->execute();
} else {
++$errors;
}
}
$this->_db->setQuery('SELECT count(*) FROM bf_folders WHERE folderinfo LIKE "%777%"');
$folders_777 = $this->_db->LoadResult();
$res = new stdClass();
$res->errors = $errors;
$res->fixed = $fixed;
$res->leftover = $folders_777;
bfEncrypt::reply('success', $res);
}
/**
* Return the list of files that have been flagged as containing mail commands or text.
*/
private function getUploaderFileList()
{
// make sure we only retrieve a small dataset
$limitstart = (int) $this->_dataObj->ls;
$sort = $this->_dataObj->s;
if (! $sort) {
$sort = 'filewithpath';
}
if (! in_array($sort, ['filewithpath', 'filemtime'])) {
exit('Invalid Sort');
}
if ('filemtime' == $sort) {
$sort = 'filemtime DESC';
}
$limit = (int) $this->_dataObj->limit;
// Set the query
$this->_db->setQuery('SELECT id, iscorefile, filewithpath, filemtime, fileperms, `size`, iscorefile from bf_files
WHERE uploader = 1
ORDER BY ' . $sort . '
LIMIT ' . (int) $limitstart . ', ' . $limit);
// Get an object list of files
$files = $this->_db->loadObjectList();
// see how many files there are in total without a limit
$this->_db->setQuery('SELECT count(*) from bf_files WHERE uploader = 1');
$count = $this->_db->loadResult();
// Only show files that still exist on the hard drive
$existingFiles = [];
foreach ($files as $k => $file) {
if (file_exists(JPATH_BASE . $file->filewithpath)) {
$existingFiles[] = $file;
} else {
$this->_db->setQuery(sprintf('DELETE FROM bf_files WHERE filewithpath = "%s"', $file->filewithpath));
$this->_db->execute();
--$count;
}
}
// return an encrypted reply
bfEncrypt::reply('success', [
'files' => $existingFiles,
'total' => $count,
]);
}
/**
* Return the list of files that have been flagged as containing mail commands or text.
*/
private function getMailerFileList()
{
$sort = null;
if (property_exists($this->_dataObj, 's')) {
$sort = $this->_dataObj->s;
}
// make sure we only retrieve a small dataset
$limitstart = (int) $this->_dataObj->ls;
if (! $sort) {
$sort = 'filewithpath';
}
if (! in_array($sort, ['filewithpath', 'filemtime'])) {
exit('Invalid Sort');
}
if ('filemtime' == $sort) {
$sort = 'filemtime DESC';
}
$limit = (int) $this->_dataObj->limit;
// Set the query
$this->_db->setQuery('SELECT id, iscorefile, filewithpath, filemtime, fileperms, `size`, iscorefile from bf_files
WHERE mailer = 1
ORDER BY ' . $sort . '
LIMIT ' . (int) $limitstart . ', ' . $limit);
// Get an object list of files
$files = $this->_db->loadObjectList();
// see how many files there are in total without a limit
$this->_db->setQuery('SELECT count(*) from bf_files WHERE mailer = 1');
$count = $this->_db->loadResult();
// Only show files that still exist on the hard drive
$existingFiles = [];
foreach ($files as $k => $file) {
if (file_exists(JPATH_BASE . $file->filewithpath)) {
$existingFiles[] = $file;
} else {
$this->_db->setQuery(sprintf('DELETE FROM bf_files WHERE filewithpath = "%s"', $file->filewithpath));
$this->_db->execute();
--$count;
}
}
// return an encrypted reply
bfEncrypt::reply('success', [
'files' => $existingFiles,
'total' => $count,
]);
}
/**
* Return the list of files that have been flagged as containing patterns that match our suspect patterns These
* maybe false positives for suspect content, but might be examples of bad code standards like using ../../../ or
* eval() method.
*/
private function getSuspectContentFileList()
{
// make sure we only retrieve a small dataset
$limitstart = (int) $this->_dataObj->ls;
$sort = $this->_dataObj->s;
if (! $sort) {
$sort = 'filewithpath';
}
if (! in_array($sort, ['filewithpath', 'filemtime'])) {
exit('Invalid Sort');
}
if ('filemtime' == $sort) {
$sort = 'filemtime DESC';
}
$limit = (int) $this->_dataObj->limit;
// Set the query
$this->_db->setQuery(
'SELECT id, iscorefile, filewithpath, filemtime, fileperms, `size`, iscorefile, hacked, currenthash from bf_files
WHERE suspectcontent = 1 AND (hacked = 0 OR hacked IS NULL)
ORDER BY ' . $sort . '
LIMIT ' . (int) $limitstart . ', ' . $limit
);
// Get an object list of files
$files = $this->_db->loadObjectList();
// see how many files there are in total without a limit
$this->_db->setQuery('SELECT count(*) from bf_files WHERE suspectcontent = 1 AND (hacked = 0 OR hacked IS NULL)');
$count = $this->_db->loadResult();
// Only show files that still exist on the hard drive
$existingFiles = [];
foreach ($files as $k => $file) {
if (file_exists(JPATH_BASE . $file->filewithpath)) {
$existingFiles[] = $file;
} else {
$this->_db->setQuery(sprintf('DELETE FROM bf_files WHERE filewithpath = "%s"', $file->filewithpath));
$this->_db->execute();
--$count;
}
}
// return an encrypted reply
bfEncrypt::reply('success', [
'files' => $existingFiles,
'total' => $count,
]);
}
/**
* Return only the files we have confirmed are hacked (hacked = 1).
* Unlike the suspect-content list, every file here is a definite match against a
* known-bad hash, a known-bad filename pattern, or a detected compromise. No heuristics.
*/
private function getHackedFileList()
{
// make sure we only retrieve a small dataset
$limitstart = (int) $this->_dataObj->ls;
$sort = $this->_dataObj->s;
if (! $sort) {
$sort = 'filewithpath';
}
if (! in_array($sort, ['filewithpath', 'filemtime'])) {
exit('Invalid Sort');
}
if ('filemtime' == $sort) {
$sort = 'filemtime DESC';
}
$limit = (int) $this->_dataObj->limit;
// Set the query
$this->_db->setQuery(
'SELECT id, iscorefile, filewithpath, filemtime, fileperms, `size`, iscorefile, hacked, currenthash from bf_files
WHERE hacked = 1
ORDER BY ' . $sort . '
LIMIT ' . (int) $limitstart . ', ' . $limit
);
// Get an object list of files
$files = $this->_db->loadObjectList();
// see how many files there are in total without a limit
$this->_db->setQuery('SELECT count(*) from bf_files WHERE hacked = 1');
$count = $this->_db->loadResult();
// Only show files that still exist on the hard drive
$existingFiles = [];
foreach ($files as $k => $file) {
if (file_exists(JPATH_BASE . $file->filewithpath)) {
// A corrupt filename (invalid UTF-8) makes json_encode fail and would
// blank the entire reply, so skip and log just that row.
if (json_encode($file) !== false) {
$existingFiles[] = $file;
} else {
bfLog::log('FILE ENCODING ERROR: ' . $file->filewithpath);
}
} else {
$this->_db->setQuery(sprintf('DELETE FROM bf_files WHERE filewithpath = "%s"', $file->filewithpath));
$this->_db->execute();
--$count;
}
}
// return an encrypted reply
bfEncrypt::reply('success', [
'files' => $existingFiles,
'total' => $count,
]);
}
private function getNeedsCompact()
{
// make sure we only retrieve a small dataset
$limitstart = (int) $this->_dataObj->ls;
$sort = $this->_dataObj->s;
if (! $sort) {
$sort = 'filewithpath';
}
if (! in_array($sort, ['filewithpath', 'filemtime'])) {
exit('Invalid Sort');
}
if ('filemtime' === $sort) {
$sort = 'filemtime DESC';
}
$limit = (int) $this->_dataObj->limit;
// Set the query
$this->_db->setQuery(
'SELECT id, iscorefile, filewithpath, filemtime, fileperms, `size`, iscorefile, hacked, currenthash from bf_files
WHERE needscompat = 1
ORDER BY ' . $sort . '
LIMIT ' . (int) $limitstart . ', ' . $limit
);
// Get an object list of files
$files = $this->_db->loadObjectList();
// see how many files there are in total without a limit
$this->_db->setQuery('SELECT count(*) from bf_files WHERE needscompat = 1');
$count = $this->_db->loadResult();
// Only show files that still exist on the hard drive
$existingFiles = [];
foreach ($files as $k => $file) {
if (file_exists(JPATH_BASE . $file->filewithpath)) {
$existingFiles[] = $file;
} else {
$this->_db->setQuery(sprintf('DELETE FROM bf_files WHERE filewithpath = "%s"', $file->filewithpath));
$this->_db->execute();
--$count;
}
}
// return an encrypted reply
bfEncrypt::reply('success', [
'files' => $existingFiles,
'total' => $count,
]);
}
/**
* Get SQL files found.
*/
private function getSQLFiles()
{
// make sure we only retrieve a small dataset
$limitstart = (int) $this->_dataObj->ls;
$sort = $this->_dataObj->s;
if (! $sort) {
$sort = 'filewithpath';
}
if (! in_array($sort, ['filewithpath', 'filemtime'])) {
exit('Invalid Sort');
}
if ('filemtime' == $sort) {
$sort = 'filemtime DESC';
}
$limit = (int) $this->_dataObj->limit;
// Set the query
$this->_db->setQuery('SELECT * FROM bf_files WHERE
(
(filewithpath LIKE \'%.sql\' or filewithpath LIKE \'%sql/site.%\')
and
(iscorefile = 0 or iscorefile is null)
)
ORDER BY ' . $sort . '
LIMIT ' . (int) $limitstart . ', ' . $limit);
// Get an object list of files
$files = $this->_db->loadObjectList();
// see how many files there are in total without a limit
$this->_db->setQuery('SELECT count(*) FROM bf_files WHERE
(
(filewithpath LIKE \'%.sql\' or filewithpath LIKE \'%sql/site.%\')
and
(iscorefile = 0 or iscorefile is null)
)');
$count = $this->_db->loadResult();
// Only show files that still exist on the hard drive
$existingFiles = [];
foreach ($files as $k => $file) {
if (file_exists(JPATH_BASE . $file->filewithpath)) {
$existingFiles[] = $file;
} else {
$this->_db->setQuery(sprintf('DELETE FROM bf_files WHERE filewithpath = "%s"', $file->filewithpath));
$this->_db->execute();
--$count;
}
}
// return an encrypted reply
bfEncrypt::reply('success', [
'files' => $existingFiles,
'total' => $count,
]);
}
/**
* Return the list of files that have been flagged as containing patterns that match our suspect patterns These
* maybe false positives for suspect content, but might be examples of bad code standards like using ../../../ or
* eval() method.
*/
private function getNonCoreFileList()
{
// make sure we only retrieve a small dataset
$limitstart = (int) $this->_dataObj->ls;
$sort = $this->_dataObj->s;
if (! $sort) {
$sort = 'filewithpath';
}
if (! in_array($sort, ['filewithpath', 'filemtime'])) {
exit('Invalid Sort');
}
if ('filemtime' == $sort) {
$sort = 'filemtime DESC';
}
$limit = (int) $this->_dataObj->limit;
// Set the query
$this->_db->setQuery('SELECT id, iscorefile, filewithpath, filemtime, fileperms, `size`, iscorefile from bf_files
WHERE iscorefile IS NULL
ORDER BY ' . $sort . '
LIMIT ' . (int) $limitstart . ', ' . $limit);
// Get an object list of files
$files = $this->_db->loadObjectList();
// see how many files there are in total without a limit
$this->_db->setQuery('SELECT count(*) from bf_files WHERE iscorefile IS NULL');
$count = $this->_db->loadResult();
// Only show files that still exist on the hard drive
$existingFiles = [];
foreach ($files as $k => $file) {
if (file_exists(JPATH_BASE . $file->filewithpath)) {
$existingFiles[] = $file;
} else {
$this->_db->setQuery(sprintf('DELETE FROM bf_files WHERE filewithpath = "%s"', $file->filewithpath));
$this->_db->execute();
--$count;
}
}
// return an encrypted reply
bfEncrypt::reply('success', [
'files' => $existingFiles,
'total' => $count,
]);
}
/**
* @param bool $internal
*
* @return array|mixed
*/
private function getInstallationFolders($internal = false)
{
$installationFolders = [];
$folders = $this->getFolders(JPATH_BASE);
foreach ($folders as $folder) {
if (preg_match(
'/installation|installation.old|docs\/installation|install|installation.bak|installation.old|installation.backup|installation.delete/i',
$folder
)) {
$installationFolders[] = $folder;
}
}
bfEncrypt::reply('success', [
'files' => $installationFolders,
'total' => count($installationFolders),
]);
}
/**
* @param bool $internal
*
* @return array|mixed
*/
private function getRecentlyModified($internal = false)
{
$limitstart = (int) $this->_dataObj->ls;
$limit = (int) $this->_dataObj->limit;
if (! $limitstart) {
$limitstart = 0;
}
if (! $limit) {
$limit = '9999999999999999';
}
$sql = "SELECT * FROM bf_files WHERE filemtime > '" . strtotime(
'-3 days',
time()
) . "' ORDER BY filemtime DESC LIMIT " . (int) $limitstart . ', ' . $limit;
$this->_db->setQuery($sql);
$files = $this->_db->LoadObjectList();
if (true === $internal) {
return $files;
}
$this->_db->setQuery("SELECT count(*) FROM bf_files WHERE filemtime > '" . strtotime('-3 days', time()) . "'");
$count = $this->_db->loadResult();
bfEncrypt::reply('success', [
'files' => $files,
'total' => $count,
]);
}
/**
* @param bool $internal
*
* @return array|mixed
*/
private function getHtaccessFiles($internal = false)
{
$limitstart = (int) $this->_dataObj->ls;
$limit = (int) $this->_dataObj->limit;
if (! $limitstart) {
$limitstart = 0;
}
if (! $limit) {
$limit = '9999999999999999';
}
$sql = "SELECT * FROM bf_files WHERE filewithpath LIKE '%/.htaccess' ORDER BY filewithpath DESC LIMIT " . (int) $limitstart . ', ' . $limit;
$this->_db->setQuery($sql);
$files = $this->_db->LoadObjectList();
if (true === $internal) {
return $files;
}
$this->_db->setQuery("SELECT count(*) FROM bf_files WHERE filewithpath LIKE '%/.htaccess'");
$count = $this->_db->loadResult();
bfEncrypt::reply('success', [
'files' => $files,
'total' => $count,
]);
}
/**
* @param bool $internal
*
* @return array|mixed
*/
private function getLargefiles($internal = false)
{
$limitstart = (int) $this->_dataObj->ls;
$limit = (int) $this->_dataObj->limit;
$order = $this->_dataObj->orderby;
if (! in_array($order, ['filewithpath', 'filemtime', 'size'])) {
$order = 'filewithpath';
}
if (! $limitstart) {
$limitstart = 0;
}
if (! $limit) {
$limit = '9999999999999999';
}
$sql = 'SELECT * FROM bf_files WHERE SIZE > 2097152 ORDER BY ' . $order . ' DESC LIMIT ' . (int) $limitstart . ', ' . $limit;
$this->_db->setQuery($sql);
$files = $this->_db->LoadObjectList();
if (true === $internal) {
return $files;
}
$this->_db->setQuery('SELECT COUNT(*) FROM bf_files WHERE SIZE > 2097152');
$count = (int) $this->_db->loadResult();
bfEncrypt::reply('success', [
'files' => $files,
'total' => $count,
]);
}
/**
* @param bool $internal
*
* @return array|mixed
*/
private function getArchivefiles($internal = false)
{
$limitstart = (int) $this->_dataObj->ls;
$limit = (int) $this->_dataObj->limit;
if (! $limitstart) {
$limitstart = 0;
}
if (! $limit) {
$limit = '9999999999999999';
}
$sql = 'SELECT * FROM bf_files WHERE
filewithpath LIKE "%.zip"
OR filewithpath LIKE "%.tar"
OR filewithpath LIKE "%.tar.gz"
OR filewithpath LIKE "%.bz2"
OR filewithpath LIKE "%.gzip"
OR filewithpath LIKE "%.bzip2" ORDER BY filemtime DESC LIMIT ' . (int) $limitstart . ', ' . $limit;
$this->_db->setQuery($sql);
$files = $this->_db->LoadObjectList();
if (true === $internal) {
return $files;
}
$this->_db->setQuery('SELECT count(*) FROM bf_files WHERE
filewithpath LIKE "%.zip"
OR filewithpath LIKE "%.tar"
OR filewithpath LIKE "%.tar.gz"
OR filewithpath LIKE "%.bz2"
OR filewithpath LIKE "%.gzip"
OR filewithpath LIKE "%.bzip2"');
$count = (int) $this->_db->loadResult();
bfEncrypt::reply('success', [
'files' => $files,
'total' => $count,
]);
}
/**
* @param bool $internal
*
* @return array|mixed
*/
private function getPhpinwrongplace($internal = false)
{
$limitstart = (int) $this->_dataObj->ls;
$limit = (int) $this->_dataObj->limit;
if (! $limitstart) {
$limitstart = 0;
}
if (! $limit) {
$limit = '9999999999999999';
}
// .php and the other server-executable variants (.phtml/.pht/.shtml) - JCE-style
// attacks drop these too, not just .php. [.] = literal dot in MySQL REGEXP.
$sql = 'SELECT * FROM bf_files AS b WHERE filewithpath REGEXP "^/images/.*[.](php|phtml|pht|shtml)$" ORDER BY filemtime DESC LIMIT ' . (int) $limitstart . ', ' . $limit;
$this->_db->setQuery($sql);
$files = $this->_db->LoadObjectList();
if (true === $internal) {
return $files;
}
$count = (int) count($files);
bfEncrypt::reply('success', [
'files' => $files,
'total' => $count,
]);
}
/**
* @param bool $internal
*
* @return array|mixed
*/
private function getTmpfiles($internal = false)
{
$limitstart = (int) $this->_dataObj->ls;
$limit = (int) $this->_dataObj->limit;
if (! $limitstart) {
$limitstart = 0;
}
if (! $limit) {
$limit = '9999999999999999';
}
$sql = 'SELECT * FROM bf_files WHERE
filewithpath LIKE "/tmp%"
AND
filewithpath != "/tmp/index.html"
ORDER BY filemtime DESC LIMIT ' . (int) $limitstart . ', ' . $limit;
$this->_db->setQuery($sql);
$files = $this->_db->LoadObjectList();
if (true === $internal) {
return $files;
}
$this->_db->setQuery('SELECT count(*) FROM bf_files WHERE
filewithpath LIKE "/tmp%"
AND
filewithpath != "/tmp/index.html"
ORDER BY filemtime');
$count = (int) $this->_db->loadResult();
bfEncrypt::reply('success', [
'files' => $files,
'total' => $count,
]);
}
private function clearFluffFiles()
{
require 'bfFilesystem.php';
foreach ($this->fluffFiles as $file) {
// ensure we are based correctly
$fileWithPath = JPATH_BASE . $file;
// Remove File.
unlink($fileWithPath);
}
$this->getFlufffiles(true);
}
/**
* @param bool $internal
*
* @return array|mixed
*/
private function getFlufffiles($internal = false)
{
$files = [];
$files['present'] = [];
$files['notpresent'] = [];
foreach ($this->fluffFiles as $file) {
// ensure we are based correctly
$fileWithPath = JPATH_BASE . $file;
// determine if the file is present or not
if (@file_exists($fileWithPath)) { //@ to avoid any nasty warnings
$files['present'][] = $file;
} else {
$files['notpresent'][] = $file;
}
}
bfEncrypt::reply('success', [
'total' => count($files['present']),
'files' => $files,
]);
}
/**
* @param bool $internal
*
* @return array|mixed
*/
private function getRenamedToHide($internal = false)
{
$limitstart = (int) $this->_dataObj->ls;
$limit = (int) $this->_dataObj->limit;
if (! $limitstart) {
$limitstart = 0;
}
if (! $limit) {
$limit = '9999999999999999';
}
$sql = 'SELECT * FROM bf_files WHERE
filewithpath LIKE "%.backup%"
OR
filewithpath LIKE "%.bak%"
OR
filewithpath LIKE "%.old%"
ORDER BY filemtime DESC LIMIT ' . (int) $limitstart . ', ' . $limit;
$this->_db->setQuery($sql);
$files = $this->_db->LoadObjectList();
if (true === $internal) {
return $files;
}
$this->_db->setQuery('SELECT count(*) FROM bf_files WHERE
filewithpath LIKE "%.backup%"
OR
filewithpath LIKE "%.bak%"
OR
filewithpath LIKE "%.old%"');
$count = $this->_db->loadResult();
bfEncrypt::reply('success', [
'files' => $files,
'total' => $count,
]);
}
private function clearTmpFiles()
{
require 'bfFilesystem.php';
$filesAndFolders = Bf_Filesystem::readDirectory(JPATH_ROOT . '/tmp', '.', true);
foreach ($filesAndFolders as $pointer) {
$pointer = JPATH_ROOT . '/tmp/' . $pointer;
if (is_dir($pointer)) {
bfLog::log('Deleting ' . $pointer);
Bf_Filesystem::deleteRecursive($pointer, true);
} else {
bfLog::log('Deleting ' . $pointer);
unlink($pointer);
}
}
file_put_contents(JPATH_ROOT . '/tmp/index.html', '<html><body bgcolor="#FFFFFF"></body></html> ');
$sql = 'DELETE FROM bf_files WHERE
filewithpath LIKE "/tmp%"
AND
filewithpath != "/tmp/index.html"';
$this->_db->setQuery($sql);
$this->_db->execute();
bfEncrypt::reply('success', [
'res' => true,
]);
}
/**
* @param bool $internal
*
* @return array|mixed
*/
private function getDotfiles($internal = false)
{
$limitstart = (int) $this->_dataObj->ls;
$limit = (int) $this->_dataObj->limit;
if (! $limitstart) {
$limitstart = 0;
}
if (! $limit) {
$limit = '9999999999999999';
}
$sql = 'SELECT * FROM bf_files WHERE filewithpath LIKE "%/.%" ORDER BY filemtime DESC LIMIT ' . (int) $limitstart . ', ' . $limit;
$this->_db->setQuery($sql);
$files = $this->_db->LoadObjectList();
if (true === $internal) {
return $files;
}
$this->_db->setQuery('SELECT count(*) FROM bf_files WHERE filewithpath LIKE "%/.%"');
$count = $this->_db->loadResult();
bfEncrypt::reply('success', [
'files' => $files,
'total' => $count,
]);
}
/**
* Find files which have zero bytes (no content) as they just litter the webspace and run up inode counts. Joomla
* doesnt rely on zero byte files, we have seen "other hack cleanup companies" litter the webspace with zero byte
* files and so this tool deletes those too.
*
* @param bool $internal
*/
private function getZerobyteFiles($internal = false)
{
$limitstart = (int) $this->_dataObj->ls;
$limit = (int) $this->_dataObj->limit;
if (! $limitstart) {
$limitstart = 0;
}
if (! $limit) {
$limit = '9999999999999999';
}
$sql = 'SELECT * FROM bf_files WHERE size = 0 ORDER BY filemtime DESC LIMIT ' . (int) $limitstart . ', ' . $limit;
$this->_db->setQuery($sql);
$files = $this->_db->LoadObjectList();
if (true === $internal) {
return $files;
}
$this->_db->setQuery('SELECT count(*) FROM bf_files WHERE size = 0');
$count = $this->_db->loadResult();
bfEncrypt::reply('success', [
'files' => $files,
'total' => $count,
]);
}
/**
* Restore core files from a trusted source.
*
* This source (corefiles.myjoomla.io) is checked hourly for integrity, if you are concerned about MITM Attacks,
* well, if your server is compromised enough for a MITM Attack then you have bigger issues, plus this is how Joomla
* updates happen anyway so no additional security issues are created with this code!
*/
private function restoreAllMissingFiles()
{
$url = 'https://corefiles.myjoomla.io/%s%s?raw';
$restored = 0;
$notRestored = 0;
// Crappy Servers Alert!
@set_time_limit(3600);
$files = $this->getMissingCoreFiles(true);
foreach ($files as $file) {
$downloadUrl = sprintf($url, JVERSION, $file->filewithpath);
$restoreToFile = JPATH_BASE . $file->filewithpath;
// check folder and path to folder exists
$folder = dirname($restoreToFile);
if (! file_exists($folder)) {
@mkdir($folder, 0755, true);
}
$content = file_get_contents($downloadUrl);
if ($content && file_exists($folder) && file_put_contents($restoreToFile, $content)) {
// Set correct permissions @ for crappy servers
@chmod($restoreToFile, 0644);
// Update the cache database tables so we dont have to run a new audit right away
$sql = "INSERT INTO `bf_files`
(`id`, `filewithpath`, `fileperms`, `filemtime`, `toggler`, `currenthash`, `lasthash`, `iscorefile`, `hashfailed`, `hashchanged`, `hacked`, `suspectcontent`, `falsepositive`, `mailer`, `uploader`, `encrypted`, `queued`, `size`)
VALUES
(NULL, '%s', '0644', '%s', NULL, '%s', '%s', 1, NULL, NULL, NULL, 0, NULL, NULL, NULL, 0, 0, %s)";
$sql = sprintf(
$sql,
$file->filewithpath,
time(),
md5_file($restoreToFile),
md5_file($restoreToFile),
filesize($restoreToFile)
);
$this->_db->setQuery($sql);
$this->_db->execute();
++$restored;
} else {
++$notRestored;
}
}
bfEncrypt::reply('success', [
'total' => count($files),
'restored' => $restored,
'notrestored' => $notRestored,
]);
}
private function getMissingCoreFiles($internal = false)
{
$limitstart = (int) $this->_dataObj->ls;
$limit = (int) $this->_dataObj->limit;
if (! $limitstart) {
$limitstart = 0;
}
if (! $limit) {
$limit = '9999999999999999';
}
$sql = " FROM `bf_core_hashes`
WHERE filewithpath NOT IN (
SELECT filewithpath from bf_files
)
AND filewithpath NOT LIKE '/installation/%'
AND filewithpath != '/robots.txt.dist'
AND filewithpath != '/administrator/manifests/packages/pkg_weblinks.xml'
AND filewithpath != '/'
AND filewithpath != '/robots.txt.dist'
AND filewithpath != '/web.config.txt'
AND filewithpath != '/joomla.xml'
AND filewithpath != '/build.xml'
AND filewithpath != '/LICENSE.txt'
AND filewithpath != '/README.txt'
AND filewithpath != '/htaccess.txt'
AND filewithpath != '/LICENSES.php'
AND filewithpath != '/configuration.php-dist'
AND filewithpath != '/CHANGELOG.php'
AND filewithpath != '/COPYRIGHT.php'
AND filewithpath != '/CREDITS.php'
AND filewithpath != '/INSTALL.php'
AND filewithpath != '/LICENSE.php'
AND filewithpath != '/CONTRIBUTING.md'
AND filewithpath != '/phpunit.xml.dist'
AND filewithpath != '/.drone.yml'
AND filewithpath != '/README.md'
AND filewithpath != '/.travis.yml'
AND filewithpath != '/travisci-phpunit.xml'
AND filewithpath != '/images/banners/osmbanner1.png'
AND filewithpath != '/images/banners/osmbanner2.png'
AND filewithpath != '/images/banners/shop-ad-books.jpg'
AND filewithpath != '/images/banners/shop-ad.jpg'
AND filewithpath != '/images/banners/white.png'
AND filewithpath != '/images/headers/blue-flower.jpg'
AND filewithpath != '/images/headers/maple.jpg'
AND filewithpath != '/images/headers/raindrops.jpg'
AND filewithpath != '/images/headers/walden-pond.jpg'
AND filewithpath != '/images/headers/windows.jpg'
AND filewithpath != '/images/joomla_black.gif'
AND filewithpath != '/images/joomla_black.png'
AND filewithpath != '/images/joomla_green.gif'
AND filewithpath != '/images/joomla_logo_black.jpg'
AND filewithpath != '/images/powered_by.png'
AND filewithpath != '/images/sampledata/fruitshop/apple.jpg'
AND filewithpath != '/images/sampledata/fruitshop/bananas_2.jpg'
AND filewithpath != '/images/sampledata/fruitshop/fruits.gif'
AND filewithpath != '/images/sampledata/fruitshop/tamarind.jpg'
AND filewithpath != '/images/sampledata/parks/animals/180px_koala_ag1.jpg'
AND filewithpath != '/images/sampledata/parks/animals/180px_wobbegong.jpg'
AND filewithpath != '/images/sampledata/parks/animals/200px_phyllopteryx_taeniolatus1.jpg'
AND filewithpath != '/images/sampledata/parks/animals/220px_spottedquoll_2005_seanmcclean.jpg'
AND filewithpath != '/images/sampledata/parks/animals/789px_spottedquoll_2005_seanmcclean.jpg'
AND filewithpath != '/images/sampledata/parks/animals/800px_koala_ag1.jpg'
AND filewithpath != '/images/sampledata/parks/animals/800px_phyllopteryx_taeniolatus1.jpg'
AND filewithpath != '/images/sampledata/parks/animals/800px_wobbegong.jpg'
AND filewithpath != '/images/sampledata/parks/banner_cradle.jpg'
AND filewithpath != '/images/sampledata/parks/landscape/120px_pinnacles_western_australia.jpg'
AND filewithpath != '/images/sampledata/parks/landscape/120px_rainforest_bluemountainsnsw.jpg'
AND filewithpath != '/images/sampledata/parks/landscape/180px_ormiston_pound.jpg'
AND filewithpath != '/images/sampledata/parks/landscape/250px_cradle_mountain_seen_from_barn_bluff.jpg'
AND filewithpath != '/images/sampledata/parks/landscape/727px_rainforest_bluemountainsnsw.jpg'
AND filewithpath != '/images/sampledata/parks/landscape/800px_cradle_mountain_seen_from_barn_bluff.jpg'
AND filewithpath != '/images/sampledata/parks/landscape/800px_ormiston_pound.jpg'
AND filewithpath != '/images/sampledata/parks/landscape/800px_pinnacles_western_australia.jpg'
AND filewithpath != '/images/sampledata/parks/parks.gif' ORDER BY filewithpath DESC ";
$limitIt = 'LIMIT ' . (int) $limitstart . ', ' . $limit;
$this->_db->setQuery('SELECT * ' . $sql . $limitIt);
$files = $this->_db->LoadObjectList();
foreach ($files as $k => $file) {
if (file_exists(JPATH_BASE . $file->filewithpath)) {
unset($files[$k]);
}
}
if (true === $internal) {
return $files;
}
$this->_db->setQuery('SELECT count(*) ' . $sql . $limitIt);
$count = $this->_db->loadResult();
bfEncrypt::reply('success', [
'files' => $files,
'total' => $count,
]);
}
/**
* Tool57 Delete files which have zero bytes (no content) as they just litter the webspace and run up inode counts.
* Joomla doesnt rely on zero byte files, we have seen "other hack cleanup companies" litter the webspace with zero
* byte files and so this tool deletes those too.
*/
private function deleteZerobyteFiles()
{
$sql = 'SELECT filewithpath FROM bf_files WHERE size = 0';
$this->_db->setQuery($sql);
$files = $this->_db->LoadObjectList();
$filesDeleted = [];
$count = 0;
foreach ($files as $file) {
$fullFilePath = JPATH_BASE . $file->filewithpath;
if (@unlink($fullFilePath)) {
++$count;
$filesDeleted[] = $file->filewithpath;
$sql = sprintf('DELETE FROM bf_files WHERE filewithpath = " % s"', $file->filewithpath);
$this->_db->setQuery($sql);
$this->_db->execute();
}
}
bfEncrypt::reply('success', [
'files' => $filesDeleted,
'total' => $count,
]);
}
/**
* @param bool $internal
*
* @return array|mixed
*/
private function getEncrypted($internal = false)
{
$limitstart = (int) $this->_dataObj->ls;
$limit = (int) $this->_dataObj->limit;
if (! $limitstart) {
$limitstart = 0;
}
if (! $limit) {
$limit = '9999999999999999';
}
$sql = 'SELECT * FROM bf_files WHERE encrypted = 1 ORDER BY filemtime DESC LIMIT ' . (int) $limitstart . ', ' . $limit;
$this->_db->setQuery($sql);
$files = $this->_db->LoadObjectList();
if (true === $internal) {
return $files;
}
$this->_db->setQuery('SELECT count(*) FROM bf_files WHERE encrypted = 1');
$count = $this->_db->loadResult();
bfEncrypt::reply('success', [
'files' => $files,
'total' => $count,
]);
}
/**
* @param bool $internal
*
* @return JUser|mixed|object
*/
private function getUser($internal = false)
{
$row = null;
switch ($this->_dataObj->searchfield) {
case 'username':
$sql = "SELECT * FROM #__users WHERE username = '%s'";
$sql = sprintf($sql, $this->_dataObj->searchvalue);
$this->_db->setQuery($sql);
$row = $this->_db->loadObject();
break;
case 'id':
$row = new User();
$row->load((int) $this->_dataObj->searchvalue);
break;
}
if ($row->id) {
// NEVER let the users password leave the remote site
$row->password = '**REMOVED**';
}
if (true === $internal) {
return $row;
}
bfEncrypt::reply('success', [
'user' => $row,
]);
}
/**
* get $root_user from the configuration.php.
*/
private function getHasrootuser()
{
bfEncrypt::reply('success', [
'root_user' => Factory::getApplication()->getConfig()->get('root_user', ''),
]);
}
/**
* set debug_lang to false in the configuration.php.
*/
private function setHasrootuser()
{
$this->_setConfigParam('root_user', '', 'string', true);
bfEncrypt::reply('success', [
'root_user' => '',
]);
}
/**
* get debug_lang from the configuration.php.
*/
private function getDebuglanguage()
{
bfEncrypt::reply('success', [
'debug_lang' => Factory::getApplication()->getConfig()->get('debug_lang', ''),
]);
}
/**
* remove $root_user from the configuration.php.
*/
private function setDebuglanguage()
{
$this->_setConfigParam('debug_lang', ('true' == $this->_dataObj->s ? false : true), 'bool', true);
bfEncrypt::reply('success', [
'debug_lang' => '',
]);
}
/**
* remove $live_site from the configuration.php.
*/
private function removeLiveSite()
{
$this->_setConfigParam('live_site', '', 'string', true);
bfEncrypt::reply('success', []);
}
/**
* set the log_path and tmp_path to sane defaults.
*
* @throws exception Exception
*/
private function setLogTmpPaths()
{
// Require more complex methods for dealing with files
require 'bfFilesystem.php';
try {
// sane and recommended defaults
$logpath = JPATH_ROOT . \DIRECTORY_SEPARATOR . 'administrator/logs';
$tmpath = JPATH_ROOT . \DIRECTORY_SEPARATOR . 'tmp';
// force creation and set sane permissions
@mkdir($logpath);
@mkdir($tmpath);
@chmod($logpath, 0755);
@chmod($tmpath, 0755);
$config = Factory::getApplication()->getConfig();
if (version_compare(JVERSION, '3.0', 'ge')) {
$config->set('log_path', $logpath);
$config->set('tmp_path', $tmpath);
} else {
$config->setValue('config.log_path', $logpath);
$config->setValue('config.tmp_path', $tmpath);
}
$newConfig = $config->toString('PHP', [
'class' => 'JConfig',
'closingtag' => false,
]);
// On some occasions, Joomla! 1.6 ignores the configuration and
// produces "class c". Let's fix this!
$newConfig = str_replace('class c {', 'class JConfig {', $newConfig);
$newConfig = str_replace('namespace c;', '', $newConfig);
// Try to write out the configuration.php
$filename = JPATH_ROOT . \DIRECTORY_SEPARATOR . 'configuration.php';
$result = Bf_Filesystem::_write($filename, $newConfig);
if (false !== $result) {
bfEncrypt::reply('success', [
'log_path' => $logpath,
'tmp_path' => $tmpath,
'config_file' => $filename,
]);
} else {
bfEncrypt::reply(bfReply::ERROR, [
'msg' => 'Could Not Save Config',
]);
}
} catch (Exception $e) {
bfEncrypt::reply(bfReply::ERROR, [
'msg' => $e->getMessage(),
]);
}
}
/**
* Enable SEF and SEF Rewrite.
*/
private function setSEFConfig()
{
if ('true' == $this->_dataObj->s) {// true means, set to the OK value
$this->_setConfigParam('sef', true, 'bool', true);
$this->_setConfigParam('sef_rewrite', true, 'bool', true);
$this->_setConfigParam('sef_suffix', false, 'bool', true);
} else {
$this->_setConfigParam('sef', false, 'bool', true);
$this->_setConfigParam('sef_rewrite', false, 'bool', true);
$this->_setConfigParam('sef_suffix', false, 'bool', true);
}
bfEncrypt::reply('success', $this->getSEFConfig());
}
/**
* Get the settings for the SEF from Joomla Global Config.
*
* public $sef = '1'; public $sef_rewrite = '0'; public $sef_suffix = '0';
*/
private function getSEFConfig()
{
$config = Factory::getApplication()->getConfig();
$data = [
'sef' => $config->get('sef'),
'sef_rewrite' => $config->get('sef_rewrite'),
'sef_suffix' => $config->get('sef_suffix'),
];
bfEncrypt::reply('success', $data);
}
/**
* Set Cookie Settings right.
*/
private function setCookieSettings()
{
$this->_setConfigParam('cookie_domain', '', 'string', true);
$this->_setConfigParam('cookie_path', '', 'string', true);
bfEncrypt::reply('success', $this->getCookieSettings());
}
/**
* Get the settings for the cookie from config.
*
* public $cookie_domain public $cookie_path
*/
private function getCookieSettings()
{
$config = Factory::getApplication()->getConfig();
bfEncrypt::reply('success', [
'cookie_domain' => $config->get('cookie_domain'),
'cookie_path' => $config->get('cookie_path'),
]);
}
/**
* @throws exception Exception
*/
private function setDbPrefix()
{
try {
$prefix = $this->_dataObj->prefix;
$prefix = $this->_validateDbPrefix($prefix);
$config = Factory::getApplication()->getConfig();
$oldprefix = $config->get('dbprefix', '');
$dbname = $config->get('db', '');
$db = $this->_db;
$sql = "SHOW TABLES WHERE `Tables_in_{$dbname}` like '{$oldprefix}%'";
$db->setQuery($sql);
$oldTables = $db->loadColumn();
if (empty($oldTables)) {
throw new Exception('Could not find any tables with the old prefix to change to the new prefix');
}
foreach ($oldTables as $table) {
$newTable = $prefix . substr($table, strlen($oldprefix));
$sql = "RENAME TABLE `$table` TO `$newTable`";
$db->setQuery($sql);
if (! $db->execute()) {
// Something went wrong; I am pulling the plug and hope for
// the best
throw new Exception('Something went wrong; I am pulling the plug and hope for the best - Contact our support URGENTLY');
}
}
$this->_setConfigParam('prefix', $prefix, 'string', true);
bfEncrypt::reply('success', [
'prefix' => $prefix,
]);
} catch (Exception $exception) {
bfEncrypt::reply(bfReply::ERROR, [
'msg' => $exception->getMessage(),
]);
}
}
/**
* Validates a prefix. The prefix must be 3-6 lowercase characters followed by an underscore and must not alrady
* exist in the current database. It must also not be jos_ or bak_.
*
* @param string $prefix
* The prefix to check
*
* @return string bool validated prefix or false if the prefix is invalid
*
* @throws exception
*
* @copyright Copyright (c)2010-2011 Nicholas K. Dionysopoulos
*/
private function _validateDbPrefix($prefix)
{
// Check that the prefix is not jos_ or bak_
if (('jos_' == $prefix) || ('bak_' == $prefix)) {
throw new exception('Cannot be a standard prefix like jos_ or bak_');
}
// Check that we're not trying to reuse the same prefix
$config = Factory::getApplication()->getConfig();
$oldprefix = $config->get('dbprefix', '');
if ($prefix == $oldprefix) {
throw new exception('Cannot be the same as existing prefix');
}
// Check the length
$pLen = strlen($prefix);
if (($pLen < 4) || ($pLen > 6)) {
throw new exception('Prefix must be between 4 and 6 chars');
}
// Check that the prefix ends with an underscore
if ('_' != substr($prefix, -1)) {
throw new exception('Prefix must end with an underscore');
}
// Check that the part before the underscore is lowercase letters
$valid = preg_match('/[\w]_/i', $prefix);
if (0 === $valid) {
throw new exception('Prefix must be all lowercase');
}
// Turn the prefix into lowercase
$prefix = strtolower($prefix);
// Check if the prefix already exists in the database
$db = $this->_db;
$dbname = $config->get('db', '');
$sql = "SHOW TABLES WHERE `Tables_in_{$dbname}` like '{$prefix}%'";
$db->setQuery($sql);
if (version_compare(JVERSION, '3.0', 'ge')) {
$existing_tables = $db->loadColumn();
} else {
$existing_tables = $db->loadResultArray();
}
if (count($existing_tables)) {
// Sometimes we have false alerts, e.g. a prefix of dev_ will match
// tables starting with dev15_ or dev16_
$realCount = 0;
foreach ($existing_tables as $check) {
if (substr($check, 0, $pLen) == $prefix) {
++$realCount;
break;
}
}
if ($realCount) {
throw new exception('Prefix already exists in the database');
}
}
return $prefix;
}
/**
* Update details of a user, including a hashed password.
*
* @todo Not sure this is ever called anymore (April 2018)
*/
private function setUser()
{
$email = $this->_dataObj->email;
$pass = $this->_dataObj->password;
$username = $this->_dataObj->username;
$where = $this->_dataObj->where;
if (! $email || ! $pass || ! $username || ! $where) {
bfEncrypt::reply('failure', [
'msg' => 'Not all required parts set',
]);
}
$sql = 'UPDATE #__users SET username="%s", password="%s", email ="%s" WHERE %s';
$sql = sprintf($sql, $username, $pass, $email, $where);
$this->_db->setQuery($sql);
$id = $this->_db->execute();
bfEncrypt::reply('success', [
'usersaved' => $id,
]);
}
/**
* @param bool $internal
*
* @return array|mixed
*/
private function getErrorLogs($internal = false)
{
$limitstart = (int) $this->_dataObj->ls;
$limit = (int) $this->_dataObj->limit;
if (! $limitstart) {
$limitstart = 0;
}
if (! $limit) {
$limit = '9999999999999999'; //pah
}
$sql = "SELECT * FROM bf_files WHERE filewithpath LIKE '%error_log' ORDER BY filemtime DESC LIMIT " . (int) $limitstart . ', ' . $limit;
$this->_db->setQuery($sql);
$files = $this->_db->LoadObjectList();
if (true === $internal) {
return $files;
}
$this->_db->setQuery("SELECT count(*) FROM bf_files WHERE filewithpath LIKE '%error_log'");
$count = $this->_db->loadResult();
bfEncrypt::reply('success', [
'files' => $files,
'total' => $count,
]);
}
/**
* Save the robots.txt file.
*/
private function saveRobotsFile()
{
if (file_put_contents(JPATH_BASE . '/robots.txt', base64_decode($this->_dataObj->filecontents))) {
bfEncrypt::reply('success', [
'msg' => 'File saved!',
]);
} else {
bfEncrypt::reply('error', [
'msg' => 'File could not be saved!',
]);
}
}
/**
* ok ok I know this looks bad, it probably is, but this allows a subscriber to edit a file on mysites.guru and then
* save the contents back to mysites.guru.
*
* In order to get to this method a lot of security jumps have to have gone through already
*
* Its not as insecure as first seen... promise :)
*/
private function saveFile()
{
require 'bfFilesystem.php';
if (! $this->_dataObj->filename || ! $this->_dataObj->filecontents) {
bfEncrypt::reply('error', [
'msg' => 'No file name or file contents were provided!',
]);
}
if (file_exists(JPATH_BASE . $this->_dataObj->filename) && ! is_writable(JPATH_BASE . $this->_dataObj->filename)) {
bfEncrypt::reply('error', [
'msg' => 'File not saved - as file is unwritable!',
]);
}
if (! file_exists(dirname(JPATH_BASE . $this->_dataObj->filename))) {
if (! @mkdir(dirname(JPATH_BASE . $this->_dataObj->filename), 0755, true)) {
bfEncrypt::reply('error', [
'msg' => 'File not saved - could not create folder paths!',
]);
}
}
$content = base64_decode($this->_dataObj->filecontents);
if (! $content) {
bfEncrypt::reply('error', [
'msg' => 'File not saved - as no content sent to save into the file!',
]);
}
if (@Bf_Filesystem::_write(JPATH_BASE . $this->_dataObj->filename, $content)) {
bfEncrypt::reply('success', [
'msg' => 'File saved!',
]);
} else {
bfEncrypt::reply('error', [
'msg' => 'No idea why, but file content could not be saved to ' . JPATH_BASE . $this->_dataObj->filename,
]);
}
}
/**
* get the contents of the robots.txt only if it exists in the cache tables.
*/
private function getRobotsFile()
{
$this->_db->setQuery('SELECT id from bf_files WHERE filewithpath = "/robots.txt"');
$id = $this->_db->loadResult();
if (! $id) {
$obj = new stdclass();
$obj->filename = '';
$obj->filemd5 = md5('');
$obj->filewithpath = '';
$obj->filecontents = base64_encode(
'Could not load content for your own security, run a full audit before attempting to edit file content with mySites.guru'
);
$obj->filesize = 0;
$obj->basepath = JPATH_BASE;
$obj->writeable = 0;
bfEncrypt::reply('success', [
'file' => $obj,
]);
}
$this->downloadfile($id);
}
/**
* @param null $file_id
*/
private function downloadfile($file_id = null)
{
if (null === $file_id) {
$file_id = (int) $this->_dataObj->f;
}
$this->_db->setQuery('SELECT filewithpath from bf_files WHERE id = ' . $file_id);
$filename = $this->_db->loadResult();
$filewithpath = JPATH_BASE . $filename;
if (file_exists($filewithpath)) {
$contents = file_get_contents($filewithpath);
$contentsbase64_encode = base64_encode($contents);
$obj = new stdclass();
$obj->filename = $filename;
$obj->filemd5 = md5($contents);
$obj->filewithpath = $filewithpath;
$obj->filecontents = $contentsbase64_encode;
$obj->filesize = filesize($filewithpath);
$obj->basepath = JPATH_BASE;
$obj->writeable = is_writable($filewithpath);
bfEncrypt::reply('success', [
'file' => $obj,
]);
} else {
bfEncrypt::reply('error', [
'msg' => 'File No Longer Exists!',
]);
}
}
private function restorefile()
{
// Require more complex methods for dealing with files
require 'bfFilesystem.php';
// get the cached data on the file
$this->_db->setQuery('SELECT filewithpath FROM bf_files WHERE id = ' . $this->_dataObj->fileid);
$file_to_restore_nopath = $this->_db->loadResult();
$file_to_restore = JPATH_BASE . $file_to_restore_nopath;
$new_file_contents = base64_decode($this->_dataObj->filecontents);
$new_md5 = md5($new_file_contents);
if ($new_md5 !== $this->_dataObj->md5) {
bfEncrypt::reply('failure', 'MD5 Check 1 Failed');
}
$this->_db->setQuery('SELECT hash FROM bf_core_hashes WHERE filewithpath = "' . $file_to_restore_nopath . '"');
$core_md5 = $this->_db->loadResult();
if ($core_md5 !== $this->_dataObj->md5) {
bfEncrypt::reply('failure', 'MD5 Check 2 Failed');
}
$backup = file_get_contents($file_to_restore);
Bf_Filesystem::_write($file_to_restore, $new_file_contents);
if (md5_file($file_to_restore) !== $this->_dataObj->md5) {
Bf_Filesystem::_write($file_to_restore, $backup);
bfEncrypt::reply('failure', 'MD5 Check 3 Failed');
}
$this->_db->setQuery(
"UPDATE bf_files SET suspectcontent = 0 , hashfailed = 0 where filewithpath = '" . $file_to_restore_nopath . "'"
);
$this->_db->execute();
bfEncrypt::reply('success', 'Restored OK');
}
private function checkFTPLayer()
{
$config = Factory::getApplication()->getConfig();
$ftp_pass = $config->get('ftp_pass', '');
$ftp_user = $config->get('ftp_user', '');
$ftp_enable = $config->get('ftp_enable', '');
$ftp_port = $config->get('ftp_port', '');
$ftp_host = $config->get('ftp_host', '');
$ftp_root = $config->get('ftp_root', '');
if ($ftp_pass || $ftp_user || true === $ftp_enable || $ftp_host || $ftp_root || $ftp_port) {
bfEncrypt::reply('success', 1);
} else {
bfEncrypt::reply('success', 0);
}
}
private function disableFTPLayer()
{
$this->_setConfigParam('ftp_pass', '', 'string', true);
$this->_setConfigParam('ftp_user', '', 'string', true);
$this->_setConfigParam('ftp_host', '', 'string', true);
$this->_setConfigParam('ftp_port', '', 'string', true);
$this->_setConfigParam('ftp_root', '', 'string', true);
$this->_setConfigParam('ftp_enable', false, 'string', true);
bfEncrypt::reply('success', 1);
}
private function setFolderPermissions()
{
$fixed = 0;
$errors = 0;
$this->_db->setQuery('SELECT id, folderwithpath from bf_folders WHERE folderinfo = "777"');
$folders = $this->_db->loadObjectList();
foreach ($folders as $folder) {
if (@chmod(JPATH_BASE . $folder->folderwithpath, 0755)) {
++$fixed;
$this->_db->setQuery(
'UPDATE bf_folders SET folderinfo = "755" WHERE id = "' . (int) $folder->id . '" AND folderinfo = "777"'
);
$this->_db->execute();
} else {
++$errors;
}
}
$this->_db->setQuery('SELECT count(*) FROM bf_folders WHERE folderinfo LIKE "%777%"');
$folders_777 = $this->_db->LoadResult();
$res = new stdClass();
$res->errors = $errors;
$res->fixed = $fixed;
$res->leftover = $folders_777;
bfEncrypt::reply('success', $res);
}
/**
* I do some sanity checks then enable .htaccess.
*/
private function setHtaccess()
{
// Require more complex methods for dealing with files
require 'bfFilesystem.php';
// init bfDatabase
// To
$htaccess = JPATH_BASE . \DIRECTORY_SEPARATOR . '.htaccess';
// From
$htaccesstxt = JPATH_BASE . \DIRECTORY_SEPARATOR . 'htaccess.txt';
$res = new stdClass();
if (file_exists($htaccess)) {
$res->result = 'ERROR';
$res->msg = '.htaccess file already exists!';
bfEncrypt::reply(bfReply::SUCCESS, $res);
}
if (! file_exists($htaccesstxt)) {
$res->result = 'ERROR';
$res->msg = 'htaccess.txt file not found, cannot proceed';
bfEncrypt::reply(bfReply::SUCCESS, $res);
}
// Test we are on apache
if (! preg_match('/Apache|LiteSpeed/i', $_SERVER['SERVER_SOFTWARE'])) {
$res->result = 'ERROR';
$res->msg = 'Server reported its not running Apache/LiteSpeed, but is running ' . $_SERVER['SERVER_SOFTWARE'];
bfEncrypt::reply(bfReply::SUCCESS, $res);
}
$didItWork = Bf_Filesystem::_write($htaccess, file_get_contents($htaccesstxt));
if (false == $didItWork) {
$res->result = 'ERROR';
$res->msg = 'Could not copy htaccess.txt to .htaccess';
bfEncrypt::reply(bfReply::SUCCESS, $res);
}
$res->result = 'SUCCESS';
$res->msg = '.htaccess enabled! - Go and test your site!';
bfEncrypt::reply(bfReply::SUCCESS, $res);
}
/**
* I set the new database credentials in /configuration.php after some testing.
*/
private function setDbCredentials()
{
// Require more complex methods for dealing with files
require 'bfFilesystem.php';
$password = $this->_dataObj->p;
$user = $this->_dataObj->u;
$res = $this->testDbCredentials(true);
if ('error' == $res->result) {
bfEncrypt::reply(bfReply::ERROR, $res);
}
$this->_setConfigParam('user', $user, 'string', true);
$this->_setConfigParam('password', $password, 'string', true);
bfEncrypt::reply('success', [
'msg' => 'Config saved!',
'dbs_visible' => 1,
]);
}
/**
* @param bool $internal
*
* @return stdClass
*/
private function testDbCredentials($internal = false)
{
try {
$config = Factory::getApplication()->getConfig();
$pass = $this->_dataObj->p;
$user = $this->_dataObj->u;
$host = $config->get('host', '');
$db = $config->get('db', '');
if (function_exists('mysql_connect')) {
$link = @mysql_connect($host, $user, $pass);
} else {
$link = @mysqli_connect($host, $user, $pass);
}
$msg = new stdClass();
if (! $link) {
if (function_exists('mysql_connect')) {
$msg->msg = trim(mysql_error() . ' Could not connect to mysql server with supplied credentials');
} else {
$msg->msg = trim(mysqli_error() . ' Could not connect to mysql server with supplied credentials');
}
$msg->result = 'error';
if (true === $internal) {
return $msg;
}
bfEncrypt::reply('success', $msg);
}
if (function_exists('mysql_connect')) {
if (! @mysql_select_db($db, $link)) {
$msg->msg = trim(mysql_error() . ' Mysql User exists, but has no access to the database');
$msg->result = 'error';
if (true === $internal) {
return $msg;
}
bfEncrypt::reply('success', $msg);
}
} else {
if (! @mysqli_select_db($link, $db)) {
$msg->msg = trim(mysqli_error() . ' Mysql User exists, but has no access to the database');
$msg->result = 'error';
if (true === $internal) {
return $msg;
}
bfEncrypt::reply('success', $msg);
}
}
$msg->result = 'success';
if (true === $internal) {
return $msg;
}
bfEncrypt::reply('success', $msg);
} catch (Exception $e) {
bfEncrypt::reply('error', 'exception: ' . $e->getMessage());
}
}
private function getUpdatesCount()
{
require 'bfUpdates.php';
$bfUpdates = new bfUpdates($this->_db);
bfEncrypt::reply('success', [
'count' => $bfUpdates->getupdates(true, $this->_dataObj->d),
]);
}
private function getUpdatesDetail()
{
@ob_start();
@set_time_limit(60);
require 'bfUpdates.php';
$bfUpdates = new bfUpdates($this->_db);
$updates = $bfUpdates->getupdates(false, $this->_dataObj->d);
// remove any stray output
echo ' '; // must have something to clean else warning occurs
@ob_clean();
bfEncrypt::reply('success', [
'current_joomla_version' => JVERSION,
'availableUpdates' => $updates['updates'],
'updateSites' => $updates['sites'],
]);
}
/**
* Fix Db Schema version in the db.
*
* @since 20130929
*/
private function fixDbSchema()
{
/** @var DatabaseModel $model */
$model = Factory::getApplication()
->bootComponent('com_installer')
->getMVCFactory()
->createModel('Database', 'Administrator', [
'ignore_request' => true,
]);
$model->fix([211]);
/** @var \Joomla\Component\Joomlaupdate\Administrator\Model\UpdateModel $updateModel */
$updateModel = Factory::getApplication()->bootComponent('com_joomlaupdate')
->getMVCFactory()->createModel('Update', 'Administrator', [
'ignore_request' => true,
]);
$updateModel->purge();
// Refresh versionable assets cache
Factory::getApplication()->flushAssets();
$this->_db->setQuery('select extension_id from #__extensions where name = "files_joomla"');
$filesJoomlaId = $this->_db->loadResult();
$changeSet = new ChangeSet($this->_db, null);
$model->fixSchemaVersion($changeSet, $filesJoomlaId);
foreach ($model->getItems() as $item) {
if ('com_admin' !== $item['extension']->element) {
continue;
}
$version_latest = $item['extension']->version;
$version_current = JVERSION;
}
bfEncrypt::reply('success', [
'version_latest' => $version_latest,
'version_current' => $version_current,
'latest' => $changeSet->getSchema(),
'current' => $model->getSchemaVersion($filesJoomlaId),
'schema_errors' => $changeSet->check(),
]);
}
/**
* Return the DB schema.
*
* @since 20130929
*/
private function getDbSchemaVersion()
{
/** @var DatabaseModel $model */
$model = Factory::getApplication()
->bootComponent('com_installer')
->getMVCFactory()
->createModel('Database', 'Administrator', [
'ignore_request' => true,
]);
foreach ($model->getItems() as $item) {
if ('com_admin' !== $item['extension']->element) {
continue;
}
$version_latest = $item['extension']->version;
$version_current = JVERSION;
}
$changeSet = new ChangeSet($this->_db, null);
$this->_db->setQuery('select extension_id from #__extensions where name = "files_joomla"');
$filesJoomlaId = $this->_db->loadResult();
bfEncrypt::reply('success', [
'version_latest' => $version_latest,
'version_current' => $version_current,
'latest' => $changeSet->getSchema(),
'current' => $model->getSchemaVersion($filesJoomlaId),
'schema_errors' => $changeSet->check(),
]);
}
private function checkGoogleFile()
{
$found = false;
$files = scandir(JPATH_BASE);
foreach ($files as $file) {
if (preg_match('/google.*\.html/', $file)) {
$found = true;
}
}
bfEncrypt::reply('success', [
'found' => $found,
]);
}
/**
* Toggles the @offline property of JConfig in /configuration.php.
*/
private function toggleOnline()
{
if ('true' == $this->_dataObj->s) { // false = set to online, true = set to offline
return $this->_setConfigParam('offline', false, 'bool');
} else {
return $this->_setConfigParam('offline', true, 'bool');
}
}
private function toggleCache()
{
if ('true' == $this->_dataObj->s) {// true means, set to the OK value
return $this->_setConfigParam('caching', 1, 'bool');
} else {
return $this->_setConfigParam('caching', 0, 'bool');
}
}
private function getOfflineStatus()
{
bfEncrypt::reply('success', [
'offline' => Factory::getApplication()->getConfig()->get('offline'),
]);
}
private function getCacheStatus()
{
bfEncrypt::reply('success', [
'caching' => Factory::getApplication()->getConfig()->get('caching'),
]);
}
/**
* Install an extension from Url.
*/
private function doExtensionInstallFromUrl()
{
ob_start();
// Load up as much of Joomla as we need
require 'bfExtensions.php';
$ext = new bfExtensions($this->_dataObj);
$ext->installExtensionFromUrl();
}
private function doExtensionUpgrade()
{
// Load up as much of Joomla as we need
require 'bfExtensions.php';
// Support crappy extensions like OSMap that implement their own license manager via plugins
PluginHelper::importPlugin('system');
// init reply to mysites.guru
$result = [];
$result['messages'] = [];
// which row in the _updates table should we use
$this->_db->setQuery('SELECT update_id from #__updates WHERE extension_id = "' . $this->_dataObj->eid . '"');
$extension_row_id = $this->_db->loadResult();
if (! $extension_row_id) {
bfEncrypt::reply('error', [
'result' => 'Could not find cached update to run, maybe its already been updated, check for updates before trying again',
]);
}
// Do the update
$ext = new bfExtensions();
$result['result'] = $ext->doUpdate($extension_row_id);
// Grab any error messages
$result['messages'] = $this->_app->getMessageQueue();
// translate messages
$lang = Factory::getLanguage();
$lang->load('com_installer', JPATH_ADMINISTRATOR, 'en-GB', true);
$lang->load('lib_joomla', JPATH_ADMINISTRATOR, 'en-GB', true);
if (count($result['messages'])) {
foreach ($result['messages'] as &$msg) {
$msg['message'] = Text::_($msg['message']);
}
}
bfEncrypt::reply('success', [
'result' => $result,
]);
}
private function checkAkeebaOutputDirectory()
{
try {
// Check Akeeba Installed - Prerequisite
if (! file_exists(JPATH_ADMINISTRATOR . '/components/com_akeeba/BackupEngine/Autoloader.php')) {
bfEncrypt::reply('success', [
'paths' => [],
]);
}
if (! defined('AKEEBAENGINE')) {
define('AKEEBAENGINE', 1);
}
if (file_exists(JPATH_BASE . '/libraries/fof40/Autoloader/Autoloader.php')) {
require JPATH_BASE . '/libraries/fof40/Autoloader/Autoloader.php';
} elseif (file_exists(JPATH_BASE . '/libraries/fof30/Autoloader/Autoloader.php')) {
require JPATH_BASE . '/libraries/fof30/Autoloader/Autoloader.php';
}
require JPATH_ADMINISTRATOR . '/components/com_akeeba/BackupEngine/Autoloader.php';
Platform::addPlatform('joomla3x', JPATH_ADMINISTRATOR . '/components/com_akeeba/BackupPlatform/Joomla3x');
if (class_exists('FOF30\Container\Container')) {
$container = Container::getInstance('com_akeeba');
} else {
$container = \FOF40\Container\Container::getInstance('com_akeeba');
}
$profilesModel = $container->factory->model('Profiles')->tmpInstance();
$profiles = $profilesModel->get(true);
$returnData = [];
foreach ($profiles as $profile) {
$config = json_decode(\Akeeba\Engine\Factory::getSecureSettings()->decryptSettings($profile->configuration));
$stockDirs = Platform::getInstance()->get_stock_directories();
$defaultOutputPath = $stockDirs['[DEFAULT_OUTPUT]'];
$dir = $config->akeeba->basic->output_directory;
if ('[DEFAULT_OUTPUT]' === $dir) {
$dir = $defaultOutputPath;
}
$dir = realpath($dir);
$returnData[] = [
'path' => $dir,
'iswritable' => is_writable($dir),
'file_exists' => file_exists($dir),
'config' => $config,
];
}
bfEncrypt::reply('success', [
'paths' => $returnData,
]);
} catch (Exception $e) {
bfEncrypt::reply('error', [
'msg' => $e->getMessage(),
]);
}
}
/**
* return a value from the config.
*/
private function getDebugMode()
{
bfEncrypt::reply('success', [
'debug' => [
'debug' => Factory::getApplication()->getConfig()->get('debug'),
],
]);
}
/**
* set a value to the config.
*/
private function setDebugMode()
{
if ('true' == $this->_dataObj->s) {// true means, set to the OK value
return $this->_setConfigParam('debug', false, 'bool');
} else {
return $this->_setConfigParam('debug', true, 'bool');
}
}
/**
* return a value from the config.
*/
private function getErrorReporting()
{
bfEncrypt::reply('success', [
'error_reporting' => [
'error_reporting' => JFactory::getApplication()->getConfig()->get('error_reporting'),
],
]);
}
/**
* set a value to the config.
*/
private function setErrorReporting()
{
if ('true' == $this->_dataObj->s) {// true means, set to the OK value
return $this->_setConfigParam('error_reporting', 'none', 'string');
} else {
return $this->_setConfigParam('error_reporting', 'maximum', 'string');
}
}
/**
* return the main configuration.php without sensitive information like passwords.
*/
private function getJoomlaLogTmpConfig()
{
$config = Factory::getApplication()->getConfig();
$data = [
'log_path' => $config->get('log_path'),
'tmp_path' => $config->get('tmp_path'),
'base' => JPATH_BASE,
];
bfEncrypt::reply('success', [
'paths' => $data,
]);
}
/**
* Get the User Actions Log.
*
* Joomla 3.9.0 implemented a User Action Log which basically replicates what we used to do So now we load data from
* their log and not ours :-) Saves duplicating our efforts!
*/
private function getActivityLog()
{
if (! class_exists('bfActivitylog')) {
require_once 'bfActivitylog.php';
}
$inst = bfActivitylog::getInstance();
$inst->ensureTableCreated();
$limitstart = (int) $this->_dataObj->ls;
$limit = (int) $this->_dataObj->limit;
if (! $limitstart) {
$limitstart = 0;
}
if (! $limit) {
$limit = '100';
}
// Manipulate the base Uri in the Joomla Stack to provide compatibility with some 3pd extensions like ACL Manager!
try {
$uri = Uri::getInstance();
$reflection = new \ReflectionClass($uri);
$baseProperty = $reflection->getProperty('base');
$baseProperty->setAccessible(true);
$base = $baseProperty->getValue();
$base['prefix'] = $uri->toString(['scheme', 'host']);
$base['path'] = '/';
$baseProperty->setValue($base);
} catch (ReflectionException $e) {
}
/** @var ActionlogsModel $model */
$model = Factory::getApplication()
->bootComponent('com_actionlogs')
->getMVCFactory()
->createModel('Actionlogs', 'Administrator', [
'ignore_request' => true,
]);
// Set the Start and Limit
$model->setState('list.start', $limitstart);
$model->setState('list.limit', $limit);
$model->setState('list.ordering', 'a.id');
$model->setState('list.direction', 'DESC');
$rows = $model->getItems();
// Load all language files needed
ActionlogsHelper::loadActionLogPluginsLanguage();
$lang = Factory::getLanguage();
$lang->load('com_privacy', JPATH_ADMINISTRATOR, null, false, true);
$lang->load('plg_system_actionlogs', JPATH_ADMINISTRATOR, null, false, true);
$lang->load('plg_system_privacyconsent', JPATH_ADMINISTRATOR, null, false, true);
// manipulate data to push to mysites.guru
foreach ($rows as $row) {
$row->what = ActionlogsHelper::getHumanReadableLogMessage($row);
$row->ip = $row->ip_address;
$row->when = $row->log_date;
$row->who_id = $row->user_id;
$row->source = 'core_user_action_log';
}
bfEncrypt::reply('success', $rows ?: []);
}
/**
* enable/disable and get status of our plugin.
*/
private function getBFPluginStatus()
{
if (property_exists($this->_dataObj, 'action')) {
switch ($this->_dataObj->action) {
case 'enable':
$this->_db->setQuery("UPDATE `#__extensions` set enabled = 1 WHERE `name` = 'PLG_ACTIONLOG_JOOMLA'");
$this->_db->execute();
$this->_db->setQuery("UPDATE `#__extensions` set enabled = 1 WHERE `name` = 'PLG_SYSTEM_ACTIONLOGS'");
$this->_db->execute();
$this->_db->setQuery('UPDATE `#__extensions` SET enabled = 1 WHERE element = "bfnetwork"');
$this->_db->execute();
break;
case 'disable':
$this->_db->setQuery('UPDATE `#__extensions` SET enabled = 0 WHERE element = "bfnetwork"');
$this->_db->execute();
break;
}
}
$this->_db->setQuery('SELECT enabled FROM #__extensions WHERE element = "bfnetwork"');
$result = $this->_db->loadResult();
bfEncrypt::reply('success', $result);
}
/**
* get the list of users that have a 32 char password hash - e.g md5.
*/
private function getMD5PasswordUsers()
{
$this->_db->setQuery('SELECT id, username, name, password FROM #__users WHERE CHAR_LENGTH(password) = 32');
$result = $this->_db->loadObjectList();
bfEncrypt::reply('success', $result);
}
/**
* Check the session gc plugin in Joomla 3.
*/
private function setSessionGCStatus()
{
if ('true' == $this->_dataObj->s) {// true means, set to the OK value
$this->_db->setQuery("update #__extensions set enabled = 1 where name = 'plg_system_sessiongc'");
} else {
$this->_db->setQuery("update #__extensions set enabled = 0 where name = 'plg_system_sessiongc'");
}
$this->_db->execute();
bfEncrypt::reply('success', [
'status' => $this->getSessionGCStatus(),
]);
}
/**
* Check the session gc plugin in Joomla 3.
*/
private function getSessionGCStatus()
{
$res = 2;
// Session GC
$this->_db->setQuery("select count(*) from #__extensions where name = 'plg_system_sessiongc'");
$hasSessionGcPlugin = $this->_db->LoadResult();
if ($hasSessionGcPlugin) {
$this->_db->setQuery("select enabled from #__extensions where name = 'plg_system_sessiongc'");
$res = $this->_db->LoadResult();
}
bfEncrypt::reply('success', [
'status' => $res,
]);
}
/**
* Get the 2FA plugins.
*/
private function enable2FAPlugins()
{
if ('true' == $this->_dataObj->s) {// true means, set to the OK value
$this->_db->setQuery("UPDATE `#__extensions` SET enabled = 1 WHERE `folder` = 'twofactorauth'");
} else {
$this->_db->setQuery("UPDATE `#__extensions` SET enabled = 0 WHERE `folder` = 'twofactorauth'");
}
$this->_db->LoadResult();
$this->get2FAPlugins();
}
/**
* Get the 2FA plugins.
*/
private function get2FAPlugins()
{
$this->_db->setQuery("SELECT * FROM `#__extensions` WHERE `folder` = 'twofactorauth'");
$res = $this->_db->loadObjectList();
bfEncrypt::reply('success', $res);
}
/**
* set params from com_config without using a helper.
*/
private function setAdminFilterFixed()
{
$this->_db->setQuery("SELECT `params` from #__extensions WHERE `element` = 'com_config'");
$params = json_decode($this->_db->LoadResult());
if ('true' == $this->_dataObj->s) {// true means, set to the OK value
$params->filters->{7}->filter_type = 'BL';
} else {
$params->filters->{7}->filter_type = 'NONE';
}
$this->_db->setQuery(sprintf("UPDATE #__extensions set `params` = '%s' WHERE `element` = 'com_config'", json_encode($params)));
$this->_db->execute();
return $this->getAdminFilterFixed();
}
/**
* Load params from com_config without using a helper.
*/
private function getAdminFilterFixed()
{
$this->_db->setQuery("SELECT `params` from #__extensions WHERE element = 'com_config'");
$params = json_decode($this->_db->LoadResult());
bfEncrypt::reply('success', $params->filters->{7});
}
/**
* set params from com_config without using a helper.
*/
private function setUserFilterFixed()
{
$this->_db->setQuery("SELECT `params` from #__extensions WHERE `element` = 'com_config'");
$params = json_decode($this->_db->LoadResult());
$params->filters->{1}->filter_type = 'NH';
$params->filters->{2}->filter_type = 'NH';
$params->filters->{9}->filter_type = 'NH';
$this->_db->setQuery(sprintf("UPDATE #__extensions set `params` = '%s' WHERE `element` = 'com_config'", json_encode($params)));
$this->_db->execute();
return $this->getUserFilterFixed();
}
/**
* Load params from com_config without using a helper.
*/
private function getUserFilterFixed()
{
$this->_db->setQuery("SELECT `params` from #__extensions WHERE element = 'com_config'");
$params = json_decode($this->_db->LoadResult());
$ret = 1;
if ('NH' != $params->filters->{1}->filter_type) {
$ret = 0;
}
if ('NH' != $params->filters->{2}->filter_type) {
$ret = 0;
}
if ('NH' != $params->filters->{9}->filter_type) {
$ret = 0;
}
bfEncrypt::reply('success', $ret);
}
/**
* set params from com_config without using a helper.
*/
private function setPlaintextpasswords()
{
$this->_db->setQuery("SELECT `params` from #__extensions WHERE `element` = 'com_users'");
$params = json_decode($this->_db->LoadResult());
if ('true' == $this->_dataObj->s) {// true means, set to the OK value
$params->sendpassword = '0';
} else {
$params->sendpassword = '1';
}
$this->_db->setQuery(sprintf("UPDATE #__extensions set `params` = '%s' WHERE `element` = 'com_users'", json_encode($params)));
$this->_db->execute();
$this->getPlaintextpasswords();
}
/**
* Load params from com_config without using a helper.
*/
private function getPlaintextpasswords()
{
$this->_db->setQuery("SELECT `params` from #__extensions WHERE element = 'com_users'");
$params = json_decode($this->_db->LoadResult());
bfEncrypt::reply('success', [
'sendpassword' => $params->sendpassword,
]);
}
/**
* set params from com_content without using a helper.
*/
private function setMailtofrienddisabled()
{
$this->_db->setQuery("SELECT `params` from #__extensions WHERE `element` = 'com_content'");
$params = json_decode($this->_db->LoadResult());
if ('true' == $this->_dataObj->s) {// true means, set to the OK value
$params->show_email_icon = '0';
} else {
$params->show_email_icon = '1';
}
$this->_db->setQuery(sprintf("UPDATE #__extensions set `params` = '%s' WHERE `element` = 'com_content'", json_encode($params)));
$this->_db->execute();
$this->getMailtofrienddisabled();
}
/**
* Load params from com_content without using a helper.
*/
private function getMailtofrienddisabled()
{
$this->_db->setQuery("SELECT `params` from #__extensions WHERE element = 'com_content'");
$params = json_decode($this->_db->LoadResult());
bfEncrypt::reply('success', [
'show_email_icon' => $params->show_email_icon,
]);
}
/**
* set params from com_templates without using a helper.
*/
private function setTemplatePositionDisplay()
{
$this->_db->setQuery("SELECT `params` from #__extensions WHERE `element` = 'com_templates'");
$params = json_decode($this->_db->LoadResult());
if ('true' == $this->_dataObj->s) {// true means, set to the OK value
$params->template_positions_display = '0';
} else {
$params->template_positions_display = '1';
}
$this->_db->setQuery(sprintf("UPDATE #__extensions set `params` = '%s' WHERE `element` = 'com_templates'", json_encode($params)));
$this->_db->execute();
$this->getTemplatePositionDisplay();
}
/**
* Load params from com_templates without using a helper.
*/
private function getTemplatePositionDisplay()
{
$this->_db->setQuery("SELECT `params` from #__extensions WHERE element = 'com_templates'");
$params = json_decode($this->_db->LoadResult());
bfEncrypt::reply('success', [
'template_positions_display' => $params->template_positions_display,
]);
}
/**
* Get the configuration of the google recaptcha plugin and global config.
*/
private function getCaptchaConfig()
{
$config = Factory::getApplication()->getConfig();
$this->_db->setQuery("SELECT enabled FROM #__extensions WHERE name ='plg_captcha_recaptcha'");
$enabled = $this->_db->loadResult();
$this->_db->setQuery("SELECT params FROM #__extensions WHERE name ='plg_captcha_recaptcha'");
$keyed = $this->_db->loadResult();
bfEncrypt::reply('success', [
'enabled' => $enabled,
'configured' => $config->get('captcha', ''),
'keys' => json_decode($keyed),
]);
}
/**
* Set the configuration of the google recaptcha plugin and global config.
*/
private function setCaptchaConfig()
{
$this->_db->setQuery(sprintf("UPDATE #__extensions
SET
enabled = 1,
params = '{\"version\":\"2.0\",\"public_key\":\"%s\",\"private_key\":\"%s\",\"theme\":\"clean\",\"theme2\":\"light\",\"size\":\"normal\"}'
WHERE name ='plg_captcha_recaptcha'", $this->_dataObj->site_key, $this->_dataObj->secret_key));
$this->_db->execute();
$this->_setConfigParam('captcha', 'recaptcha', 'string');
}
/**
* get the list of ACL Groups.
*/
private function getGroups()
{
$this->_db->setQuery('select id, title from #__usergroups');
bfEncrypt::reply('success', [
'groups' => $this->_db->loadObjectList(),
]);
}
/**
* get the list of super admins.
*/
private function getSuperAdmins()
{
$this->_db->setQuery('select id, name, username from #__users as u
left join #__user_usergroup_map as m on u.id = m.user_id
where m.group_id = ' . (int) $this->_dataObj->groupid);
bfEncrypt::reply('success', [
'users' => $this->_db->loadObjectList(),
]);
}
/**
* 110 Identify Files That Existed In Last Audit, And Modified Before This Audit.
*/
private function getModifiedfilessincelastaudit()
{
$limitstart = (int) $this->_dataObj->ls;
$sort = $this->_dataObj->s;
if (! $sort) {
$sort = 'filewithpath';
}
if (! in_array($sort, ['filewithpath', 'filemtime'])) {
exit('Invalid Sort');
}
if ('filemtime' === $sort) {
$sort = 'filemtime DESC';
}
$limit = (int) $this->_dataObj->limit;
// Set the query
$this->_db->setQuery(
'SELECT new.id, new.iscorefile, new.filewithpath, new.filemtime, new.fileperms, new.`size`, new.iscorefile from bf_files as new
LEFT JOIN bf_files_last as old ON old.filewithpath = new.filewithpath
WHERE old.currenthash != new.currenthash
ORDER BY ' . $sort . '
LIMIT ' . $limitstart . ', ' . $limit
);
// Get an object list of files
$files = $this->_db->loadObjectList();
// see how many files there are in total without a limit
$sql = 'select count(*) from `bf_files` as new
LEFT JOIN bf_files_last as old ON old.filewithpath = new.filewithpath
WHERE old.currenthash != new.currenthash';
$this->_db->setQuery($sql);
$count = $this->_db->loadResult();
// Only show files that still exist on the hard drive
$existingFiles = [];
foreach ($files as $k => $file) {
if (file_exists(JPATH_BASE . $file->filewithpath)) {
$existingFiles[] = $file;
} else {
$this->_db->setQuery(sprintf('DELETE FROM bf_files WHERE filewithpath = "%s"', $file->filewithpath));
$this->_db->execute();
--$count;
}
}
// return an encrypted reply
bfEncrypt::reply('success', [
'files' => $existingFiles,
'total' => $count,
]);
}
/**
* Detect the JCE compromise: rogue editor profiles + malicious files in the known
* JCE drop locations. Upgrading JCE is handled by the mySites.guru mass update tools,
* not from this panel.
*/
private function getJceRogueDetection()
{
require_once 'bfJce.php';
$jce = new bfJce($this->_db);
// bf_files only exists once a full audit has run; before that the table is
// absent and this count would throw "table not found". Guard it so JCE
// detection still works on a freshly connected, never-audited site.
$hackedFiles = 0;
try {
$this->_db->setQuery('SELECT count(*) FROM bf_files WHERE hacked = 1');
$hackedFiles = (int) $this->_db->LoadResult();
} catch (\Throwable $e) {
$hackedFiles = 0;
}
bfEncrypt::reply('success', [
'profiles' => $jce->findRogueProfiles(),
'files' => $jce->findMaliciousFiles(),
'jce_version' => $this->getInstalledJceVersion(),
'hacked_files' => $hackedFiles,
]);
}
/**
* Delete the rogue JCE profiles from the database.
*/
private function removeJceRogueProfiles()
{
require_once 'bfJce.php';
$result = (new bfJce($this->_db))->removeRogueProfiles();
bfEncrypt::reply('success', $result);
}
/**
* Delete the malicious files found in the known JCE drop locations.
*/
private function removeJceMaliciousFiles()
{
require_once 'bfJce.php';
$result = (new bfJce($this->_db))->removeMaliciousFiles();
bfEncrypt::reply('success', $result);
}
/**
* Detect the rogue Super Admin accounts planted by the SP Page Builder compromise:
* Super Users carrying a @secure.local email.
*/
private function getRogueAdmins()
{
require_once 'bfRogueAdmin.php';
bfEncrypt::reply('success', [
'admins' => (new bfRogueAdmin($this->_db))->findRogueAdmins(),
]);
}
/**
* Delete the rogue Super Admin accounts found above.
*/
private function removeRogueAdmins()
{
require_once 'bfRogueAdmin.php';
$result = (new bfRogueAdmin($this->_db))->removeRogueAdmins();
bfEncrypt::reply('success', $result);
}
/**
* The IP whose Admin Tools block state we should manage. Defaults to the IP this request
* actually arrived from (the mySites.guru worker/proxy egress IP), so it is correct even when
* the published IP has drifted or the fallback proxy is in use. Honours an explicit ip in the
* payload if one was passed.
*/
private function resolveAdmintoolsIp(): string
{
$explicit = property_exists($this->_dataObj, 'ip') ? trim((string) $this->_dataObj->ip) : '';
if ($explicit !== '') {
return $explicit;
}
return str_replace('::ffff:', '', (string) (@getenv('HTTP_X_FORWARDED_FOR') ?: @$_SERVER['REMOTE_ADDR']));
}
/**
* Report whether Admin Tools is installed and whether our IP is currently blocked by it.
*/
private function getAdmintoolsIpStatus()
{
require_once 'bfAdmintools.php';
$helper = new bfAdmintools($this->_db);
$ip = $this->resolveAdmintoolsIp();
$installed = $helper->isInstalled();
bfEncrypt::reply('success', [
'installed' => $installed,
'ip' => $ip,
'blocked' => $installed && $helper->isIpBlocked($ip),
]);
}
/**
* Unblock our IP in Admin Tools (delete from all block tables) and add it to the permanent
* allow-list so it is never auto-banned again.
*/
private function unblockAdmintoolsIp()
{
require_once 'bfAdmintools.php';
$helper = new bfAdmintools($this->_db);
$ip = $this->resolveAdmintoolsIp();
if (! $helper->isInstalled()) {
bfEncrypt::reply('error', 'Admin Tools is not installed on this site.');
}
if ($ip === '') {
bfEncrypt::reply('error', 'Could not determine the IP address to unblock.');
}
$result = $helper->unblockIp($ip);
$result['ip'] = $ip;
$result['blocked'] = $helper->isIpBlocked($ip);
bfEncrypt::reply('success', $result);
}
/**
* The installed JCE version from its manifest, or null.
*/
private function getInstalledJceVersion()
{
$versionFile = JPATH_BASE . '/administrator/components/com_jce/jce.xml';
if (is_file($versionFile)) {
$xml = (string) @file_get_contents($versionFile);
if (preg_match('/<version>(.*?)<\/version>/', $xml, $m)) {
return $m[1];
}
}
return;
}
/**
* Activate realtime alerting.
*/
private function setRealtimeActivate()
{
$data = [
'until' => $this->_dataObj->until,
'endpoint' => $this->_dataObj->endpoint,
];
bfEncrypt::reply('success', [
'file_exists' => file_put_contents(__DIR__ . '/tmp/realtime.php', json_encode($data)),
]);
}
public function getSendUpdateEmails()
{
$sql = 'select count(*) from #__extensions where type = "plugin" and element = "updatenotification" and enabled = 1';
$this->_db->setQuery($sql);
bfEncrypt::reply('success', $this->_db->LoadResult());
}
public function setSendUpdateEmails()
{
$state = $this->_dataObj->s==='true' ? 0 : 1;
$sql = 'UPDATE #__extensions SET enabled = ' . $state . ' WHERE type = "plugin" and element = "updatenotification"';
$this->_db->setQuery($sql);
$this->_db->execute();
$this->getSendUpdateEmails();
}
public function getLockedTasks()
{
// #__scheduler_tasks exists in Joomla 4.1.0+
if (version_compare(JVERSION, '4.1.0', '<')) {
bfEncrypt::reply('success', [
'total' => 0,
'tasks' => [],
]);
return;
}
$this->_db->setQuery(
'SELECT id, title, type, locked, last_execution, next_execution, last_exit_code
FROM #__scheduler_tasks
WHERE locked IS NOT NULL
ORDER BY locked ASC'
);
$tasks = $this->_db->loadObjectList();
bfEncrypt::reply('success', [
'total' => count($tasks),
'tasks' => $tasks,
]);
}
public function unlockLockedTask()
{
if (version_compare(JVERSION, '4.1.0', '<')) {
bfEncrypt::reply('error', [
'message' => 'Task Scheduler not available before Joomla 4.1.0',
]);
return;
}
$taskId = (int) ($this->_dataObj->task_id ?? 0);
if ($taskId <= 0) {
bfEncrypt::reply('error', [
'message' => 'Invalid task ID',
]);
return;
}
$query = $this->_db->getQuery(true)
->update('#__scheduler_tasks')
->set($this->_db->quoteName('locked') . ' = NULL')
->where($this->_db->quoteName('id') . ' = ' . $taskId)
->where($this->_db->quoteName('locked') . ' IS NOT NULL');
$this->_db->setQuery($query);
$this->_db->execute();
bfEncrypt::reply('success', [
'unlocked' => $this->_db->getAffectedRows(),
'task_id' => $taskId,
]);
}
public function getLogEverything()
{
bfEncrypt::reply('success', [
'enabled' => (int) Factory::getApplication()->getConfig()->get('log_everything', '0'),
]);
}
public function setLogEverything()
{
if ('true' == $this->_dataObj->s) {// true means, set to the OK value
return $this->_setConfigParam('log_everything', 0, 'int');
} else {
return $this->_setConfigParam('log_everything', 1, 'int');
}
}
public function getLogDeprecated()
{
bfEncrypt::reply('success', [
'enabled' => (int) Factory::getApplication()->getConfig()->get('log_deprecated', '0'),
]);
}
public function setLogDeprecated()
{
if ('true' == $this->_dataObj->s) {// true means, set to the OK value
return $this->_setConfigParam('log_deprecated', 0, 'int');
} else {
return $this->_setConfigParam('log_deprecated', 1, 'int');
}
}
/**
* 144 - Get list of inactive users (180 days threshold)
*/
private function getInactiveUsers()
{
$limitstart = (int) ($this->_dataObj->ls ?? 0);
$limit = (int) ($this->_dataObj->limit ?? 100);
$sql = "SELECT id, name, username, email, registerDate, lastvisitDate, block
FROM #__users
WHERE lastvisitDate IS NULL
OR lastvisitDate < DATE_SUB(NOW(), INTERVAL 180 DAY)
ORDER BY lastvisitDate ASC, registerDate ASC
LIMIT {$limitstart}, {$limit}";
$this->_db->setQuery($sql);
$users = $this->_db->loadObjectList();
$this->_db->setQuery(
"SELECT COUNT(*) FROM #__users
WHERE lastvisitDate IS NULL
OR lastvisitDate < DATE_SUB(NOW(), INTERVAL 180 DAY)"
);
$total = (int) $this->_db->loadResult();
bfEncrypt::reply('success', [
'users' => $users,
'total' => $total,
]);
}
private function getSuperUsersGroupId()
{
$this->_db->setQuery("SELECT id FROM #__usergroups WHERE title = 'Super Users' OR title = 'Super Utilisateur' LIMIT 1");
return (int) $this->_db->loadResult();
}
private function isSuperAdmin($userId)
{
$superGroupId = $this->getSuperUsersGroupId();
if (! $superGroupId) {
return false;
}
$this->_db->setQuery(sprintf(
"SELECT COUNT(*) FROM #__user_usergroup_map WHERE user_id = %d AND group_id = %d",
(int) $userId,
$superGroupId
));
return $this->_db->loadResult() > 0;
}
private function blockUser()
{
$userId = (int) ($this->_dataObj->user_id ?? 0);
if (! $userId) {
bfEncrypt::reply('error', 'No user ID provided');
}
if ($this->isSuperAdmin($userId)) {
bfEncrypt::reply('error', 'Cannot block super admin users');
}
$this->_db->setQuery(sprintf("UPDATE #__users SET block = 1 WHERE id = %d", $userId));
$result = $this->_db->execute();
bfEncrypt::reply('success', [
'blocked' => $result ? 1 : 0,
'user_id' => $userId,
]);
}
private function deleteUser()
{
$userId = (int) ($this->_dataObj->user_id ?? 0);
if (! $userId) {
bfEncrypt::reply('error', 'No user ID provided');
}
if ($this->isSuperAdmin($userId)) {
bfEncrypt::reply('error', 'Cannot delete super admin users');
}
// Use Joomla's User model for proper cleanup (user_profiles, user_notes, sessions, events)
$user = Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById($userId);
if (! $user || ! $user->id) {
bfEncrypt::reply('error', 'User not found');
}
if (! $user->delete()) {
bfEncrypt::reply('error', 'Failed to delete user: ' . $user->getError());
}
bfEncrypt::reply('success', [
'deleted' => 1,
'user_id' => $userId,
]);
}
private function blockAllUsers()
{
$userIds = $this->_dataObj->user_ids ?? [];
if (! is_array($userIds) || empty($userIds)) {
bfEncrypt::reply('error', 'No user IDs provided');
}
$blocked = 0;
$skipped = 0;
foreach ($userIds as $userId) {
$userId = (int) $userId;
if ($userId <= 0) {
continue;
}
if ($this->isSuperAdmin($userId)) {
$skipped++;
continue;
}
$this->_db->setQuery(sprintf("UPDATE #__users SET block = 1 WHERE id = %d", $userId));
$this->_db->execute();
$blocked++;
}
bfEncrypt::reply('success', [
'blocked' => $blocked,
'skipped' => $skipped,
]);
}
private function deleteAllUsers()
{
$userIds = $this->_dataObj->user_ids ?? [];
if (! is_array($userIds) || empty($userIds)) {
bfEncrypt::reply('error', 'No user IDs provided');
}
$userFactory = Factory::getContainer()->get(UserFactoryInterface::class);
$deleted = 0;
$skipped = 0;
$errors = 0;
foreach ($userIds as $userId) {
$userId = (int) $userId;
if ($userId <= 0) {
continue;
}
if ($this->isSuperAdmin($userId)) {
$skipped++;
continue;
}
// Use Joomla's User model for proper cleanup (user_profiles, user_notes, sessions, events)
$user = $userFactory->loadUserById($userId);
if (! $user || ! $user->id) {
continue;
}
if ($user->delete()) {
$deleted++;
} else {
$errors++;
}
}
bfEncrypt::reply('success', [
'deleted' => $deleted,
'skipped' => $skipped,
'errors' => $errors,
]);
}
/**
* 149 - Get list of unactivated users (pending account activation).
*/
private function getUnactivatedUsers()
{
$limitstart = isset($this->_dataObj->ls) ? (int) $this->_dataObj->ls : 0;
$limit = isset($this->_dataObj->limit) ? (int) $this->_dataObj->limit : 100;
// Count total
$this->_db->setQuery(
"SELECT COUNT(*) FROM #__users
WHERE activation != ''
AND activation IS NOT NULL
AND block = 0"
);
$total = (int) $this->_db->loadResult();
// Get unactivated users
$sql = sprintf(
"SELECT u.id, u.name, u.username, u.email, u.registerDate, u.activation
FROM #__users u
WHERE u.activation != ''
AND u.activation IS NOT NULL
AND u.block = 0
ORDER BY u.registerDate DESC
LIMIT %d, %d",
$limitstart,
$limit
);
$this->_db->setQuery($sql);
$users = $this->_db->loadObjectList();
if ($users === null) {
$users = [];
}
bfEncrypt::reply('success', [
'users' => $users,
'total' => $total,
]);
}
/**
* 150 - Activate a single user by clearing their activation token.
*/
private function activateUser()
{
$userId = isset($this->_dataObj->user_id) ? (int) $this->_dataObj->user_id : 0;
if ($userId <= 0) {
bfEncrypt::reply('error', 'No user ID provided');
}
// Clear the activation token to activate the user
$this->_db->setQuery("UPDATE #__users SET activation = '' WHERE id = " . $userId);
$this->_db->execute();
bfEncrypt::reply('success', [
'userId' => $userId,
'activated' => true,
]);
}
/**
* 151 - Delete a single unactivated user.
*/
private function deleteUnactivatedUser()
{
$userId = isset($this->_dataObj->user_id) ? (int) $this->_dataObj->user_id : 0;
if ($userId <= 0) {
bfEncrypt::reply('error', 'No user ID provided');
}
// Verify user is unactivated
$this->_db->setQuery("SELECT activation FROM #__users WHERE id = " . $userId);
$activation = $this->_db->loadResult();
if (empty($activation)) {
bfEncrypt::reply('error', 'User is already activated or does not exist');
}
if ($this->isSuperAdmin($userId)) {
bfEncrypt::reply('error', 'Cannot delete super admin users');
}
$userFactory = Factory::getContainer()->get(UserFactoryInterface::class);
$user = $userFactory->loadUserById($userId);
if (! $user || ! $user->id) {
bfEncrypt::reply('error', 'User not found');
}
if ($user->delete()) {
bfEncrypt::reply('success', [
'userId' => $userId,
'deleted' => true,
]);
} else {
bfEncrypt::reply('error', 'Failed to delete user');
}
}
/**
* 152 - Activate all unactivated users.
*/
private function activateAllUsers()
{
$userIds = isset($this->_dataObj->user_ids) ? $this->_dataObj->user_ids : [];
if (! is_array($userIds) || empty($userIds)) {
bfEncrypt::reply('error', 'No user IDs provided');
}
$activated = 0;
foreach ($userIds as $userId) {
$userId = (int) $userId;
if ($userId <= 0) {
continue;
}
$this->_db->setQuery("UPDATE #__users SET activation = '' WHERE id = " . $userId);
$this->_db->execute();
$activated++;
}
bfEncrypt::reply('success', [
'activated' => $activated,
]);
}
/**
* 153 - Delete all unactivated users.
*/
private function deleteAllUnactivatedUsers()
{
$userIds = isset($this->_dataObj->user_ids) ? $this->_dataObj->user_ids : [];
if (! is_array($userIds) || empty($userIds)) {
bfEncrypt::reply('error', 'No user IDs provided');
}
$userFactory = Factory::getContainer()->get(UserFactoryInterface::class);
$deleted = 0;
$skipped = 0;
foreach ($userIds as $userId) {
$userId = (int) $userId;
if ($userId <= 0) {
continue;
}
if ($this->isSuperAdmin($userId)) {
$skipped++;
continue;
}
// Verify user is unactivated
$this->_db->setQuery("SELECT activation FROM #__users WHERE id = " . $userId);
$activation = $this->_db->loadResult();
if (empty($activation)) {
$skipped++;
continue;
}
$user = $userFactory->loadUserById($userId);
if (! $user || ! $user->id) {
continue;
}
if ($user->delete()) {
$deleted++;
}
}
bfEncrypt::reply('success', [
'deleted' => $deleted,
'skipped' => $skipped,
]);
}
/**
* 154 - Resend activation email to a user.
*/
private function resendActivation()
{
$userId = isset($this->_dataObj->user_id) ? (int) $this->_dataObj->user_id : 0;
if ($userId <= 0) {
bfEncrypt::reply('error', 'No user ID provided');
}
$userFactory = Factory::getContainer()->get(UserFactoryInterface::class);
$user = $userFactory->loadUserById($userId);
if (! $user || ! $user->id) {
bfEncrypt::reply('error', 'User not found');
}
// Generate a new activation token
$token = UserHelper::genRandomPassword(32);
$hashedToken = UserHelper::hashPassword($token);
// Update the activation token
$this->_db->setQuery("UPDATE #__users SET activation = " . $this->_db->quote($hashedToken) . " WHERE id = " . $userId);
$this->_db->execute();
// Get site configuration
$config = Factory::getApplication()->getConfig();
$sitename = $config->get('sitename');
$mailfrom = $config->get('mailfrom');
$fromname = $config->get('fromname');
// Build activation URL
$activationUrl = Uri::root() . 'index.php?option=com_users&task=registration.activate&token=' . $token;
// Send email
$mailer = Factory::getMailer();
$mailer->setSender([$mailfrom, $fromname]);
$mailer->addRecipient($user->email);
$mailer->setSubject(sprintf('Account Activation - %s', $sitename));
$mailer->setBody(sprintf(
"Hello %s,\n\nPlease click the following link to activate your account:\n\n%s\n\nThank you.",
$user->name,
$activationUrl
));
if ($mailer->Send()) {
bfEncrypt::reply('success', [
'userId' => $userId,
'email_sent' => true,
]);
} else {
bfEncrypt::reply('error', 'Failed to send activation email');
}
}
private function getBlockedUsers()
{
$page = isset($this->_dataObj->page) ? (int) $this->_dataObj->page : 1;
$perPage = isset($this->_dataObj->per_page) ? (int) $this->_dataObj->per_page : 50;
$offset = ($page - 1) * $perPage;
// Get total count
$this->_db->setQuery('SELECT COUNT(*) FROM #__users WHERE block = 1');
$total = (int) $this->_db->loadResult();
// Get blocked users
$sql = sprintf(
'SELECT id, name, username, email, registerDate, lastvisitDate, block
FROM #__users
WHERE block = 1
ORDER BY lastvisitDate DESC
LIMIT %d OFFSET %d',
$perPage,
$offset
);
$this->_db->setQuery($sql);
$users = $this->_db->loadObjectList();
// Check if users are super admins (to protect them from unblock actions)
foreach ($users as $user) {
$user->is_super_admin = $this->isSuperAdmin((int) $user->id);
}
bfEncrypt::reply('success', [
'users' => $users,
'total' => $total,
'page' => $page,
'per_page' => $perPage,
'total_pages' => (int) ceil($total / $perPage),
]);
}
private function unblockUser()
{
$userId = isset($this->_dataObj->user_id) ? (int) $this->_dataObj->user_id : 0;
if ($userId <= 0) {
bfEncrypt::reply('error', 'No user ID provided');
}
// Check if user exists and is blocked
$this->_db->setQuery(sprintf('SELECT id, block FROM #__users WHERE id = %d', $userId));
$user = $this->_db->loadObject();
if (! $user) {
bfEncrypt::reply('error', 'User not found');
}
if ((int) $user->block !== 1) {
bfEncrypt::reply('error', 'User is not blocked');
}
// Unblock the user
$this->_db->setQuery(sprintf('UPDATE #__users SET block = 0 WHERE id = %d', $userId));
$this->_db->execute();
bfEncrypt::reply('success', [
'userId' => $userId,
'unblocked' => true,
]);
}
private function unblockAllUsers()
{
$userIds = isset($this->_dataObj->user_ids) ? $this->_dataObj->user_ids : [];
if (! is_array($userIds) || empty($userIds)) {
bfEncrypt::reply('error', 'No user IDs provided');
}
$unblocked = 0;
$skipped = 0;
foreach ($userIds as $userId) {
$userId = (int) $userId;
if ($userId <= 0) {
continue;
}
if ($this->isSuperAdmin($userId)) {
$skipped++;
continue;
}
$this->_db->setQuery(sprintf('UPDATE #__users SET block = 0 WHERE id = %d AND block = 1', $userId));
$this->_db->execute();
if ($this->_db->getAffectedRows() > 0) {
$unblocked++;
}
}
bfEncrypt::reply('success', [
'unblocked' => $unblocked,
'skipped' => $skipped,
]);
}
}
try {
// init this class
$securityController = new bfTools($dataObj);
// Run the tool method
$securityController->run();
} catch (PrepareStatementFailureException $exception) {
echo $exception->getMessage();
} catch (Exception $exception) {
echo $exception->getMessage();
}