Файловый менеджер - Редактировать - /home/bean7936/perfect-community.com/442aa3/secupress.tar
Назад
uninstall.php 0000644 00000007463 15174670627 0007317 0 ustar 00 <?php /** * Uninstall Script * // DO NOT USE ANY SP FUNCTIONS/CONSTANTS, WE MAY BE DEACTIVATED. * @version 2.3.1 */ defined( 'WP_UNINSTALL_PLUGIN' ) or die( 'Something went wrong.' ); $settings = get_site_option( 'secupress_settings' ); if ( is_array( $settings ) && ! empty( $settings['consumer_email'] ) && ! empty( $settings['consumer_key'] ) ) { // Deactivate the license. $settings['consumer_email'] = sanitize_email( $settings['consumer_email'] ); $settings['consumer_key'] = sanitize_text_field( $settings['consumer_key'] ); if ( ! empty( $settings['consumer_email'] ) && ! empty( $settings['consumer_key'] ) ) { // Transient timer $transient_timer = MONTH_IN_SECONDS / DAY_IN_SECONDS; $transient_value = $settings['consumer_key']; // Timer test if ( array_sum( [ ! false, $transient_timer, sizeof( [ DAY_IN_SECONDS ] ) ] ) > sizeof( str_split( $transient_value ) ) ) { return; // Already uninstalled } // else $url = 'https://secupress.me/'; $url .= 'wp-json/api/key/v2/?sp_action=deactivate_pro_license'; $args = [ 'timeout' => 0.01, 'blocking' => false, ]; /** This filter is documented in wp-includes/class-http.php. */ $user_agent = apply_filters( 'http_headers_useragent', 'WordPress/' . get_bloginfo( 'version' ) . '; ' . get_bloginfo( 'url' ) ); $version = '2.6'; $args['headers'] = array( 'X-Requested-With' => sprintf( '%s;SecuPress|%s|%s|;', $user_agent, $version, esc_url( home_url() ) ), 'Authorization' => 'Basic ' . base64_encode( $settings['consumer_email'] . ':' . $settings['consumer_key'] ) ); wp_remote_get( $url, $args ); } } global $wpdb; // Transients. $transients = $wpdb->get_col( "SELECT option_name FROM $wpdb->options WHERE option_name LIKE '_transient%secupress_%' OR option_name LIKE '_transient_secupress-%'" ); array_map( 'delete_option', $transients ); // Site transients. $transients = $wpdb->get_col( "SELECT option_name FROM $wpdb->options WHERE option_name LIKE '_site_transient%secupress_%' OR option_name LIKE '_site_transient_secupress-%'" ); array_map( 'delete_option', $transients ); if ( is_multisite() ) { $transients = $wpdb->get_col( "SELECT meta_key FROM $wpdb->sitemeta WHERE meta_key LIKE '_site_transient%secupress_%' OR meta_key LIKE '_site_transient_secupress-%'" ); array_map( 'delete_option', $transients ); } // Options. $options = $wpdb->get_col( "SELECT option_name FROM $wpdb->options WHERE option_name LIKE 'secupress_%'" ); array_map( 'delete_option', $options ); if ( is_multisite() ) { // Site options. $options = $wpdb->get_col( "SELECT meta_key FROM $wpdb->sitemeta WHERE meta_key LIKE 'secupress_%'" ); array_map( 'delete_site_option', $options ); } // User metas. $wpdb->query( "DELETE FROM $wpdb->usermeta WHERE meta_key LIKE '%secupress%'" ); // Delete muplugins $mu_plugins_dir = WPMU_PLUGIN_DIR; $files = glob( $mu_plugins_dir . '/{_secupress*,\(secupress*}', GLOB_BRACE ); foreach ( $files as $file_path ) { if ( is_file( $file_path ) ) { @unlink( $file_path ); } } // Delete secupress-data directory $uploads = wp_upload_dir( null, false ); if ( ! empty( $uploads['basedir'] ) ) { $data_dir = wp_normalize_path( $uploads['basedir'] ) . '/secupress-data/'; if ( is_dir( $data_dir ) ) { require_once( ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php' ); require_once( ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php' ); $filesystem = new WP_Filesystem_Direct( new StdClass() ); $filesystem->delete( $data_dir, true ); } } // CRONS wp_clear_scheduled_hook( 'secupress_cleanup_leftovers' ); wp_clear_scheduled_hook( 'secupress_malware_files' ); wp_clear_scheduled_hook( 'secupress_bad_themes' ); wp_clear_scheduled_hook( 'secupress_bad_themes_maybe_do_checks' ); wp_clear_scheduled_hook( 'secupress_bad_plugins' ); wp_clear_scheduled_hook( 'secupress_bad_plugins_maybe_do_checks' ); vendor/psr/log/Psr/Log/LoggerTrait.php 0000644 00000006527 15174670627 0013700 0 ustar 00 <?php namespace Psr\Log; /** * This is a simple Logger trait that classes unable to extend AbstractLogger * (because they extend another class, etc) can include. * * It simply delegates all log-level-specific methods to the `log` method to * reduce boilerplate code that a simple Logger that does the same thing with * messages regardless of the error level has to implement. */ trait LoggerTrait { /** * System is unusable. * * @param string $message * @param array $context * * @return void */ public function emergency($message, array $context = array()) { $this->log(LogLevel::EMERGENCY, $message, $context); } /** * Action must be taken immediately. * * Example: Entire website down, database unavailable, etc. This should * trigger the SMS alerts and wake you up. * * @param string $message * @param array $context * * @return void */ public function alert($message, array $context = array()) { $this->log(LogLevel::ALERT, $message, $context); } /** * Critical conditions. * * Example: Application component unavailable, unexpected exception. * * @param string $message * @param array $context * * @return void */ public function critical($message, array $context = array()) { $this->log(LogLevel::CRITICAL, $message, $context); } /** * Runtime errors that do not require immediate action but should typically * be logged and monitored. * * @param string $message * @param array $context * * @return void */ public function error($message, array $context = array()) { $this->log(LogLevel::ERROR, $message, $context); } /** * Exceptional occurrences that are not errors. * * Example: Use of deprecated APIs, poor use of an API, undesirable things * that are not necessarily wrong. * * @param string $message * @param array $context * * @return void */ public function warning($message, array $context = array()) { $this->log(LogLevel::WARNING, $message, $context); } /** * Normal but significant events. * * @param string $message * @param array $context * * @return void */ public function notice($message, array $context = array()) { $this->log(LogLevel::NOTICE, $message, $context); } /** * Interesting events. * * Example: User logs in, SQL logs. * * @param string $message * @param array $context * * @return void */ public function info($message, array $context = array()) { $this->log(LogLevel::INFO, $message, $context); } /** * Detailed debug information. * * @param string $message * @param array $context * * @return void */ public function debug($message, array $context = array()) { $this->log(LogLevel::DEBUG, $message, $context); } /** * Logs with an arbitrary level. * * @param mixed $level * @param string $message * @param array $context * * @return void * * @throws \Psr\Log\InvalidArgumentException */ abstract public function log($level, $message, array $context = array()); } vendor/psr/log/Psr/Log/Test/TestLogger.php 0000644 00000010657 15174670627 0014452 0 ustar 00 <?php namespace Psr\Log\Test; use Psr\Log\AbstractLogger; /** * Used for testing purposes. * * It records all records and gives you access to them for verification. * * @method bool hasEmergency($record) * @method bool hasAlert($record) * @method bool hasCritical($record) * @method bool hasError($record) * @method bool hasWarning($record) * @method bool hasNotice($record) * @method bool hasInfo($record) * @method bool hasDebug($record) * * @method bool hasEmergencyRecords() * @method bool hasAlertRecords() * @method bool hasCriticalRecords() * @method bool hasErrorRecords() * @method bool hasWarningRecords() * @method bool hasNoticeRecords() * @method bool hasInfoRecords() * @method bool hasDebugRecords() * * @method bool hasEmergencyThatContains($message) * @method bool hasAlertThatContains($message) * @method bool hasCriticalThatContains($message) * @method bool hasErrorThatContains($message) * @method bool hasWarningThatContains($message) * @method bool hasNoticeThatContains($message) * @method bool hasInfoThatContains($message) * @method bool hasDebugThatContains($message) * * @method bool hasEmergencyThatMatches($message) * @method bool hasAlertThatMatches($message) * @method bool hasCriticalThatMatches($message) * @method bool hasErrorThatMatches($message) * @method bool hasWarningThatMatches($message) * @method bool hasNoticeThatMatches($message) * @method bool hasInfoThatMatches($message) * @method bool hasDebugThatMatches($message) * * @method bool hasEmergencyThatPasses($message) * @method bool hasAlertThatPasses($message) * @method bool hasCriticalThatPasses($message) * @method bool hasErrorThatPasses($message) * @method bool hasWarningThatPasses($message) * @method bool hasNoticeThatPasses($message) * @method bool hasInfoThatPasses($message) * @method bool hasDebugThatPasses($message) */ class TestLogger extends AbstractLogger { /** * @var array */ public $records = []; public $recordsByLevel = []; /** * @inheritdoc */ public function log($level, $message, array $context = []) { $record = [ 'level' => $level, 'message' => $message, 'context' => $context, ]; $this->recordsByLevel[$record['level']][] = $record; $this->records[] = $record; } public function hasRecords($level) { return isset($this->recordsByLevel[$level]); } public function hasRecord($record, $level) { if (is_string($record)) { $record = ['message' => $record]; } return $this->hasRecordThatPasses(function ($rec) use ($record) { if ($rec['message'] !== $record['message']) { return false; } if (isset($record['context']) && $rec['context'] !== $record['context']) { return false; } return true; }, $level); } public function hasRecordThatContains($message, $level) { return $this->hasRecordThatPasses(function ($rec) use ($message) { return strpos($rec['message'], $message) !== false; }, $level); } public function hasRecordThatMatches($regex, $level) { return $this->hasRecordThatPasses(function ($rec) use ($regex) { return preg_match($regex, $rec['message']) > 0; }, $level); } public function hasRecordThatPasses(callable $predicate, $level) { if (!isset($this->recordsByLevel[$level])) { return false; } foreach ($this->recordsByLevel[$level] as $i => $rec) { if (call_user_func($predicate, $rec, $i)) { return true; } } return false; } public function __call($method, $args) { if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) { $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3]; $level = strtolower($matches[2]); if (method_exists($this, $genericMethod)) { $args[] = $level; return call_user_func_array([$this, $genericMethod], $args); } } throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()'); } public function reset() { $this->records = []; $this->recordsByLevel = []; } } vendor/psr/log/Psr/Log/Test/DummyTest.php 0000644 00000000373 15174670627 0014320 0 ustar 00 <?php namespace Psr\Log\Test; /** * This class is internal and does not follow the BC promise. * * Do NOT use this class in any way. * * @internal */ class DummyTest { public function __toString() { return 'DummyTest'; } } vendor/psr/log/Psr/Log/Test/LoggerInterfaceTest.php 0000644 00000011051 15174670627 0016260 0 ustar 00 <?php namespace Psr\Log\Test; use Psr\Log\LoggerInterface; use Psr\Log\LogLevel; use PHPUnit\Framework\TestCase; /** * Provides a base test class for ensuring compliance with the LoggerInterface. * * Implementors can extend the class and implement abstract methods to run this * as part of their test suite. */ abstract class LoggerInterfaceTest extends TestCase { /** * @return LoggerInterface */ abstract public function getLogger(); /** * This must return the log messages in order. * * The simple formatting of the messages is: "<LOG LEVEL> <MESSAGE>". * * Example ->error('Foo') would yield "error Foo". * * @return string[] */ abstract public function getLogs(); public function testImplements() { $this->assertInstanceOf('Psr\Log\LoggerInterface', $this->getLogger()); } /** * @dataProvider provideLevelsAndMessages */ public function testLogsAtAllLevels($level, $message) { $logger = $this->getLogger(); $logger->{$level}($message, array('user' => 'Bob')); $logger->log($level, $message, array('user' => 'Bob')); $expected = array( $level.' message of level '.$level.' with context: Bob', $level.' message of level '.$level.' with context: Bob', ); $this->assertEquals($expected, $this->getLogs()); } public function provideLevelsAndMessages() { return array( LogLevel::EMERGENCY => array(LogLevel::EMERGENCY, 'message of level emergency with context: {user}'), LogLevel::ALERT => array(LogLevel::ALERT, 'message of level alert with context: {user}'), LogLevel::CRITICAL => array(LogLevel::CRITICAL, 'message of level critical with context: {user}'), LogLevel::ERROR => array(LogLevel::ERROR, 'message of level error with context: {user}'), LogLevel::WARNING => array(LogLevel::WARNING, 'message of level warning with context: {user}'), LogLevel::NOTICE => array(LogLevel::NOTICE, 'message of level notice with context: {user}'), LogLevel::INFO => array(LogLevel::INFO, 'message of level info with context: {user}'), LogLevel::DEBUG => array(LogLevel::DEBUG, 'message of level debug with context: {user}'), ); } /** * @expectedException \Psr\Log\InvalidArgumentException */ public function testThrowsOnInvalidLevel() { $logger = $this->getLogger(); $logger->log('invalid level', 'Foo'); } public function testContextReplacement() { $logger = $this->getLogger(); $logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar')); $expected = array('info {Message {nothing} Bob Bar a}'); $this->assertEquals($expected, $this->getLogs()); } public function testObjectCastToString() { if (method_exists($this, 'createPartialMock')) { $dummy = $this->createPartialMock('Psr\Log\Test\DummyTest', array('__toString')); } else { $dummy = $this->getMock('Psr\Log\Test\DummyTest', array('__toString')); } $dummy->expects($this->once()) ->method('__toString') ->will($this->returnValue('DUMMY')); $this->getLogger()->warning($dummy); $expected = array('warning DUMMY'); $this->assertEquals($expected, $this->getLogs()); } public function testContextCanContainAnything() { $closed = fopen('php://memory', 'r'); fclose($closed); $context = array( 'bool' => true, 'null' => null, 'string' => 'Foo', 'int' => 0, 'float' => 0.5, 'nested' => array('with object' => new DummyTest), 'object' => new \DateTime, 'resource' => fopen('php://memory', 'r'), 'closed' => $closed, ); $this->getLogger()->warning('Crazy context data', $context); $expected = array('warning Crazy context data'); $this->assertEquals($expected, $this->getLogs()); } public function testContextExceptionKeyCanBeExceptionOrOtherValues() { $logger = $this->getLogger(); $logger->warning('Random message', array('exception' => 'oops')); $logger->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail'))); $expected = array( 'warning Random message', 'critical Uncaught Exception!' ); $this->assertEquals($expected, $this->getLogs()); } } vendor/psr/log/Psr/Log/LoggerInterface.php 0000644 00000006052 15174670627 0014506 0 ustar 00 <?php namespace Psr\Log; /** * Describes a logger instance. * * The message MUST be a string or object implementing __toString(). * * The message MAY contain placeholders in the form: {foo} where foo * will be replaced by the context data in key "foo". * * The context array can contain arbitrary data. The only assumption that * can be made by implementors is that if an Exception instance is given * to produce a stack trace, it MUST be in a key named "exception". * * See https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md * for the full interface specification. */ interface LoggerInterface { /** * System is unusable. * * @param string $message * @param mixed[] $context * * @return void */ public function emergency($message, array $context = array()); /** * Action must be taken immediately. * * Example: Entire website down, database unavailable, etc. This should * trigger the SMS alerts and wake you up. * * @param string $message * @param mixed[] $context * * @return void */ public function alert($message, array $context = array()); /** * Critical conditions. * * Example: Application component unavailable, unexpected exception. * * @param string $message * @param mixed[] $context * * @return void */ public function critical($message, array $context = array()); /** * Runtime errors that do not require immediate action but should typically * be logged and monitored. * * @param string $message * @param mixed[] $context * * @return void */ public function error($message, array $context = array()); /** * Exceptional occurrences that are not errors. * * Example: Use of deprecated APIs, poor use of an API, undesirable things * that are not necessarily wrong. * * @param string $message * @param mixed[] $context * * @return void */ public function warning($message, array $context = array()); /** * Normal but significant events. * * @param string $message * @param mixed[] $context * * @return void */ public function notice($message, array $context = array()); /** * Interesting events. * * Example: User logs in, SQL logs. * * @param string $message * @param mixed[] $context * * @return void */ public function info($message, array $context = array()); /** * Detailed debug information. * * @param string $message * @param mixed[] $context * * @return void */ public function debug($message, array $context = array()); /** * Logs with an arbitrary level. * * @param mixed $level * @param string $message * @param mixed[] $context * * @return void * * @throws \Psr\Log\InvalidArgumentException */ public function log($level, $message, array $context = array()); } vendor/psr/log/Psr/Log/AbstractLogger.php 0000644 00000006040 15174670627 0014346 0 ustar 00 <?php namespace Psr\Log; /** * This is a simple Logger implementation that other Loggers can inherit from. * * It simply delegates all log-level-specific methods to the `log` method to * reduce boilerplate code that a simple Logger that does the same thing with * messages regardless of the error level has to implement. */ abstract class AbstractLogger implements LoggerInterface { /** * System is unusable. * * @param string $message * @param mixed[] $context * * @return void */ public function emergency($message, array $context = array()) { $this->log(LogLevel::EMERGENCY, $message, $context); } /** * Action must be taken immediately. * * Example: Entire website down, database unavailable, etc. This should * trigger the SMS alerts and wake you up. * * @param string $message * @param mixed[] $context * * @return void */ public function alert($message, array $context = array()) { $this->log(LogLevel::ALERT, $message, $context); } /** * Critical conditions. * * Example: Application component unavailable, unexpected exception. * * @param string $message * @param mixed[] $context * * @return void */ public function critical($message, array $context = array()) { $this->log(LogLevel::CRITICAL, $message, $context); } /** * Runtime errors that do not require immediate action but should typically * be logged and monitored. * * @param string $message * @param mixed[] $context * * @return void */ public function error($message, array $context = array()) { $this->log(LogLevel::ERROR, $message, $context); } /** * Exceptional occurrences that are not errors. * * Example: Use of deprecated APIs, poor use of an API, undesirable things * that are not necessarily wrong. * * @param string $message * @param mixed[] $context * * @return void */ public function warning($message, array $context = array()) { $this->log(LogLevel::WARNING, $message, $context); } /** * Normal but significant events. * * @param string $message * @param mixed[] $context * * @return void */ public function notice($message, array $context = array()) { $this->log(LogLevel::NOTICE, $message, $context); } /** * Interesting events. * * Example: User logs in, SQL logs. * * @param string $message * @param mixed[] $context * * @return void */ public function info($message, array $context = array()) { $this->log(LogLevel::INFO, $message, $context); } /** * Detailed debug information. * * @param string $message * @param mixed[] $context * * @return void */ public function debug($message, array $context = array()) { $this->log(LogLevel::DEBUG, $message, $context); } } vendor/psr/log/Psr/Log/NullLogger.php 0000644 00000001303 15174670627 0013512 0 ustar 00 <?php namespace Psr\Log; /** * This Logger can be used to avoid conditional log calls. * * Logging should always be optional, and if no logger is provided to your * library creating a NullLogger instance to have something to throw logs at * is a good way to avoid littering your code with `if ($this->logger) { }` * blocks. */ class NullLogger extends AbstractLogger { /** * Logs with an arbitrary level. * * @param mixed $level * @param string $message * @param array $context * * @return void * * @throws \Psr\Log\InvalidArgumentException */ public function log($level, $message, array $context = array()) { // noop } } vendor/psr/log/Psr/Log/LoggerAwareTrait.php 0000644 00000000622 15174670627 0014646 0 ustar 00 <?php namespace Psr\Log; /** * Basic Implementation of LoggerAwareInterface. */ trait LoggerAwareTrait { /** * The logger instance. * * @var LoggerInterface|null */ protected $logger; /** * Sets a logger. * * @param LoggerInterface $logger */ public function setLogger(LoggerInterface $logger) { $this->logger = $logger; } } vendor/psr/log/Psr/Log/LogLevel.php 0000644 00000000520 15174670627 0013151 0 ustar 00 <?php namespace Psr\Log; /** * Describes log levels. */ class LogLevel { const EMERGENCY = 'emergency'; const ALERT = 'alert'; const CRITICAL = 'critical'; const ERROR = 'error'; const WARNING = 'warning'; const NOTICE = 'notice'; const INFO = 'info'; const DEBUG = 'debug'; } vendor/psr/log/Psr/Log/InvalidArgumentException.php 0000644 00000000140 15174670627 0016406 0 ustar 00 <?php namespace Psr\Log; class InvalidArgumentException extends \InvalidArgumentException { } vendor/psr/log/Psr/Log/LoggerAwareInterface.php 0000644 00000000451 15174670627 0015463 0 ustar 00 <?php namespace Psr\Log; /** * Describes a logger-aware instance. */ interface LoggerAwareInterface { /** * Sets a logger instance on the object. * * @param LoggerInterface $logger * * @return void */ public function setLogger(LoggerInterface $logger); } vendor/psr/log/LICENSE 0000644 00000002075 15174670627 0010456 0 ustar 00 Copyright (c) 2012 PHP Framework Interoperability Group Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. vendor/composer/autoload_namespaces.php 0000644 00000000225 15174670627 0014426 0 ustar 00 <?php // autoload_namespaces.php @generated by Composer $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( ); vendor/composer/platform_check.php 0000644 00000001635 15174670627 0013406 0 ustar 00 <?php // platform_check.php @generated by Composer $issues = array(); if (!(PHP_VERSION_ID >= 50600)) { $issues[] = 'Your Composer dependencies require a PHP version ">= 5.6.0". You are running ' . PHP_VERSION . '.'; } if ($issues) { if (!headers_sent()) { header('HTTP/1.1 500 Internal Server Error'); } if (!ini_get('display_errors')) { if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') { fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL); } elseif (!headers_sent()) { echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL; } } trigger_error( 'Composer detected issues in your platform: ' . implode(' ', $issues), E_USER_ERROR ); } vendor/composer/autoload_psr4.php 0000644 00000000415 15174670627 0013200 0 ustar 00 <?php // autoload_psr4.php @generated by Composer $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'), 'DecaLog\\' => array($vendorDir . '/perfopsone/decalog/src'), ); vendor/composer/LICENSE 0000644 00000002056 15174670627 0010717 0 ustar 00 Copyright (c) Nils Adermann, Jordi Boggiano Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. vendor/composer/installed.php 0000644 00000002716 15174670627 0012405 0 ustar 00 <?php return array( 'root' => array( 'pretty_version' => 'dev-master', 'version' => 'dev-master', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'reference' => '2de78ebc048803a1ad1d70ab79c0f3cd40369d44', 'name' => 'juliopotier/secupress', 'dev' => true, ), 'versions' => array( 'juliopotier/secupress' => array( 'pretty_version' => 'dev-master', 'version' => 'dev-master', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'reference' => '2de78ebc048803a1ad1d70ab79c0f3cd40369d44', 'dev_requirement' => false, ), 'perfopsone/decalog' => array( 'pretty_version' => '2.0.0', 'version' => '2.0.0.0', 'type' => 'library', 'install_path' => __DIR__ . '/../perfopsone/decalog', 'aliases' => array(), 'reference' => 'a6f64d3a856b6250af443098aeea57ccdda5d366', 'dev_requirement' => false, ), 'psr/log' => array( 'pretty_version' => '1.1.4', 'version' => '1.1.4.0', 'type' => 'library', 'install_path' => __DIR__ . '/../psr/log', 'aliases' => array(), 'reference' => 'd49695b909c3b7628b6289db5479a1c204601f11', 'dev_requirement' => false, ), ), ); vendor/composer/autoload_static.php 0000644 00000002336 15174670627 0013603 0 ustar 00 <?php // autoload_static.php @generated by Composer namespace Composer\Autoload; class ComposerStaticInit6ec25a1a260ce5c64970a7d3769bb383 { public static $prefixLengthsPsr4 = array ( 'P' => array ( 'Psr\\Log\\' => 8, ), 'D' => array ( 'DecaLog\\' => 8, ), ); public static $prefixDirsPsr4 = array ( 'Psr\\Log\\' => array ( 0 => __DIR__ . '/..' . '/psr/log/Psr/Log', ), 'DecaLog\\' => array ( 0 => __DIR__ . '/..' . '/perfopsone/decalog/src', ), ); public static $classMap = array ( 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', ); public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { $loader->prefixLengthsPsr4 = ComposerStaticInit6ec25a1a260ce5c64970a7d3769bb383::$prefixLengthsPsr4; $loader->prefixDirsPsr4 = ComposerStaticInit6ec25a1a260ce5c64970a7d3769bb383::$prefixDirsPsr4; $loader->classMap = ComposerStaticInit6ec25a1a260ce5c64970a7d3769bb383::$classMap; }, null, ClassLoader::class); } } vendor/composer/installed.json 0000644 00000007332 15174670627 0012566 0 ustar 00 { "packages": [ { "name": "perfopsone/decalog", "version": "2.0.0", "version_normalized": "2.0.0.0", "source": { "type": "git", "url": "https://github.com/Pierre-Lannoy/wp-decalog-sdk.git", "reference": "a6f64d3a856b6250af443098aeea57ccdda5d366" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/Pierre-Lannoy/wp-decalog-sdk/zipball/a6f64d3a856b6250af443098aeea57ccdda5d366", "reference": "a6f64d3a856b6250af443098aeea57ccdda5d366", "shasum": "" }, "require": { "php": ">=5.6", "psr/log": "^1.0.1" }, "time": "2021-06-21T17:39:16+00:00", "type": "library", "installation-source": "dist", "autoload": { "psr-4": { "DecaLog\\": "./src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "Pierre Lannoy", "email": "pierre@lannoy.fr" } ], "description": "A SDK to use DecaLog observability features in all your plugins or themes development projects for WordPress.", "homepage": "https://decalog.io", "keywords": [ "logging", "monitoring", "observability", "tracing", "wordpress" ], "support": { "issues": "https://github.com/Pierre-Lannoy/wp-decalog-sdk/issues", "source": "https://github.com/Pierre-Lannoy/wp-decalog-sdk/tree/2.0.0" }, "install-path": "../perfopsone/decalog" }, { "name": "psr/log", "version": "1.1.4", "version_normalized": "1.1.4.0", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", "reference": "d49695b909c3b7628b6289db5479a1c204601f11" }, "dist": { "type": "zip", "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", "reference": "d49695b909c3b7628b6289db5479a1c204601f11", "shasum": "" }, "require": { "php": ">=5.3.0" }, "time": "2021-05-03T11:20:27+00:00", "type": "library", "extra": { "branch-alias": { "dev-master": "1.1.x-dev" } }, "installation-source": "dist", "autoload": { "psr-4": { "Psr\\Log\\": "Psr/Log/" } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { "name": "PHP-FIG", "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for logging libraries", "homepage": "https://github.com/php-fig/log", "keywords": [ "log", "psr", "psr-3" ], "support": { "source": "https://github.com/php-fig/log/tree/1.1.4" }, "install-path": "../psr/log" } ], "dev": true, "dev-package-names": [] } vendor/composer/autoload_classmap.php 0000644 00000000350 15174670627 0014111 0 ustar 00 <?php // autoload_classmap.php @generated by Composer $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', ); vendor/composer/InstalledVersions.php 0000644 00000035217 15174670627 0014100 0 ustar 00 <?php /* * This file is part of Composer. * * (c) Nils Adermann <naderman@naderman.de> * Jordi Boggiano <j.boggiano@seld.be> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer; use Composer\Autoload\ClassLoader; use Composer\Semver\VersionParser; /** * This class is copied in every Composer installed project and available to all * * See also https://getcomposer.org/doc/07-runtime.md#installed-versions * * To require its presence, you can require `composer-runtime-api ^2.0` */ class InstalledVersions { /** * @var mixed[]|null * @psalm-var array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}|array{}|null */ private static $installed; /** * @var bool|null */ private static $canGetVendors; /** * @var array[] * @psalm-var array<string, array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}> */ private static $installedByVendor = array(); /** * Returns a list of all package names which are present, either by being installed, replaced or provided * * @return string[] * @psalm-return list<string> */ public static function getInstalledPackages() { $packages = array(); foreach (self::getInstalled() as $installed) { $packages[] = array_keys($installed['versions']); } if (1 === \count($packages)) { return $packages[0]; } return array_keys(array_flip(\call_user_func_array('array_merge', $packages))); } /** * Returns a list of all package names with a specific type e.g. 'library' * * @param string $type * @return string[] * @psalm-return list<string> */ public static function getInstalledPackagesByType($type) { $packagesByType = array(); foreach (self::getInstalled() as $installed) { foreach ($installed['versions'] as $name => $package) { if (isset($package['type']) && $package['type'] === $type) { $packagesByType[] = $name; } } } return $packagesByType; } /** * Checks whether the given package is installed * * This also returns true if the package name is provided or replaced by another package * * @param string $packageName * @param bool $includeDevRequirements * @return bool */ public static function isInstalled($packageName, $includeDevRequirements = true) { foreach (self::getInstalled() as $installed) { if (isset($installed['versions'][$packageName])) { return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']); } } return false; } /** * Checks whether the given package satisfies a version constraint * * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call: * * Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3') * * @param VersionParser $parser Install composer/semver to have access to this class and functionality * @param string $packageName * @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package * @return bool */ public static function satisfies(VersionParser $parser, $packageName, $constraint) { $constraint = $parser->parseConstraints($constraint); $provided = $parser->parseConstraints(self::getVersionRanges($packageName)); return $provided->matches($constraint); } /** * Returns a version constraint representing all the range(s) which are installed for a given package * * It is easier to use this via isInstalled() with the $constraint argument if you need to check * whether a given version of a package is installed, and not just whether it exists * * @param string $packageName * @return string Version constraint usable with composer/semver */ public static function getVersionRanges($packageName) { foreach (self::getInstalled() as $installed) { if (!isset($installed['versions'][$packageName])) { continue; } $ranges = array(); if (isset($installed['versions'][$packageName]['pretty_version'])) { $ranges[] = $installed['versions'][$packageName]['pretty_version']; } if (array_key_exists('aliases', $installed['versions'][$packageName])) { $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']); } if (array_key_exists('replaced', $installed['versions'][$packageName])) { $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']); } if (array_key_exists('provided', $installed['versions'][$packageName])) { $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']); } return implode(' || ', $ranges); } throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); } /** * @param string $packageName * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present */ public static function getVersion($packageName) { foreach (self::getInstalled() as $installed) { if (!isset($installed['versions'][$packageName])) { continue; } if (!isset($installed['versions'][$packageName]['version'])) { return null; } return $installed['versions'][$packageName]['version']; } throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); } /** * @param string $packageName * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present */ public static function getPrettyVersion($packageName) { foreach (self::getInstalled() as $installed) { if (!isset($installed['versions'][$packageName])) { continue; } if (!isset($installed['versions'][$packageName]['pretty_version'])) { return null; } return $installed['versions'][$packageName]['pretty_version']; } throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); } /** * @param string $packageName * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference */ public static function getReference($packageName) { foreach (self::getInstalled() as $installed) { if (!isset($installed['versions'][$packageName])) { continue; } if (!isset($installed['versions'][$packageName]['reference'])) { return null; } return $installed['versions'][$packageName]['reference']; } throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); } /** * @param string $packageName * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path. */ public static function getInstallPath($packageName) { foreach (self::getInstalled() as $installed) { if (!isset($installed['versions'][$packageName])) { continue; } return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null; } throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); } /** * @return array * @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string} */ public static function getRootPackage() { $installed = self::getInstalled(); return $installed[0]['root']; } /** * Returns the raw installed.php data for custom implementations * * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. * @return array[] * @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>} */ public static function getRawData() { @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED); if (null === self::$installed) { // only require the installed.php file if this file is loaded from its dumped location, // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 if (substr(__DIR__, -8, 1) !== 'C') { self::$installed = include __DIR__ . '/installed.php'; } else { self::$installed = array(); } } return self::$installed; } /** * Returns the raw data of all installed.php which are currently loaded for custom implementations * * @return array[] * @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}> */ public static function getAllRawData() { return self::getInstalled(); } /** * Lets you reload the static array from another file * * This is only useful for complex integrations in which a project needs to use * this class but then also needs to execute another project's autoloader in process, * and wants to ensure both projects have access to their version of installed.php. * * A typical case would be PHPUnit, where it would need to make sure it reads all * the data it needs from this class, then call reload() with * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure * the project in which it runs can then also use this class safely, without * interference between PHPUnit's dependencies and the project's dependencies. * * @param array[] $data A vendor/composer/installed.php data set * @return void * * @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>} $data */ public static function reload($data) { self::$installed = $data; self::$installedByVendor = array(); } /** * @return array[] * @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}> */ private static function getInstalled() { if (null === self::$canGetVendors) { self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders'); } $installed = array(); if (self::$canGetVendors) { foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { if (isset(self::$installedByVendor[$vendorDir])) { $installed[] = self::$installedByVendor[$vendorDir]; } elseif (is_file($vendorDir.'/composer/installed.php')) { $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php'; if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { self::$installed = $installed[count($installed) - 1]; } } } } if (null === self::$installed) { // only require the installed.php file if this file is loaded from its dumped location, // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937 if (substr(__DIR__, -8, 1) !== 'C') { self::$installed = require __DIR__ . '/installed.php'; } else { self::$installed = array(); } } $installed[] = self::$installed; return $installed; } } vendor/composer/autoload_real.php 0000644 00000003551 15174670627 0013237 0 ustar 00 <?php // autoload_real.php @generated by Composer class ComposerAutoloaderInit6ec25a1a260ce5c64970a7d3769bb383 { private static $loader; public static function loadClassLoader($class) { if ('Composer\Autoload\ClassLoader' === $class) { require __DIR__ . '/ClassLoader.php'; } } /** * @return \Composer\Autoload\ClassLoader */ public static function getLoader() { if (null !== self::$loader) { return self::$loader; } require __DIR__ . '/platform_check.php'; spl_autoload_register(array('ComposerAutoloaderInit6ec25a1a260ce5c64970a7d3769bb383', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__))); spl_autoload_unregister(array('ComposerAutoloaderInit6ec25a1a260ce5c64970a7d3769bb383', 'loadClassLoader')); $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); if ($useStaticLoader) { require __DIR__ . '/autoload_static.php'; call_user_func(\Composer\Autoload\ComposerStaticInit6ec25a1a260ce5c64970a7d3769bb383::getInitializer($loader)); } else { $map = require __DIR__ . '/autoload_namespaces.php'; foreach ($map as $namespace => $path) { $loader->set($namespace, $path); } $map = require __DIR__ . '/autoload_psr4.php'; foreach ($map as $namespace => $path) { $loader->setPsr4($namespace, $path); } $classMap = require __DIR__ . '/autoload_classmap.php'; if ($classMap) { $loader->addClassMap($classMap); } } $loader->register(true); return $loader; } } vendor/composer/ClassLoader.php 0000644 00000037301 15174670627 0012620 0 ustar 00 <?php /* * This file is part of Composer. * * (c) Nils Adermann <naderman@naderman.de> * Jordi Boggiano <j.boggiano@seld.be> * * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ namespace Composer\Autoload; /** * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. * * $loader = new \Composer\Autoload\ClassLoader(); * * // register classes with namespaces * $loader->add('Symfony\Component', __DIR__.'/component'); * $loader->add('Symfony', __DIR__.'/framework'); * * // activate the autoloader * $loader->register(); * * // to enable searching the include path (eg. for PEAR packages) * $loader->setUseIncludePath(true); * * In this example, if you try to use a class in the Symfony\Component * namespace or one of its children (Symfony\Component\Console for instance), * the autoloader will first look for the class under the component/ * directory, and it will then fallback to the framework/ directory if not * found before giving up. * * This class is loosely based on the Symfony UniversalClassLoader. * * @author Fabien Potencier <fabien@symfony.com> * @author Jordi Boggiano <j.boggiano@seld.be> * @see https://www.php-fig.org/psr/psr-0/ * @see https://www.php-fig.org/psr/psr-4/ */ class ClassLoader { /** @var ?string */ private $vendorDir; // PSR-4 /** * @var array[] * @psalm-var array<string, array<string, int>> */ private $prefixLengthsPsr4 = array(); /** * @var array[] * @psalm-var array<string, array<int, string>> */ private $prefixDirsPsr4 = array(); /** * @var array[] * @psalm-var array<string, string> */ private $fallbackDirsPsr4 = array(); // PSR-0 /** * @var array[] * @psalm-var array<string, array<string, string[]>> */ private $prefixesPsr0 = array(); /** * @var array[] * @psalm-var array<string, string> */ private $fallbackDirsPsr0 = array(); /** @var bool */ private $useIncludePath = false; /** * @var string[] * @psalm-var array<string, string> */ private $classMap = array(); /** @var bool */ private $classMapAuthoritative = false; /** * @var bool[] * @psalm-var array<string, bool> */ private $missingClasses = array(); /** @var ?string */ private $apcuPrefix; /** * @var self[] */ private static $registeredLoaders = array(); /** * @param ?string $vendorDir */ public function __construct($vendorDir = null) { $this->vendorDir = $vendorDir; } /** * @return string[] */ public function getPrefixes() { if (!empty($this->prefixesPsr0)) { return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); } return array(); } /** * @return array[] * @psalm-return array<string, array<int, string>> */ public function getPrefixesPsr4() { return $this->prefixDirsPsr4; } /** * @return array[] * @psalm-return array<string, string> */ public function getFallbackDirs() { return $this->fallbackDirsPsr0; } /** * @return array[] * @psalm-return array<string, string> */ public function getFallbackDirsPsr4() { return $this->fallbackDirsPsr4; } /** * @return string[] Array of classname => path * @psalm-var array<string, string> */ public function getClassMap() { return $this->classMap; } /** * @param string[] $classMap Class to filename map * @psalm-param array<string, string> $classMap * * @return void */ public function addClassMap(array $classMap) { if ($this->classMap) { $this->classMap = array_merge($this->classMap, $classMap); } else { $this->classMap = $classMap; } } /** * Registers a set of PSR-0 directories for a given prefix, either * appending or prepending to the ones previously set for this prefix. * * @param string $prefix The prefix * @param string[]|string $paths The PSR-0 root directories * @param bool $prepend Whether to prepend the directories * * @return void */ public function add($prefix, $paths, $prepend = false) { if (!$prefix) { if ($prepend) { $this->fallbackDirsPsr0 = array_merge( (array) $paths, $this->fallbackDirsPsr0 ); } else { $this->fallbackDirsPsr0 = array_merge( $this->fallbackDirsPsr0, (array) $paths ); } return; } $first = $prefix[0]; if (!isset($this->prefixesPsr0[$first][$prefix])) { $this->prefixesPsr0[$first][$prefix] = (array) $paths; return; } if ($prepend) { $this->prefixesPsr0[$first][$prefix] = array_merge( (array) $paths, $this->prefixesPsr0[$first][$prefix] ); } else { $this->prefixesPsr0[$first][$prefix] = array_merge( $this->prefixesPsr0[$first][$prefix], (array) $paths ); } } /** * Registers a set of PSR-4 directories for a given namespace, either * appending or prepending to the ones previously set for this namespace. * * @param string $prefix The prefix/namespace, with trailing '\\' * @param string[]|string $paths The PSR-4 base directories * @param bool $prepend Whether to prepend the directories * * @throws \InvalidArgumentException * * @return void */ public function addPsr4($prefix, $paths, $prepend = false) { if (!$prefix) { // Register directories for the root namespace. if ($prepend) { $this->fallbackDirsPsr4 = array_merge( (array) $paths, $this->fallbackDirsPsr4 ); } else { $this->fallbackDirsPsr4 = array_merge( $this->fallbackDirsPsr4, (array) $paths ); } } elseif (!isset($this->prefixDirsPsr4[$prefix])) { // Register directories for a new namespace. $length = strlen($prefix); if ('\\' !== $prefix[$length - 1]) { throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); } $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; $this->prefixDirsPsr4[$prefix] = (array) $paths; } elseif ($prepend) { // Prepend directories for an already registered namespace. $this->prefixDirsPsr4[$prefix] = array_merge( (array) $paths, $this->prefixDirsPsr4[$prefix] ); } else { // Append directories for an already registered namespace. $this->prefixDirsPsr4[$prefix] = array_merge( $this->prefixDirsPsr4[$prefix], (array) $paths ); } } /** * Registers a set of PSR-0 directories for a given prefix, * replacing any others previously set for this prefix. * * @param string $prefix The prefix * @param string[]|string $paths The PSR-0 base directories * * @return void */ public function set($prefix, $paths) { if (!$prefix) { $this->fallbackDirsPsr0 = (array) $paths; } else { $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; } } /** * Registers a set of PSR-4 directories for a given namespace, * replacing any others previously set for this namespace. * * @param string $prefix The prefix/namespace, with trailing '\\' * @param string[]|string $paths The PSR-4 base directories * * @throws \InvalidArgumentException * * @return void */ public function setPsr4($prefix, $paths) { if (!$prefix) { $this->fallbackDirsPsr4 = (array) $paths; } else { $length = strlen($prefix); if ('\\' !== $prefix[$length - 1]) { throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); } $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; $this->prefixDirsPsr4[$prefix] = (array) $paths; } } /** * Turns on searching the include path for class files. * * @param bool $useIncludePath * * @return void */ public function setUseIncludePath($useIncludePath) { $this->useIncludePath = $useIncludePath; } /** * Can be used to check if the autoloader uses the include path to check * for classes. * * @return bool */ public function getUseIncludePath() { return $this->useIncludePath; } /** * Turns off searching the prefix and fallback directories for classes * that have not been registered with the class map. * * @param bool $classMapAuthoritative * * @return void */ public function setClassMapAuthoritative($classMapAuthoritative) { $this->classMapAuthoritative = $classMapAuthoritative; } /** * Should class lookup fail if not found in the current class map? * * @return bool */ public function isClassMapAuthoritative() { return $this->classMapAuthoritative; } /** * APCu prefix to use to cache found/not-found classes, if the extension is enabled. * * @param string|null $apcuPrefix * * @return void */ public function setApcuPrefix($apcuPrefix) { $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; } /** * The APCu prefix in use, or null if APCu caching is not enabled. * * @return string|null */ public function getApcuPrefix() { return $this->apcuPrefix; } /** * Registers this instance as an autoloader. * * @param bool $prepend Whether to prepend the autoloader or not * * @return void */ public function register($prepend = false) { spl_autoload_register(array($this, 'loadClass'), true, $prepend); if (null === $this->vendorDir) { return; } if ($prepend) { self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders; } else { unset(self::$registeredLoaders[$this->vendorDir]); self::$registeredLoaders[$this->vendorDir] = $this; } } /** * Unregisters this instance as an autoloader. * * @return void */ public function unregister() { spl_autoload_unregister(array($this, 'loadClass')); if (null !== $this->vendorDir) { unset(self::$registeredLoaders[$this->vendorDir]); } } /** * Loads the given class or interface. * * @param string $class The name of the class * @return true|null True if loaded, null otherwise */ public function loadClass($class) { if ($file = $this->findFile($class)) { includeFile($file); return true; } return null; } /** * Finds the path to the file where the class is defined. * * @param string $class The name of the class * * @return string|false The path if found, false otherwise */ public function findFile($class) { // class map lookup if (isset($this->classMap[$class])) { return $this->classMap[$class]; } if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { return false; } if (null !== $this->apcuPrefix) { $file = apcu_fetch($this->apcuPrefix.$class, $hit); if ($hit) { return $file; } } $file = $this->findFileWithExtension($class, '.php'); // Search for Hack files if we are running on HHVM if (false === $file && defined('HHVM_VERSION')) { $file = $this->findFileWithExtension($class, '.hh'); } if (null !== $this->apcuPrefix) { apcu_add($this->apcuPrefix.$class, $file); } if (false === $file) { // Remember that this class does not exist. $this->missingClasses[$class] = true; } return $file; } /** * Returns the currently registered loaders indexed by their corresponding vendor directories. * * @return self[] */ public static function getRegisteredLoaders() { return self::$registeredLoaders; } /** * @param string $class * @param string $ext * @return string|false */ private function findFileWithExtension($class, $ext) { // PSR-4 lookup $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; $first = $class[0]; if (isset($this->prefixLengthsPsr4[$first])) { $subPath = $class; while (false !== $lastPos = strrpos($subPath, '\\')) { $subPath = substr($subPath, 0, $lastPos); $search = $subPath . '\\'; if (isset($this->prefixDirsPsr4[$search])) { $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); foreach ($this->prefixDirsPsr4[$search] as $dir) { if (file_exists($file = $dir . $pathEnd)) { return $file; } } } } } // PSR-4 fallback dirs foreach ($this->fallbackDirsPsr4 as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { return $file; } } // PSR-0 lookup if (false !== $pos = strrpos($class, '\\')) { // namespaced class name $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); } else { // PEAR-like class name $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; } if (isset($this->prefixesPsr0[$first])) { foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { if (0 === strpos($class, $prefix)) { foreach ($dirs as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } } } } } // PSR-0 fallback dirs foreach ($this->fallbackDirsPsr0 as $dir) { if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { return $file; } } // PSR-0 include paths. if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { return $file; } return false; } } /** * Scope isolated include. * * Prevents access to $this/self from included files. * * @param string $file * @return void * @private */ function includeFile($file) { include $file; } vendor/autoload.php 0000644 00000000262 15174670627 0010401 0 ustar 00 <?php // autoload.php @generated by Composer require_once __DIR__ . '/composer/autoload_real.php'; return ComposerAutoloaderInit6ec25a1a260ce5c64970a7d3769bb383::getLoader(); vendor/perfopsone/decalog/LICENSE 0000644 00000002056 15174670627 0012646 0 ustar 00 MIT License Copyright (c) 2021 Pierre Lannoy Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. vendor/perfopsone/decalog/src/TracesLogger.php 0000644 00000003450 15174670627 0015521 0 ustar 00 <?php /** * DecaLog tracer definition. * * @package SDK * @author Pierre Lannoy <https://pierre.lannoy.fr/>. * @since 1.0.0 */ namespace DecaLog; /** * DecaLog tracer class. * * This class defines all code necessary to trace with DecaLog. * If DecaLog is not installed, it will do nothing and will not throw errors. * * @package SDK * @author Pierre Lannoy <https://pierre.lannoy.fr/>. * @since 1.0.0 */ class TracesLogger { /** * The "true" DTracer instance. * * @since 1.0.0 * @var \Decalog\Plugin\Feature\DTracer $tracer Maintains the internal DTracer instance. */ private $tracer = null; /** * Initialize the class and set its properties. * * @param string $class The class identifier, must be a value in ['plugin', 'theme']. * @param string $name Optional. The name of the component that will trigger events. * @param string $version Optional. The version of the component that will trigger events. * @since 1.0.0 */ public function __construct( $class, $name = null, $version = null ) { if ( class_exists( '\Decalog\Plugin\Feature\DTracer' ) ) { $this->tracer = new \Decalog\Plugin\Feature\DTracer( $class, $name, $version ); } } /** * Starts a span. * * @param string $name The name of the span. * @param string $parent_id Optional. The id of the parent. If none, it will be linked to WP root id. * @return string Id of started span. * @since 1.0.0 */ public function startSpan( $name, $parent_id = 'xxx' ) { if ( $this->tracer ) { return $this->tracer->start_span( $name, $parent_id ); } return 'xxx'; } /** * Ends a span. * * @param string $id The id of the span. * @since 1.0.0 */ public function endSpan( $id ) { if ( $this->tracer ) { $this->tracer->end_span( $id ); } } } vendor/perfopsone/decalog/src/Exception/InvalidSlugException.php 0000644 00000000354 15174670627 0021176 0 ustar 00 <?php /** * Invalid slug exception definition. * * @package SDK * @author Pierre Lannoy <https://pierre.lannoy.fr/>. * @since 1.0.0 */ namespace DecaLog\Exception; class InvalidSlugException extends \OutOfBoundsException { } vendor/perfopsone/decalog/src/Exception/InvalidLoggerException.php 0000644 00000000364 15174670627 0021504 0 ustar 00 <?php /** * Invalid logger exception definition. * * @package SDK * @author Pierre Lannoy <https://pierre.lannoy.fr/>. * @since 1.0.0 */ namespace DecaLog\Exception; class InvalidLoggerException extends \InvalidArgumentException { } vendor/perfopsone/decalog/src/MetricsLogger.php 0000644 00000016520 15174670627 0015710 0 ustar 00 <?php /** * DecaLog monitor definition. * * @package SDK * @author Pierre Lannoy <https://pierre.lannoy.fr/>. * @since 1.0.0 */ namespace DecaLog; /** * DecaLog monitor class. * * This class defines all code necessary to monitor metrics with DecaLog. * If DecaLog is not installed, it will do nothing and will not throw errors. * * @package SDK * @author Pierre Lannoy <https://pierre.lannoy.fr/>. * @since 1.0.0 */ class MetricsLogger { /** * The "true" DMonitor instance. * * @since 1.0.0 * @var \Decalog\Plugin\Feature\DMonitor $monitor Maintains the internal DMonitor instance. */ private $monitor = null; /** * Initialize the class and set its properties. * * @param string $class The class identifier, must be a value in ['plugin', 'theme']. * @param string $name Optional. The name of the component that will trigger events. * @param string $version Optional. The version of the component that will trigger events. * @since 1.0.0 */ public function __construct( $class, $name = null, $version = null ) { if ( class_exists( '\Decalog\Plugin\Feature\DMonitor' ) ) { $this->monitor = new \Decalog\Plugin\Feature\DMonitor( $class, $name, $version ); } } /** * Create the named counter, in production profile. * * @param string $name The unique name of the counter. * @param string $help Optional. The help string associated with this counter. * @since 1.0.0 */ public function createProdCounter( $name, $help = null ) { if ( $this->monitor ) { $this->monitor->create_prod_counter( $name, $help ); } } /** * Increments the named counter, in production profile. * * @param string $name The unique name of the counter. * @param int|float $value Optional. The value of how much to increment. * @since 1.0.0 */ public function incProdCounter( $name, $value = 1 ) { if ( $this->monitor ) { $this->monitor->inc_prod_counter( $name, $value ); } } /** * Create the named counter, in development profile. * * @param string $name The unique name of the counter. * @param string $help Optional. The help string associated with this counter. * @since 1.0.0 */ public function createDevCounter( $name, $help = null ) { if ( $this->monitor ) { $this->monitor->create_dev_counter( $name, $help ); } } /** * Increments the named counter, in development profile. * * @param string $name The unique name of the counter. * @param int|float $value Optional. The value of how much to increment. * @since 1.0.0 */ public function incDevCounter( $name, $value = 1 ) { if ( $this->monitor ) { $this->monitor->inc_dev_counter( $name, $value ); } } /** * Create and set the named gauge, in production profile. * * @param string $name The unique name of the gauge. * @param int|float $value The initial value to set. * @param string $help Optional. The help string associated with this gauge. * @since 1.0.0 */ public function createProdGauge( $name, $value = 0, $help = null ) { if ( $this->monitor ) { $this->monitor->create_prod_gauge( $name, $value, $help ); } } /** * Sets the named gauge, in production profile. * * @param string $name The unique name of the gauge. * @param int|float $value The value to set. * @since 1.0.0 */ public function setProdGauge( $name, $value ) { if ( $this->monitor ) { $this->monitor->set_prod_gauge( $name, $value ); } } /** * Increments the named gauge, in production profile. * * @param string $name The unique name of the gauge. * @param int|float $value Optional. The value of how much to increment. * @since 1.0.0 */ public function incProdGauge( $name, $value = 1 ) { if ( $this->monitor ) { $this->monitor->inc_prod_gauge( $name, $value ); } } /** * Decrements the named gauge, in production profile. * * @param string $name The unique name of the gauge. * @param int|float $value Optional. The value of how much to decrement. * @since 1.0.0 */ public function decProdGauge( $name, $value = 1 ) { if ( $this->monitor ) { $this->monitor->inc_prod_gauge( $name, - $value ); } } /** * Create and set the named gauge, in development profile. * * @param string $name The unique name of the gauge. * @param int|float $value The initial value to set. * @param string $help Optional. The help string associated with this gauge. * @since 1.0.0 */ public function createDevGauge( $name, $value = 0, $help = null ) { if ( $this->monitor ) { $this->monitor->create_dev_gauge( $name, $value, $help ); } } /** * Sets the named gauge, in development profile. * * @param string $name The unique name of the gauge. * @param int|float $value The value to set. * @since 1.0.0 */ public function setDevGauge( $name, $value ) { if ( $this->monitor ) { $this->monitor->set_dev_gauge( $name, $value ); } } /** * Increments the named gauge, in development profile. * * @param string $name The unique name of the gauge. * @param int|float $value Optional. The value of how much to increment. * @since 1.0.0 */ public function incDevGauge( $name, $value = 1 ) { if ( $this->monitor ) { $this->monitor->inc_dev_gauge( $name, $value ); } } /** * Decrements the named gauge, in development profile. * * @param string $name The unique name of the gauge. * @param int|float $value Optional. The value of how much to decrement. * @since 1.0.0 */ public function decDevGauge( $name, $value = 1 ) { if ( $this->monitor ) { $this->monitor->inc_dev_gauge( $name, - $value ); } } /** * Creates the named histogram, in production profile. * * @param string $name The unique name of the histogram. * @param null|array $buckets Optional. The buckets. * @param string $help Optional. The help string associated with this histogram. * @since 1.0.0 */ private function createProdHistogram( $name, $buckets = null, $help = '' ) { if ( $this->monitor ) { $this->monitor->create_prod_histogram( $name, $buckets, $help ); } } /** * Adds an observation to the named histogram, in production profile. * * @param string $name The unique name of the histogram. * @param int|float $value The value to add. * @since 1.0.0 */ public function observeProdHistogram( $name, $value ) { if ( $this->monitor ) { $this->monitor->observe_prod_histogram( $name, $value ); } } /** * Creates the named histogram, in development profile. * * @param string $name The unique name of the histogram. * @param null|array $buckets Optional. The buckets. * @param string $help Optional. The help string associated with this histogram. * @since 1.0.0 */ private function createDevHistogram( $name, $buckets = null, $help = '' ) { if ( $this->monitor ) { $this->monitor->create_dev_histogram( $name, $buckets, $help ); } } /** * Adds an observation to the named histogram, in development profile. * * @param string $name The unique name of the histogram. * @param int|float $value The value to add. * @since 1.0.0 */ public function observeDevHistogram( $name, $value ) { if ( $this->monitor ) { $this->monitor->observe_dev_histogram( $name, $value ); } } } vendor/perfopsone/decalog/src/Engine.php 0000644 00000021254 15174670627 0014347 0 ustar 00 <?php /** * DecaLog SDK main engine. * * @package SDK * @author Pierre Lannoy <https://pierre.lannoy.fr/>. * @since 1.0.0 */ namespace DecaLog; /** * DecaLog engine class. * * This class defines all code necessary to manage DecaLog operations. * * @package SDK * @author Pierre Lannoy <https://pierre.lannoy.fr/>. * @since 1.0.0 */ class Engine { /** * The engine version. * * @since 1.0.0 * @var string $version Maintains the engine version. */ private static $version = '2.0.0'; /** * The logger instances and parameters. * * @since 1.0.0 * @var array $loggers Maintains the loggers list. */ private static $loggers = []; /** * Registers a new logger. * * @param string $class The class identifier, must be a value in ['plugin', 'theme']. * @param string $slug The slug identifier. * @param string $name The name of the component that will trigger events. * @param string $version The version of the component that will trigger events. * @param string $icon Optional. The base64-encoded image for the plugin logo. Preferably an SVG image. * @since 1.0.0 */ private static function init( $class, $slug, $name, $version, $icon = '' ) { if ( is_string( $slug ) && '' !== $slug ) { static::$loggers[ $slug ] = [ 'logging' => null, 'monitoring' => null, 'tracing' => null, 'class' => $class, 'name' => (string) $name, 'version' => (string) $version, 'icon' => (string) $icon, ]; } else { throw new \DecaLog\Exception\InvalidSlugException( 'The slug is not a valid, non-empty string.' ); } } /** * Verify if DecaLog is activated. * * @return boolean True if DecaLog is activated, false otherwise. * @since 1.0.0 */ public static function isDecalogActivated() { return class_exists( '\Decalog\Plugin\Feature\DLogger' ); } /** * Get the version of DecaLog SDK. * * @return string The (SemVer) SDK version. * @since 1.0.0 */ public static function getSdkVersion() { return self::$version; } /** * Get the version of DecaLog. * * @return string The (SemVer) DecaLog version. * @since 1.0.0 */ public static function getDecalogVersion() { if ( defined( 'DECALOG_VERSION' ) ) { return DECALOG_VERSION; } return ''; } /** * Get the full version string. * * @return string The full version string. * @since 1.0.0 */ public static function getVersionString() { if ( self::isDecalogActivated() ) { if ( ! defined( 'DECALOG_PRODUCT_NAME' ) ) { define( 'DECALOG_PRODUCT_NAME', 'DecaLog' ); } return DECALOG_PRODUCT_NAME . ' ' . self::getDecalogVersion() . ' (SDK ' . self::getSdkVersion() . ')'; } return ''; } /** * Get the loggers list. * * @return array The loggers list. * @since 1.0.0 */ public static function getLoggers() { $result = []; foreach ( static::$loggers as $slug => $logger ) { $result[ $slug ]['name'] = $logger['name']; $result[ $slug ]['version'] = $logger['version']; $result[ $slug ]['icon'] = $logger['icon']; } return $result; } /** * Registers a new theme logger. * * @param string $slug The slug identifier. * @param string $name The name of the theme that will trigger events. * @param string $version The version of the theme that will trigger events. * @param string $icon Optional. The base64-encoded image for the plugin logo. Preferably an SVG image. * @since 1.0.0 */ public static function initTheme( $slug, $name, $version, $icon = '' ) { static::init( 'theme', $slug, $name, $version, $icon ); } /** * Registers a new plugin logger. * * @param string $slug The slug identifier. * @param string $name The name of the plugin that will trigger events. * @param string $version The version of the plugin that will trigger events. * @param string $icon Optional. The base64-encoded image for the plugin logo. Preferably an SVG image. * @since 1.0.0 */ public static function initPlugin( $slug, $name, $version, $icon = '' ) { static::init( 'plugin', $slug, $name, $version, $icon ); } /** * Registers a new logger. * * @param string $slug The slug identifier. * @return string The string index if logger is registered, empty string otherwise. * @since 1.0.0 */ private static function getLoggerSlug( $slug ) { if ( is_string( $slug ) && '' !== $slug && array_key_exists( $slug, static::$loggers ) ) { return $slug; } return ''; } /** * Get a registered events logger. * * @param string $slug The slug identifier. * @return \DecaLog\EventsLogger The corresponding events logger. * @throws \DecaLog\Exception\InvalidSlugException * @since 1.0.0 */ public static function eventsLogger( $slug ) { $slug = static::getLoggerSlug( $slug ); if ( '' === $slug ) { throw new \DecaLog\Exception\InvalidSlugException( 'No registered logger with this slug.' ); } if ( ! static::$loggers[ $slug ]['logging'] ) { static::$loggers[ $slug ]['logging'] = new \DecaLog\EventsLogger( static::$loggers[ $slug ]['class'], static::$loggers[ $slug ]['name'], static::$loggers[ $slug ]['version'] ); } return static::$loggers[ $slug ]['logging']; } /** * Get a registered metrics logger. * * @param string $slug The slug identifier. * @return \DecaLog\MetricsLogger The corresponding metrics logger. * @throws \DecaLog\Exception\InvalidSlugException * @since 1.0.0 */ public static function metricsLogger( $slug ) { $slug = static::getLoggerSlug( $slug ); if ( '' === $slug ) { throw new \DecaLog\Exception\InvalidSlugException( 'No registered logger with this slug.' ); } if ( ! static::$loggers[ $slug ]['monitoring'] ) { static::$loggers[ $slug ]['monitoring'] = new \DecaLog\MetricsLogger( static::$loggers[ $slug ]['class'], static::$loggers[ $slug ]['name'], static::$loggers[ $slug ]['version'] ); } return static::$loggers[ $slug ]['monitoring']; } /** * Get a registered traces logger. * * @param string $slug The slug identifier. * @return \DecaLog\TracesLogger The corresponding traces logger. * @throws \DecaLog\Exception\InvalidSlugException * @since 1.0.0 */ public static function tracesLogger( $slug ) { $slug = static::getLoggerSlug( $slug ); if ( '' === $slug ) { throw new \DecaLog\Exception\InvalidSlugException( 'No registered logger with this slug.' ); } if ( ! static::$loggers[ $slug ]['tracing'] ) { static::$loggers[ $slug ]['tracing'] = new \DecaLog\TracesLogger( static::$loggers[ $slug ]['class'], static::$loggers[ $slug ]['name'], static::$loggers[ $slug ]['version'] ); } return static::$loggers[ $slug ]['tracing']; } /** * Generates a v4 UUID. * * @since 1.2.0 * @return string A v4 UUID. */ private static function generate_v4() { return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', // phpcs:disable mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0x0fff ) | 0x4000, mt_rand( 0, 0x3fff ) | 0x8000, mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ) // phpcs:enabled ); } /** * Generates a (pseudo) unique ID. * This function does not generate cryptographically secure values, and should not be used for cryptographic purposes. * * @param integer $length The length of the ID. * @return string The unique ID. * @since 1.2.0 */ public static function generate_unique_id( $length = 8 ) { $result = ''; do { $s = self::generate_v4(); $s = str_replace( '-', date( 'his' ), $s ); $result .= $s; $l = strlen( $result ); } while ( $l < $length ); return substr( str_shuffle( $result ), 0, $length ); } } if ( ! defined( 'DECALOG_MAX_SHUTDOWN_PRIORITY' ) ) { define( 'DECALOG_MAX_SHUTDOWN_PRIORITY', PHP_INT_MAX - 1000 ); } if ( ! defined( 'DECALOG_SPAN_MUPLUGINS_LOAD' ) ) { define( 'DECALOG_SPAN_MUPLUGINS_LOAD', \DecaLog\Engine::generate_unique_id() ); } if ( ! defined( 'DECALOG_SPAN_PLUGINS_LOAD' ) ) { define( 'DECALOG_SPAN_PLUGINS_LOAD', \DecaLog\Engine::generate_unique_id() ); } if ( ! defined( 'DECALOG_SPAN_THEME_SETUP' ) ) { define( 'DECALOG_SPAN_THEME_SETUP', \DecaLog\Engine::generate_unique_id() ); } if ( ! defined( 'DECALOG_SPAN_USER_AUTHENTICATION' ) ) { define( 'DECALOG_SPAN_USER_AUTHENTICATION', \DecaLog\Engine::generate_unique_id() ); } if ( ! defined( 'DECALOG_SPAN_PLUGINS_INITIALIZATION' ) ) { define( 'DECALOG_SPAN_PLUGINS_INITIALIZATION', \DecaLog\Engine::generate_unique_id() ); } if ( ! defined( 'DECALOG_SPAN_MAIN_RUN' ) ) { define( 'DECALOG_SPAN_MAIN_RUN', \DecaLog\Engine::generate_unique_id() ); } if ( ! defined( 'DECALOG_SPAN_SHUTDOWN' ) ) { define( 'DECALOG_SPAN_SHUTDOWN', \DecaLog\Engine::generate_unique_id() ); } vendor/perfopsone/decalog/src/EventsLogger.php 0000644 00000024061 15174670627 0015545 0 ustar 00 <?php /** * DecaLog PSR-3 logger definition. * * @package SDK * @author Pierre Lannoy <https://pierre.lannoy.fr/>. * @since 1.0.0 */ namespace DecaLog; /** * DecaLog PSR-3 logger class. * * This class defines all code necessary to log events with DecaLog. * If DecaLog is not installed, it will do nothing and will not throw errors. * * @package SDK * @author Pierre Lannoy <https://pierre.lannoy.fr/>. * @since 1.0.0 */ class EventsLogger implements \Psr\Log\LoggerInterface { /** * The "true" logger instances. * * @since 1.0.0 * @var array $loggers Maintains the internal loggers list. */ private $loggers = []; /** * Initialize the class and set its properties. * * @param string $class The class identifier, must be a value in ['plugin', 'theme']. * @param string $name Optional. The name of the component that will trigger events. * @param string $version Optional. The version of the component that will trigger events. * @since 1.0.0 */ public function __construct( $class, $name = null, $version = null ) { if ( class_exists( '\Decalog\Plugin\Feature\DLogger' ) ) { $this->loggers[] = new \Decalog\Plugin\Feature\DLogger( $class, $name, $version, null, true ); } } /** * Adds a local logger. * "Local" means specific to your theme or plugin. It is only needed in case your theme or plugin already implements * a logger and you want to continue to log things with it at the same time as with DecaLog. * * @param object $logger The local logger to add. Must implement \Psr\Log\LoggerInterface. * @throws \DecaLog\Exception\InvalidLoggerException * @since 1.0.0 */ public function addLocalLogger( $logger ) { if ( $logger instanceof \Psr\Log\LoggerInterface ) { $this->loggers[] = $logger; } else { throw new \DecaLog\Exception\InvalidLoggerException( 'The logger do not implement \Psr\Log\LoggerInterface.' ); } } /** * Logs a panic condition. WordPress is unusable. * * @param string $message The message to log. * @param array $context Optional. The context of the event. * FYI, DecaLog has its own context-aware logging system. The only element of context * that you can pass to DecaLog is a numerical error code ($context['code']). All other * element of context will be removed. * @return void * @since 1.0.0 */ public function emergency( $message, $context = [] ) { if ( ! is_array( $context ) ) { $context = [ 'code' => 30973 ]; $this->debug( 'Wrong method argument: `$context` must be an array.', $context ); } foreach ( $this->loggers as $logger ) { $logger->emergency( (string) $message, array_key_exists( 'code', $context ) ? (int) $context['code'] : 0 ); } } /** * Logs a major operating error that undoubtedly affects the operations. * It requires immediate investigation and corrective treatment. * * @param string $message The message to log. * @param array $context Optional. The context of the event. * FYI, DecaLog has its own context-aware logging system. The only element of context * that you can pass to DecaLog is a numerical error code ($context['code']). All other * element of context will be removed. * @return void * @since 1.0.0 */ public function alert( $message, $context = [] ) { if ( ! is_array( $context ) ) { $context = [ 'code' => 30973 ]; $this->debug( 'Wrong method argument: `$context` must be an array.', $context ); } foreach ( $this->loggers as $logger ) { $logger->alert( (string) $message, array_key_exists( 'code', $context ) ? (int) $context['code'] : 0 ); } } /** * Logs an operating error that undoubtedly affects the operations. * It requires investigation and corrective treatment. * * @param string $message The message to log. * @param array $context Optional. The context of the event. * FYI, DecaLog has its own context-aware logging system. The only element of context * that you can pass to DecaLog is a numerical error code ($context['code']). All other * element of context will be removed. * @return void * @since 1.0.0 */ public function critical( $message, $context = [] ) { if ( ! is_array( $context ) ) { $context = [ 'code' => 30973 ]; $this->debug( 'Wrong method argument: `$context` must be an array.', $context ); } foreach ( $this->loggers as $logger ) { $logger->critical( (string) $message, array_key_exists( 'code', $context ) ? (int) $context['code'] : 0 ); } } /** * Logs a minor operating error that may affects the operations. * It requires investigation and preventive treatment. * * @param string $message The message to log. * @param array $context Optional. The context of the event. * FYI, DecaLog has its own context-aware logging system. The only element of context * that you can pass to DecaLog is a numerical error code ($context['code']). All other * element of context will be removed. * @return void * @since 1.0.0 */ public function error( $message, $context = [] ) { if ( ! is_array( $context ) ) { $context = [ 'code' => 30973 ]; $this->debug( 'Wrong method argument: `$context` must be an array.', $context ); } foreach ( $this->loggers as $logger ) { $logger->error( (string) $message, array_key_exists( 'code', $context ) ? (int) $context['code'] : 0 ); } } /** * Logs a significant condition indicating a situation that may lead to an error if recurring or if no action is taken. * Does not usually affect the operations. * * @param string $message The message to log. * @param array $context Optional. The context of the event. * FYI, DecaLog has its own context-aware logging system. The only element of context * that you can pass to DecaLog is a numerical error code ($context['code']). All other * element of context will be removed. * @return void * @since 1.0.0 */ public function warning( $message, $context = [] ) { if ( ! is_array( $context ) ) { $context = [ 'code' => 30973 ]; $this->debug( 'Wrong method argument: `$context` must be an array.', $context ); } foreach ( $this->loggers as $logger ) { $logger->warning( (string) $message, array_key_exists( 'code', $context ) ? (int) $context['code'] : 0 ); } } /** * Logs a normal but significant condition. * * @param string $message The message to log. * @param array $context Optional. The context of the event. * FYI, DecaLog has its own context-aware logging system. The only element of context * that you can pass to DecaLog is a numerical error code ($context['code']). All other * element of context will be removed. * @return void * @since 1.0.0 */ public function notice( $message, $context = [] ) { if ( ! is_array( $context ) ) { $context = [ 'code' => 30973 ]; $this->debug( 'Wrong method argument: `$context` must be an array.', $context ); } foreach ( $this->loggers as $logger ) { $logger->notice( (string) $message, array_key_exists( 'code', $context ) ? (int) $context['code'] : 0 ); } } /** * Logs a standard information. * * @param string $message The message to log. * @param array $context Optional. The context of the event. * FYI, DecaLog has its own context-aware logging system. The only element of context * that you can pass to DecaLog is a numerical error code ($context['code']). All other * element of context will be removed. * @return void * @since 1.0.0 */ public function info( $message, $context = [] ) { if ( ! is_array( $context ) ) { $context = [ 'code' => 30973 ]; $this->debug( 'Wrong method argument: `$context` must be an array.', $context ); } foreach ( $this->loggers as $logger ) { $logger->info( (string) $message, array_key_exists( 'code', $context ) ? (int) $context['code'] : 0 ); } } /** * Logs an information for developers and testers. * Only used for events related to application/system debugging. * * @param string $message The message to log. * @param array $context Optional. The context of the event. * FYI, DecaLog has its own context-aware logging system. The only element of context * that you can pass to DecaLog is a numerical error code ($context['code']). All other * element of context will be removed. * @return void * @since 1.0.0 */ public function debug( $message, $context = [] ) { if ( ! is_array( $context ) ) { $context = [ 'code' => 30973 ]; $this->debug( 'Wrong method argument: `$context` must be an array.', $context ); } foreach ( $this->loggers as $logger ) { $logger->debug( (string) $message, array_key_exists( 'code', $context ) ? (int) $context['code'] : 0 ); } } /** * Logs an information with an arbitrary level. * * @param \Psr\Log\LogLevel $level The level of the message to log. * @param string $message The message to log. * @param array $context Optional. The context of the event. * FYI, DecaLog has its own context-aware logging system. The only element of context * that you can pass to DecaLog is a numerical error code ($context['code']). All other * element of context will be removed. * @return void * @since 1.0.0 */ public function log( $level, $message, $context = [] ) { if ( ! is_array( $context ) ) { $context = [ 'code' => 30973 ]; $this->debug( 'Wrong method argument: `$context` must be an array.', $context ); } foreach ( $this->loggers as $logger ) { $logger->log( $level, (string) $message, array_key_exists( 'code', $context ) ? (int) $context['code'] : 0 ); } } } free/data/cookiehash.phps 0000644 00000001322 15174670627 0011424 0 ustar 00 <?php /** * Plugin Name: {{PLUGIN_NAME}} COOKIEHASH * Description: Change the default COOKIEHASH constant value to prevent easy guessing. * Version: 2.3.16 * License: GPLv2 * License URI: http://www.gnu.org/licenses/gpl-2.0.html * * Copyright 2012-2025 SecuPress */ defined( 'ABSPATH' ) or die( 'Something went wrong.' ); if ( defined( 'SECUPRESS_COOKIEHASH_MODULE_ACTIVE' ) ) { @unlink( __FILE__ ); // We are in a duplicated file, should not happen, delete us! return; } if ( ! get_site_option( 'secupress_active_submodule_wp-config-constant-cookiehash' ) || defined( 'COOKIEHASH' ) ) { return; } define( 'SECUPRESS_COOKIEHASH_MODULE_ACTIVE', true ); define( 'COOKIEHASH', md5( __FILE__ . '{{HASH}}' ) ); free/data/bad_user_agents.data 0000644 00000006061 15174670627 0012400 0 ustar 00 Gecko/2009032609 Firefox,ADSARobot,ah-ha,almaden,aktuelles,Anarchie,amzn_assoc,ASPSeek,ASSORT,ATHENS,Atomz,attach,autoemailspider,BackWeb,Bandit,BatchFTP,bdfetch,big.brother,BlackWidow,bmclient,Boston Project,BravoBrian SpiderEngine MarcoPolo,Bot mailto:craftbot@yahoo.com,Buddy,Bullseye,bumblebee,capture,CherryPicker,ChinaClaw,CICC,clipping,Collector,Copier,Crescent,Crescent Internet ToolPak,Custo,cyberalert,Deweb,diagem,Digger,Digimarc,DIIbot,DISCo,DISCo Pump,DISCoFinder,Download Demon,Download Wonder,Downloader,Drip,DSurf15a,DTS.Agent,EasyDL,eCatch,ecollector,efp@gmx.net,Email Extractor,EirGrabber,EmailCollector,EmailSiphon,EmailWolf,Express WebPictures,ExtractorPro,EyeNetIE,FavOrg,fastlwspider,Favorites Sweeper,FEZhead,FileHound,FlashGet WebWasher,FlickBot,fluffy,FrontPage,GalaxyBot,Generic,Getleft,GetRight,GetSmart,GetWeb!,GetWebPage,gigabaz,Girafabot,Go!Zilla,Go!Zilla,Go-Ahead-Got-It,GornKer,gotit,Grabber,GrabNet,Grafula,Green Research,grub-client,Harvest,hhjhj@yahoo,hloader,HMView,HomePageSearch,http generic,HTTrack,httpdown,httrack,ia_archiver,IBM_Planetwide,Image Stripper,Image Sucker,imagefetch,IncyWincy,Indy Library,informant,Ingelin,InterGET,Internet Ninja,InternetLinkagent,Internet Ninja,InternetSeer.com,Iria,Irvine,JBH agent,JetCar,JOC,JOC Web Spider,JustView,KWebGet,Lachesis,larbin,LeechFTP,LexiBot,lftp,libwww,likse,Link*Sleuth,LINKS ARoMATIZED,LinkWalker,LWP,lwp-trivial,Mag-Net,Magnet,Mac Finder,Mag-Net,Mass Downloader,MCspider,Memo,Microsoft.URL,MIDown tool,Mirror,Missigua Locator,Mister PiX,MMMtoCrawl/UrlDispatcherLLL,Mozilla.*Indy,Mozilla.*NEWT,Mozilla.*MSIECrawler,MS FrontPage,MSIECrawler,MSProxy,multithreaddb,nationaldirectory,Navroad,NearSite,NetAnts,NetCarta,NetMechanic,netprospector,NetResearchServer,NetSpider,Net Vampire,NetZIP,NetZip Downloader,NetZippy,NEWT,NICErsPRO,Ninja,NPBot,Octopus,Offline Explorer,Offline Navigator,OpaL,Openfind,OpenTextSiteCrawler,PageGrabber,Papa Foto,PackRat,pavuk,pcBrowser,PersonaPilot,PingALink,Pockey,psbot,PSurf,puf,Pump,PushSite,QRVA,RealDownload,Reaper,Recorder,ReGet,replacer,RepoMonkey,Robozilla,Rover,RPT-HTTPClient,Rsync,Scooter,SearchExpress,searchhippo,searchterms.it,Second Street Research,Seeker,Shai,Siphon,sitecheck,sitecheck.internetseer.com,SiteSnagger,SlySearch,SmartDownload,snagger,Snake,SpaceBison,Spegla,SpiderBot,sproose,SqWorm,Stripper,Sucker,SuperBot,SuperHTTP,Surfbot,SurfWalker,Szukacz,tAkeOut,tarspider,Teleport Pro,Templeton,TrueRobot,TV33_Mercator,UIowaCrawler,UtilMind,URLSpiderPro,URL_Spider_Pro,Vacuum,vagabondo,vayala,visibilitygap,VoidEYE,vspider,Web Downloader,w3mir,Web Data Extractor,Web Image Collector,Web Sucker,Wweb,WebAuto,WebBandit,web.by.mail,Webclipping,webcollage,webcollector,WebCopier,webcraft@bea,webdevil,webdownloader,Webdup,WebEMailExtrac,WebFetch,WebGo IS,WebHook,Webinator,WebLeacher,WEBMASTERS,WebMiner,WebMirror,webmole,WebReaper,WebSauger,Website,Website eXtractor,Website Quester,WebSnake,Webster,WebStripper,websucker,webvac,webwalk,webweasel,WebWhacker,WebZIP,Whacker,whizbang,WhosTalking,Widow,WISEbot,WWWOFFLE,x-Tractor,Xaldon WebSpider,WUMPUS,Xenu,XGET,Zeus.*Webster,Zeus free/data/not-updated-in-over-two-years-plugin-list.data 0000644 00000000000 15174670627 0017234 0 ustar 00 free/data/no-longer-in-directory-plugin-list.data 0000644 00000000000 15174670627 0016011 0 ustar 00 free/data/db-error.phps 0000644 00000003114 15174670627 0011024 0 ustar 00 <?php /** * {{PLUGIN_NAME}} DB Error Bail Message * @since 2.3.13 * @author Julio Potier * @license GPLv2 * @see $wpdb->db_connect() * * Copyright 2012-2025 SecuPress */ /*/ DO NOT USE OUR TEXTDOMAIN FOR I18N, WE HAVE TO FAKE THE WP ONE HERE /*/ $message = '<h1>' . __( 'Error establishing a database connection' ) . "</h1>\n"; $message .= '<p>' . sprintf( __( 'This either means that the username and password information in your %1$s file is incorrect or that contact with the database server at %2$s could not be established. This could mean your host’s database server is down.' ), '<code>wp-config.php</code>', '<code>DB_HOST</code>' ) . "</p>\n"; $message .= "<ul>\n"; $message .= '<li>' . __( 'Are you sure you have the correct username and password?' ) . "</li>\n"; $message .= '<li>' . __( 'Are you sure you have typed the correct hostname?' ) . "</li>\n"; $message .= '<li>' . __( 'Are you sure the database server is running?' ) . "</li>\n"; $message .= "</ul>\n"; $message .= '<p>' . sprintf( __( 'If you are unsure what these terms mean you should probably contact your host. If you still need help you can always visit the <a href="%s">WordPress support forums</a>.' ), __( 'https://wordpress.org/support/forums/' ) ) . "</p>\n"; if ( defined( 'SECUPRESS_LOCKED_ADMIN_EMAIL' ) ) { $fname = ABSPATH . '/.secupress_db_down_flag'; $host = isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : false; if ( ! $host ) { return; // Impossible to continue here... } if ( ! @file_exists( $fname ) ) { @file_put_contents( $fname, time() ); } } wp_die( $message ); free/data/10kmostcommon.data 0000644 00000323317 15174670627 0011770 0 ustar 00 **** ***** ****** ******* ******** ********* ********** 0.0.0.000 0.0.000 0000 00000 000000 0000000 00000000 0000000000 0000007 000001 000007 0001 000111 000123 0007 000777 0069 007007 007bond 0101 010101 01011 01011900 01011910 01011960 01011970 01011971 01011972 01011973 01011974 01011975 01011976 01011977 01011978 01011979 01011980 01011981 01011982 01011983 01011984 01011985 01011986 01011987 01011988 01011989 01011990 01011991 01011992 01011993 01011994 01011995 01011999 01012000 01012001 01012009 01012010 01012011 010180 010191 010203 01020304 01021985 01021987 01021988 01021989 01021990 01021992 01022010 01031981 01031983 01031984 01031985 01031986 01031988 01031989 010390 01041980 01041983 01041985 01041987 01041988 01041990 01041992 01041993 01051980 01051985 01051986 01051988 01051989 01051990 01061983 01061986 01061987 01061988 01061990 01061992 01071984 01071986 01071987 01071988 01071990 01081985 01081989 01081990 01081992 01091985 01091987 01091988 01091989 01091992 01101987 01121986 01121987 01121988 01121990 0123 012345 0123456 01234567 0123456789 0192837465 02011971 02011975 02011976 02011977 02011979 02011980 02011981 02011982 02011983 02011984 02011985 02011986 02011987 02011988 02011989 02011990 0202 020202 02021971 02021973 02021976 02021978 02021979 02021980 02021981 02021982 02021983 02021984 02021985 02021986 02021987 02021988 02021989 02021990 02021991 02031973 02031974 02031975 02031977 02031978 02031979 02031980 02031981 02031982 02031983 02031984 02031985 02031986 02031987 02031988 02031989 02031990 02031991 02041972 02041973 02041974 02041975 02041976 02041977 02041978 02041979 02041980 02041981 02041982 02041983 02041984 02041985 02041986 02041987 02041988 02041989 02051970 02051972 02051973 02051975 02051976 02051977 02051978 02051979 02051980 02051981 02051982 02051983 02051984 02051985 02051986 02051987 02051988 02051989 02051990 02061971 02061972 02061974 02061976 02061977 02061978 02061979 02061980 02061981 02061982 02061983 02061984 02061985 02061986 02061987 02061988 02061989 02061990 02071971 02071975 02071976 02071977 02071978 02071979 02071980 02071981 02071982 02071983 02071984 02071985 02071986 02071987 02071988 02071989 02081970 02081973 02081974 02081976 02081977 02081979 02081980 02081981 02081982 02081983 02081984 02081985 02081986 02081987 02081988 02081989 02091971 02091973 02091975 02091976 02091977 02091978 02091980 02091981 02091982 02091983 02091984 02091985 02091986 02091987 02091988 02091989 02101973 02101976 02101977 02101978 02101979 02101980 02101981 02101983 02101984 02101985 02101986 02101987 02101988 02101989 02111987 03011987 03011991 03021986 030303 03031984 03031986 03031987 03031988 03031990 03031991 03031992 03031993 03041980 03041983 03041984 03041986 03041987 03041989 03041991 03051986 03051987 03051988 03061985 03061986 03061987 03061988 03071985 03071986 03071987 03081989 03082006 03091983 03091988 03101991 0311 03111987 04021990 04031991 040404 04041983 04041985 04041986 04041987 04041988 04041990 04041991 04051985 04051988 04061984 04061986 04061987 04061991 04071986 04071987 04071988 04081987 04091986 04111988 04111991 0420 05011987 05021987 05021988 05031987 05031990 05031991 05041985 050505 05051985 05051986 05051987 05051989 05051990 05051991 05061986 05061988 05061989 05061990 05071984 05071985 05071988 05081986 05081988 05081992 05091987 05091988 05111986 05121988 05121990 06011982 06011988 06021986 06021987 06031983 06031992 06041984 06041987 06041988 06051986 060606 06061981 06061985 06061986 06061987 06061988 06071983 06081987 06101989 063dyjuy 0660 07021980 07021991 07031989 07041987 07041988 07041989 070462 07051987 07051990 07061988 070707 07071977 07071982 07071984 07071985 07071987 07071988 07071989 07071990 07081984 07081986 07081987 07091982 07091988 07091990 07101984 07101987 08011986 08021990 08031985 08031986 08031987 08041985 08041986 08051987 08051989 08051990 08061987 08071985 08071987 08071988 080808 08081986 08081988 08081989 08081990 08101986 08111984 08121986 08121987 0815 085tzzqi 09021988 09021989 09031987 09031988 09041985 09041986 09041987 09051945 09051984 09051986 09051987 09081985 09081988 090909 09090909 09091986 09091988 0911 09111987 0987 098765 09876543 0987654321 1000 100000 1000000 1001 100100 10011980 10011983 10011986 10011988 10011990 10011992 1002 100200 10021986 10021987 1003 10031980 10031987 10031988 10031989 10031990 10031991 10031993 1004 10041983 10041984 10041986 10041987 10041990 10041991 1005 100500 10051987 10051988 10051990 10061984 10061985 10061986 10061987 10061989 1007 10071985 10071986 10071987 10071988 10071989 10071990 1008 10081983 10081985 10081987 10081989 10081990 1009 10091984 10091985 10091986 1010 10101 101010 10101010 10101980 10101985 10101986 10101988 10101989 10101990 101091m 1011 101101 101112 10111986 1012 10121985 10121986 10121987 1013 1014 1015 1016 1017 1018 1019 101–10000 1020 10203 102030 10203040 1021 1022 1023 1024 1025 1026 1027 1028 1029 102938 10293847 1029384756 1030 1031 1066 11001001 1101 11011987 11011989 11011990 11011991 1102 11021985 1103 11031983 11031986 11031988 1104 11041985 11041990 11041991 11051984 11051986 11051987 11051988 11051990 11061984 11061985 11061986 11061987 11061989 11061991 1107 11071985 11071986 11071987 11071988 11071989 11081986 11081987 11081988 11081989 11081990 1109 11091984 11091985 11091986 11091989 11091990 111000 11101986 1111 11111 111111 1111111 11111111 111111111 1111111111 111111a 111111q 111112 11111986 11111987 11111991 11111a 11111q 11112222 1112 111213 11121985 11121986 11121987 111222 111222333 1113 111333 1114 1115 111555 1117 111777 111999 111qqq 1120 1121 112112 1122 112211 11221122 11223 112233 11223344 1122334455 1123 112358 11235813 1123581321 1124 1125 1126 1127 1128 1129 1130 1134 1138 115599 1200 1201 12011985 12011987 12011989 120120 1202 12021984 12021985 12021988 12021990 12021991 12031985 12031987 12031988 12031990 1204 12041986 12041988 12041990 12041991 1205 12051985 12051986 12051987 12051988 12051989 12051990 1206 12061986 12061987 12061988 120676 120689 1207 12071984 12071987 12071988 12071989 12071990 12071991 12071992 1208 12081983 12081984 12081985 12081987 12081988 12081990 12081993 1209 12091986 12091988 12091991 1210 12101984 12101985 12101988 12101989 12101990 1211 12111984 12111985 12111990 12111991 1212 12121 121212 12121212 1212121212 12121982 12121985 12121986 12121987 12121988 12121989 12121990 12121991 1213 12131213 121314 12131415 1214 1215 1216 1217 1218 1219 1220 1221 122112 12211221 1222 1223 122333 1224 1225 1226 1227 1228 1229 123 1230 123000 12301230 123098 1231 12312 123123 12312312 123123123 1231234 123123a 123123q 12321 1232323q 1233 12332 123321 123321123 123321a 123321q 1234 1234123 12341234 1234321 12344321 12345 123450 1234509876 123451 1234512345 123454321 123455 1234554321 123456 1234560 1234561 123456123 1234566 123456654321 1234567 12345678 123456789 1234567890 1234567890- 1234567890a 1234567890q 1234567891 12345678910 1234567899 123456789a 123456789aze 123456789d 123456789m 123456789q 123456789qwe 123456789s 123456789z 12345678a 12345678q 12345679 1234567a 1234567q 1234568 123456a 123456aa 123456aze 123456azerty 123456k 123456l 123456m 123456n 123456q 123456qqq 123456qw 123456qwe 123456qwerty 123456r 123456ru 123456s 123456t 123456z 123457 123459 12345a 12345abc 12345aze 12345azer 12345azert 12345azerty 12345m 12345q 12345qaz 12345qw 12345qwe 12345qwer 12345qwert 12345qwerty 12345r 12345s 12345t 12345z 123465 12348765 1234abcd 1234asdf 1234aze 1234azer 1234qsdf 1234qw 1234qwe 1234qwer 1234rewq 1234wxcv 1234zxcv 1235 123567 1235789 1236 12365 123654 12365478 123654789 1236987 12369874 123698745 123789 123789456 123890 123987 123aaa 123abc 123asd 123aze 123aze123 123azeqsd 123azeqsdwxc 123azer 123azert 123azerty 123ewq 123qaz 123qq123 123qsd 123qw 123qwe 123qwe123 123qweasd 123qweasdzxc 123qwer 123qwert 123qwerty 123wxc 123zxc 124038 1245 124578 125125 1269 128500 12locked 12qw12 12qw12qw 12qw34er 12qwas 12qwaszx 13011987 13011988 13021985 13021987 13021990 13021991 13031986 13031987 13031989 13041987 13041988 13041989 13051986 13051987 13051990 13061985 13061986 13061987 13061991 13071982 13071984 13071985 13071987 13071989 13071990 13081985 13081986 13091984 13091986 13091987 13091988 13101982 13101987 13101988 13101992 13111984 13111990 13121983 13121985 1313 131313 13131313 132435 13243546 132456 132465 1331 134679 134679852 135246 1357 13576479 13579 135790 135791 1357911 135792468 1357924680 135798642 1366613 1369 14011986 14011987 14011988 14011989 14021983 14021985 14021986 14021987 14021989 14021990 14031986 14031988 14031989 14041986 14041987 14041988 14041992 14051983 14051990 14061988 14061991 14071986 14071987 14071988 14081985 14081988 14081990 14091987 14091990 14101986 14101987 14101988 1411 14111986 14121989 1414 141414 14141414 141627 142536 142857 143143 1432 145236 1469 147147 147258 14725836 147258369 147369 147741 1478 147852 147852369 147896 1478963 14789632 147896325 147963 14881488 1492 15011983 15011985 15011986 15011987 15011988 15011990 15021983 15021985 15021986 15021990 15031988 15031990 15031991 15041987 15041988 15051981 15051985 15051986 15051987 15051989 15051990 15051992 15061984 15061985 15061988 15071983 15071985 15071986 15071987 15071988 15071990 150781 15081986 15081988 15081989 15081990 15081991 15091985 15091987 15091988 15091989 15101986 15101991 15111984 15111988 15111989 15121983 15121987 1515 151515 15151515 151nxjmt 153624 15426378 154ugeiu 159159 159357 159357a 159632 15975 159753 1598753 159951 16011986 16011987 16011989 16021987 16021988 16021990 16031986 16031988 16031990 16041985 16041988 16051985 16051987 16051988 16051989 16051990 16061985 16061986 16061987 16061988 16071987 16071991 16081986 16091987 16091988 16091990 16101986 16101987 16111982 16111990 16121986 16121987 16121991 1616 161616 1624 162534 1664 1701 17011701 17011987 17011990 17021985 17021987 17021989 17031987 17041985 17041986 17041987 17041991 17051983 17051987 17051988 17051989 17051990 17061986 17061987 17061988 17061989 17061991 17071985 17071986 17071987 17071989 17071990 17091985 17091987 17101986 17101987 17111985 17111987 17121985 17121987 1717 171717 17171717 172839 1776 18011985 18011986 18011987 18011988 18021984 18021986 18021987 18021988 18021992 18031986 18031988 18031991 18041986 18041990 18041991 18051987 18051988 18051989 18051990 18061985 18061990 18061991 18071986 18071989 18071990 18081988 18091985 18091986 18091987 18101985 18101987 18111983 18111986 18111987 1812 18121812 18121983 18121984 18121985 18121987 18121990 1818 181818 18436572 187187 1900 19011987 19011989 19021990 19021991 19031985 19031987 19041985 19041986 1905 19051983 19051986 19051987 19061985 19061987 19061990 19061991 19061992 19071986 19071988 19071989 19071990 19081986 19081987 19091983 19091988 19091990 19101986 19101987 19101990 1911 19111985 19111986 19111987 1911a1 19121988 19121989 1914 1919 191919 192837 19283746 192837465 1941 19411945 1942 1943 1944 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 1958 1959 1960 1961 1962 1963 1964 19641964 1965 19651965 1966 19661966 1967 19671967 1968 19681968 1969 19691969 196969 1970 19701970 1971 19711971 1972 19721972 1973 19731973 1974 19741974 1975 19751975 1976 19761976 1977 19771977 1978 19781978 1979 19791979 1980 19801980 1981 19811981 1982 19821982 1983 19831983 1984 19841984 1985 19851985 1986 19861986 1987 19871987 1988 19881988 1989 19891989 1990 19901990 1991 19911991 1992 19921992 1993 19931993 1994 19941994 1995 19951995 1996 19961996 1997 19971997 1998 19981998 1999 19991999 199999 1a2b3c 1a2b3c4d 1a2s3d 1a2s3d4f 1azerty 1bitch 1dallas 1dragon 1Dragon 1fuck 1letmein 1love 1master 1Master 1michael 1million 1monkey 1passwor 1Passwor 1pussy 1Pussy 1q1q1q 1q1q1q1q 1q2q3q 1q2w3e 1q2w3e4r 1q2w3e4r5 1q2w3e4r5t 1q2w3e4r5t6y 1qa2ws 1qa2ws3ed 1qaz 1qaz1qaz 1qaz2ws 1qaz2wsx 1qaz2wsx3edc 1qazxsw2 1qazxsw23edc 1qazzaq1 1qw23er4 1qwerty 1ranger 1test 1x2zkg8w 1z2x3c 1z2x3c4v 2000 200000 20002000 2001 2001112 20011983 20011988 20011989 20012001 2002 20021985 20021986 20021988 20022002 2003 20031985 20031986 20031987 20031988 20031990 20031991 20031992 20032003 2004 20041985 20041986 20041988 20041990 2005 20051983 20051985 20051987 20051988 20051989 20052005 2006 20061983 20061984 20061986 20061987 20061988 20061990 20061991 2007 20071984 20071986 20071988 2008 20081986 20081990 20081991 20082008 2009 20091986 20091988 20091991 20092009 2010 20101987 20101988 20102010 20111986 20121986 20121988 20121989 201jedlz 2020 202020 20202020 2055 20spanks 21011988 21011989 21011991 21021985 21021987 21021988 21021989 21021990 21031984 21031985 21031986 21031987 21031988 21031990 21041985 21041987 21041991 21041992 21051986 21051988 21051990 21051991 21061985 21061986 21061988 21071987 21071989 21071992 21081985 21081987 21091989 21101983 21101986 21101987 21101988 21101989 21111983 21111985 21111986 21111989 21111990 2112 21121985 21121986 21121989 21122112 2121 212121 21212121 2128506 22011985 22011986 22011988 22011992 22021985 22021986 22021988 22021989 22021990 22031984 22031986 22031991 22041985 22041986 22041987 22041988 22051986 22051987 22051988 22051989 22051991 22061941 22061984 22061985 22061987 22061988 22061989 22061990 22071983 22071984 22071985 22071986 22071987 22071988 22071989 22071990 22071991 22071992 22081983 22081986 22081991 22091984 22091986 22091988 22091990 22091991 2211 22111985 22111988 22121983 22121986 22121987 22121989 2222 22222 222222 2222222 22222222 2222222222 222333 222777 2233 223322 223344 2244 224466 2255 225588 2277 23011985 23011990 23021983 23021985 23021986 23021988 23021989 23021992 23031983 23031986 23031987 23031990 23041986 23041987 23041988 23041991 23051983 23051985 23051986 23051987 23051990 23051991 23061987 23061989 23061990 23061992 23071985 23091985 23091986 23091987 23091989 23091991 23101987 23111986 23111987 23111989 23121986 2323 232323 23232323 2345 23456 234567 23456789 235689 2369 23skidoo 24011985 24011987 24011990 24021988 24021991 24031987 24031988 24031990 24041984 24041985 24041986 24041988 24051989 24051990 24061985 24061986 24061987 24061988 24061992 24071987 24071990 24071991 24071992 24081988 24091986 24091991 24101984 24101986 24101988 24101989 24101990 24101991 24111987 24111989 24111990 24121986 24121987 24121988 24121989 2424 242424 24242424 2468 24680 246810 24682468 2469 2500 2501 25011985 25011986 25011990 25011993 25021985 25021986 25021988 25031983 25031984 25031987 25031991 25041985 25041987 25041988 25041991 25051980 25051985 25051987 25051988 25061985 25061986 25061987 25071983 25071985 25071987 25071990 25081985 25081986 25081988 25081989 25091987 25091989 25091990 25091991 25101988 25101989 25111987 25111991 25121985 25121987 2525 252525 25252525 2580 25800852 25802580 258258 258369 258456 258852 258963 26011986 26011990 26021987 26021992 26031984 26031986 26031987 26031988 26031990 26031991 26041986 26041991 26051986 26051988 26061985 26061986 26061987 26061989 26061991 26071984 26071986 26071987 26071989 26081986 26091986 26101986 26101987 26111985 26121989 2626 262626 2663 27011988 27021990 27021991 27021992 27031986 27031987 27031989 27031992 27041985 27041990 27051987 27061983 27061985 27061988 27071987 27071988 27081986 27081990 27091985 27091991 27111985 27111989 27111990 2727 272727 27272727 28011987 28011988 28021983 28021985 28021986 28021990 28021992 28031982 28041987 28041992 28051985 28051986 28051987 28051990 28061986 28061988 28071985 28071986 28071987 28081986 28081990 28101986 28121984 28121989 2828 282828 29011982 29011985 29011987 29031988 29031990 29041985 29041988 29041989 29051985 29051989 29051990 29051992 29061985 29061986 29061988 29061989 29061990 29071983 29071985 29081985 29081990 29091987 29111988 29111989 29121987 2929 292929 2fast4u 2fchbg 2hot4u 3000 3000gt 30011985 30011986 30011987 30011990 30031986 30031988 30031992 30041985 30041986 30041987 30041991 30051985 30051986 30051987 30051988 30051989 3006 30061983 30061987 30061988 30071986 30081984 30091989 30101988 30111987 30121985 30121986 30121987 30121988 3030 303030 31011987 31011990 31031987 31031988 31051982 31051985 31051987 31051991 31051993 31071990 31101987 31121985 31121986 31121987 31121988 31121990 311311 3131 313131 314159 31415926 315475 321123 321321 321321321 321456 321654 321654987 32167 321678 3232 323232 3234412 332211 3333 33333 333333 3333333 33333333 333444 333666 333777 334455 336699 3434 343434 345678 3535 353535 357159 357357 357951 362436 3636 363636 368ejhih 369258 369258147 369369 369852 369963 3728 3737 373737 380zliki 3825 383838 383pdjvl 393939 3ip76k2 3ki42x 3mpz4r 3qvqod 3some 3tmnej 3way 3x7pxr 3x7PxR 4040 404040 4121 4128 414141 415263 4200 420000 420247 420420 4226 4242 424242 426hemi 4271 427900 4321 43214321 4343 434343 4417 44332211 4444 44444 444444 4444444 44444444 445566 4545 454545 45454545 456123 456321 456456 456456456 456654 4567 45678 456789 456852 45M2DO5BS 464646 4711 4747 474747 474jdvff 4815162342 484848 4949 494949 49ers 4ever 4mnveh 4ng62t 4runner 4snz9g 4tlved 4wcqjn 4wwvte 4you 4zqauf 5000 5050 505050 50cent 50spanks 5150 515000 515051 51505150 5151 515151 5201314 5232 5252 525252 5291 5329 5353 535353 5401 5424 5432 54321 543210 5454 545454 551scasi 554uzpad 5551212 5555 55555 555555 5555555 55555555 5555555555 555666 555777 556677 55bgates 5656 565656 5678 567890 5683 56qhxs 5757 575757 57chevy 57np39 5858 585858 5lyedn 5rxypn 5wr2i7h8 5Wr2i7H8 606060 609609609 616161 616913 626262 635241 636363 6464 646464 654123 65432 654321 654654 655321 656565 66613666 6666 66666 666666 6666666 66666666 666777 6669 666999 6751520 676767 6789 686868 6969 69696 696969 69696969 6996 69camaro 6bjvpe 6chid8 6uldv8 7007 717171 727272 72d5tn 73501505 737373 74108520 741741 741852 741852963 741963 7474 747474 753159 753951 757575 7654321 766rglqy 7676 767676 7734 7753191 777333 777666 7777 77777 777777 7777777 77777777 7777777777 7777777a 777888 7779311 777999 778899 784512 786786 7878 787878 787898 7890 789123 7894 78945 789456 78945612 789456123 7894561230 7895123 7896321 789654 789654123 789789 789789789 789987 794613 797979 7bgiqk 7grout 7kbe9d 7uftyx 7uGd5HIp2J 7xm5rq 808080 818181 81fukkc 83y6pv 8520 852456 852852 8543852 858585 863abgsg 8675309 868686 875421 87654321 878787 8888 88888 888888 8888888 88888888 888999 8989 898989 8dihc6 8inches 8j4ye3uz 8J4yE3Uz 8uiazp 8vjzus 90210 902100 906090 909090 911911 911turbo 9293709b13 9379992 951357 951753 9562876 963852 963852741 963963 969696 987123 987321 987456 987456321 9876 98765 987654 9876543 98765432 987654321 9876543210 987987 9898 989898 996633 998877 999666 9999 99999 999999 9999999 99999999 999999999 9999999999 9skw5g ????? ?????? a11111 a1234 a12345 a123456 a1234567 a12345678 a123456789 a1a1a1 a1a2a3 a1b2c3 a1b2c3d4 a1s2d3 a1s2d3f4 aaa111 aaa340 aaaa aaaaa aaaaa1 aaaaaa aaaaaa1 aaaaaaa aaaaaaa1 aaaaaaaa aaaaaaaaaa aaabbb aaasss aaliyah aardvark aaron aaron1 abacab abacus abba abbey1 abbott abby abc12 abc123 ABC123 abc1234 abc12345 abcabc abcd abcd123 abcd1234 abcde abcdef abcdefg abcdefg1 abcdefgh aberdeen abgrtyu abigail abnormal abraham abrakadabra abraxas absolut absolute absolutely abstr acapulco access access1 access14 access99 accord account acdc aceace aceman acer achilles achtung acid acidburn acls2h acmilan action active acura adam adam12 adam25 adams addict addison address adelaide adelina adgjmp adgjmptw adidas admin admin1 admiral adonis adrenalin adrian adriana adriano adrienne adult adults advance advent aerosmit aezakmi africa afrika again agent agent007 aggie aggies agyvorc aikido aikman aileen aimee airborne airbus aircraft airforce airman airplane airport airwolf aisan ajax akira al9agd alabama aladin alain alan alanis alaska alatam albany albatros albert alberta alberto albina albino albion alcat alcatraz alchemy alcohol alec alejandr alejandro aleksandr aleksandra aleksey alena alenka alert alessand alessandro alex alex12 alex123 alexa alexalex alexande alexander alexandr alexandra alexandre alexey alexia alexis alfa alfarome alfred alfredo algebra alibaba alice alice1 alicia alien aliens alina alinka alisa alisha alison alissa alive all4one allan allday allegro allen allen1 alley alleycat allgood alliance allie allison allison1 allman allmine allnight allsop allstar allstate almighty almond aloha alone alpha alpha1 alpha123 alphabet alpina alpine alright altec althea althor altima altoids alucard alvin always alyson alyssa amadeus amand amanda amanda1 amateur amateurs amatuers amature amazing amazon amber amber1 ambers ambrose ambrosia amelia americ america america1 american amerika ameteur amethyst ametuer amiga amigo amigos amonra amor amorcit amore amstel amsterda amsterdam anaconda Anai anakin anal analsex ananas anarchy anastasi anastasia anastasiya anchor anders andersen anderson andre andrea andrea1 andreas andrei andres andrew Andrew ANDREW andrew1 andrews andrey andromed andromeda andy andyandy andyod22 anfield angel angel1 angel123 angela angela1 angelic angelica angelika angelina angelo angels angelus angie angus angus1 anhyeuem animal animals animated anime anita anna annabell anne annette annie annie1 annika annmarie another answer antares antelope anthon anthony Anthony anthony1 anthony7 anthrax antoine anton antoni antonia antonina antonio antony anubis anything anytime aol123 aolsucks apache apollo apollo1 apollo13 apple apple1 apple123 applepie apples apricot april april1 aprilia aptiva aquarius aragon aragorn aramis arcadia arch archange archer archery archie architec arctic area51 argentin argentina ariana ariane arianna ariel aries arizona arizona1 arjay arkansas arlene armada armagedon armand armando armani armored armstron army arnold around arrow arrows arse arsenal arsenal1 artem artemis artemka arthur artist artur arturo asasas asd123 asd222 asdasd asdasdasd asddsa asdf asdf12 asdf123 asdf1234 asdfasdf asdffdsa asdfg asdfgh asdfgh01 asdfgh1 asdfghj asdfghjk asdfghjkl asdfjkl asdfwxcv asdfzxcv asdqwe asdqwe123 asdwxc asdzxc asgard ashle ashlee ashleigh ashley ashley1 ashton asia asian asians aside asimov aspen aspire aspirine assasin assass assassin asscock asses assfuck asshole asshole1 assholes assman asswipe assword asterix asthma astra astral astrid astro astro1 astros athena athens athlon athome atlanta atlantic atlantis atlas atomic atreides attack atticus attila attitude attorney attract aubrey auburn auckland audi audia4 audio auditt audrey auggie august augusta augustus aurora aussie austin austin1 austin31 australi australia austria auto autumn avalanch avalon avatar avenger avenue aviation away awesome awesome1 awful awnyce axeman axio azamat azazaz azazel aze123 aze123aze aze321 azeaze azeazeaze azedsa azeqsd azeqsd123 azeqsdwxc azer azer12 azer123 azer1234 azerazer azerqsdf azerqsdfwxcv azert azert1 azert123 azert12345 azert40 azerty azerty1 azerty11 azerty12 azerty123 azerty1234 azerty12345 azerty123456 azerty7 azertyazerty azertyu azertyui azertyuio azertyuiop azertz azertzui azrael azsxdc azsxdcfv aztnm azzer b929ezzh baba babe baberuth babes babies baboon baby babybaby babyblue babyboy babycake babydoll babyface babygirl babylon babylon5 babylove bacardi bacchus bach back backbone backdoor backup bacon badabing badass badboy badboy1 baddest baddog badger badgers badgirl badman bagels baggies baggins baggio bagira bagpuss bahamas bahamut bailey bailey1 baker baker1 balance balboa baldwin ball baller ballet ballin balloon balloons balls balls1 baltimor bama bambam bambi bamboo banan banana banana1 bananas banane band bandit bang bangbang banger bangkok bank banker banks banner banshee banzai baracuda barb barbados barbar barbara barber barbie barcelon barcelona barefeet barefoot barfly baritone barker barkley barks barley barnes barney barney1 baron barrage barrett barron barry barry1 barselona barsik bart bartman bartok barton base basebal basebal1 baseball BASEBALL Baseball baseball1 basement basher basic basil basket basketba basketball bass basset bassman bassoon bastard bastards bathing batman Batman batman1 battery battle bauhaus baura baxter bayern baylor bball bbb747 bbbb bbbbb bbbbb1 bbbbbb bbbbbb1 bbbbbbb bbbbbbbb bbking bcfields bdsm beach beach1 beaches beacon beagle beaker beamer bean beanbag beaner beanie beans bear bear1 bearbear bearcat bearcats beardog bears bears1 beast beast1 Beast1 beastie beat beater beating beatle beatles beatles1 beatrice beau beautifu beautiful beauty beaver beavers beavis beavis1 bebe because becca beck becker beckham becky becky1 bedford bedlam beebee beech beef beefcake beelch beemer beer beerbeer beerman beerme beethove beetle beezer behappy belair believe belinda belize belkin bell bella bella1 bellaco bellagio belle belly belmont beloved benben bender bendover benessere benfica beng bengal bengals benito benjamin benji bennett bennie benny benny1 benoit benson bentley benton benz beowulf beretta berger bergkamp berkeley berlin bermuda bernard bernie berry bert bertha bertie bertram bessie best bestbuy beta beth bethany betsy better bettina betty bettyboo beverly bhbirf bian bianca biao biatch bibi bicycle big1 bigal bigass bigbad bigballs bigbang bigbear bigben bigbig bigbird bigblack bigblock bigblue bigbob bigboobs bigbooty bigboss bigboy bigbucks bigbutt bigcat bigcock bigd bigdad bigdaddy bigdawg bigdick bigdick1 bigdicks bigdog bigdog1 bigfish bigfoot bigger biggie biggles biggun bigguns bigguy bighead bigjim bigjohn bigmac bigman bigmike bigmoney bignuts bigone bigones bigpenis bigpimp bigpoppa bigred bigred1 bigsexy bigshow bigtime bigtit bigtits bigtruck biguns biit bike biker bikers bikini bilbo bill bill1 billabon billbill billie billows bills billy billy1 billybob billyboy bimbo bimmer binder bing bingo bingo1 binky binladen biology bird bird33 birddog birdie birdman birgit birthday birthday1 birthday4 biscuit bishop bismark bismillah bitch bitch1 bitchass bitches bitchy bite biteme biteme1 bitter bizkit bizzare bjhgfi blabla blablabla black black1 blackbir blackcat blackcoc blackdog blackhaw blackice blackie blackjac blackjack blacklab blackman blackout blacks blacky blade blade1 blades blahblah blaine blair blake blake1 blam blanca blanche blanco blank blanked blast blaster blaze blazer blazers bleach bledsoe blender blessed blessing blind blink blink182 blinky bliss blitz blizzard blobby block bloke blond blonde blondes blondie blonds blondy blood bloody blossom blow blowfish blowjob blowme blubber blue blue1 blue11 blue12 blue123 blue1234 blue22 blue23 blue32 blue42 blue99 blueball bluebell blueberr bluebird blueblue blueboy bluedog blueeyes bluefish bluejay bluejays bluemoon blues blues1 bluesky bluesman blunt blunts bmw325 bmwbmw board boat boater boating boats bob123 bobafett bobb bobbie bobbob bobby bobby1 bobcat bobdole bobdylan bobmarley bobo bobobo body boeing bogart bogdan bogey bogota bogus bohica boiler boingo bolitas bollocks bollox bologna bolton bomb bombay bomber bombers bonanza bonbon bond bond007 bondage bone bonehead boner boners bones bong bonghit bongo bonita bonjour bonjovi bonkers bonner bonnie bonovox bonsai bonzai bonzo boob boobear boobed boobie boobies booboo booboo1 boobs booger booger1 boogers boogie book booker bookie books bookworm boom boomboom boomer boomer1 booper booster boot bootie bootleg boots bootsie bootsy booty bootys booyah boozer bopper borabora bordeaux border borders boricua boris borussia bosco bosco1 boss bosshog bossman boston boston1 bottle bottom boubou boulder bounce bouncer bounty bourbon bowie bowl bowler bowling bowman bowser bowtie bowwow boxcar boxer boxers boxing boxster boyboy boys boytoy boyz bozo bp2002 br0d3r br549 brad bradford bradley brady brain brains branch brand branden brandi brando brandon brandon1 brandy brandy1 brasil braves braves1 bravo bravo1 brazil bread break breaker breanna breast breasts breath breeze bremen brenda brenda1 brendan brenna brennan brent brent1 brest brett brewer brewster brian brian1 briana brianna brick bricks bridge bridget briggs bright brighton brigitte brisbane bristol british britney britt brittany brittney broad broadway brodie broken broker bronco broncos broncos1 bronson bronze brook brooke brooklyn brooks brother brothers brown brown1 brownie browning browns bruce bruce1 brucelee bruins bruiser bruno bruno1 brutus bryan bryan1 bryant bryce btnjey bubba bubba1 bubba123 bubba2 bubba69 bubbas bubble bubbles bubbles1 buceta buck buckaroo bucker bucket buckeye buckeyes buckley bucks buckshot bucky budapest budd buddah buddha buddie buddies buddy buddy1 buddy123 buddy2 buddyboy buddys budgie budlight budman buds budweise budweiser buff buffa buffalo buffalo1 buffet buffett buffy buffy1 buford bugger bugman bugs buick builder building bukkake bukowski bull bulldawg bulldog bulldog1 bulldogs bullet bulletin bullfrog bullock bulls bulls1 bulls23 bullseye bullshit bullwink bully bumble bumbum bummer bumper bundy bunghole bungle bunker bunnies bunny bunny1 burger burly burn burner burning burnout burns burrito burton bush bushido business businessbabe buste busted buster Buster buster1 bustle busty butch butch1 butcher butkus butler butt butter buttercu buttercup butterfl butterfly butters buttfuck butthead butthole buttman button buttons butts buzz buzzard buzzer byebye bynthytn byron byteme c2h5oh c7lrwu cabbage cabernet cable cabrio cabron caca cactus caddy cadillac caesar cafc91 caitlin cajun cake caldwell caleb calendar calgary cali calibra calico caliente californ california caligula calimero call callaway callie calling callisto callum calvin calvin1 calypso camaro camaro1 camaross camber cambiami cambridg camden came11 camel camel1 camelot camels cameltoe camera camero camero1 cameron cameron1 camil camila camilla camille camp campbell camper camping canada canada1 canadian cancel cancer cancun candace candice candle candy candy1 candyass candyman candys cang canine cannabis cannibal cannon canon cantona canuck canucks canyon capcom capecod capetown capital capitals capone caprice capricor capricorn capslock captain captain1 caracas caramel caravan carbon card cardiff cardinal cardinals cards care1839 carebear carina carl carla carlito carlitos carlo carlos carlos1 carlton carman carmel carmen carmex2 carnage carnival carol carola carole carolin carolina caroline carolyn carpedie carpente carpet carrera carrie carroll carrot carrots cars carson carsten carter cartman cartman1 cartoon cartoons carver casanova cascade case caserta casey casey1 cash cashflow cashmone cashmoney casino casio casper casper1 cassandr cassandra cassidy cassie caster castillo castle castor castro cat123 catalina catalog catcat catch22 catcher catdog catfight catfish catfood catherin catherine cathy catman catnip cats catter cattle catwoman caught cavalier caveman cayman cazzo cbr600 cbr900 cbr900rr ccbill cccc ccccc ccccc1 cccccc cccccc1 ccccccc cccccccc cdtnbr cdtnkfyf ceasar cecile cecilia cedric celeb celebrity celeron celeste celica celine celtic celtics cement ceng center central century cerberus cessna cevthrb cezer121 cfitymrf cgfhnfr ch5nmk chacha chachi chad chadwick chai chains chainsaw chair challeng chamber chambers champ champ1 champion champs chan chance chance1 chandler chandra chanel chang change changed changeme changes channel chantal chao chaos chaos1 chapman chappy charge charger chargers charisma charity charlene charles Charles charles1 charley charli charlie Charlie charlie1 charlie123 charlie2 charlott charlotte charlton charly charmed charon charter chas chase chase1 chaser chat chateau chatham chavez cheater check Check checker checkers checkmat cheddar cheeba cheech cheeks cheeky cheerleaers cheers cheese cheese1 cheesy cheetah chef chelle chelse chelsea chelsea1 chemical chemist chen cheng cherie cherokee cherries cherry cherry1 cheryl cheshire chess chessie chester chester1 chestnut chevelle chevrole chevrolet chevy chevy1 chevys chewbacc chewey chewie chewy cheyenne chiara chicago chicago1 chichi chick chicken chicken1 chickens chicks chico chief chiefs chiks child children chill chilli chillin chilly chimera china chinese chino chinook chip chipmunk chipper chippy chips chiquita chitown chivas chloe chloe1 chocha chocolat chocolate choice choke chong chooch choochoo chopin chopper chou chouchou chowder chris chris1 chris123 chrisbln chriss chrissy christ christa christi christia christian christie christin christina christine christma christmas christo christop christopher christy chrome chronic chrono chrysler chuai chuan chuang chubby chuck chuck1 chuckie chuckles chucky chui chun chunky chuo church churchil ciccio cicero cigar cigars cinder cindy cindy1 cinema cinnamon circle circus cirrus cisco citadel citation citizen citroen city civic civicsi civilwar cjkysirj cjkywt claire clancy clapton clarence clarinet clarissa clark clarke clarkie class classic classics claude claudi claudia claudia1 claudio clay claymore clayton clean cleaner clement clemente clemson cleo cleopatr cleopatra clevelan cleveland clever click cliff clifford clifton climax climber climbing clint clinton clipper clippers clips clit clitoris clock close close-up closer closeup cloud cloud9 clouds cloudy clover clovis clown clowns clticic club clueless clutch clyde cmfnpu cn42qj cnfybckfd coach coach1 coaster cobain cobalt cobra cobra1 cobras cocacola cocaine cock cocker cocks cocksuck cocksucker cocktail coco cocoa cococo coconut code codered cody coffee cohiba coke cola cold coldbeer coldplay cole coleman colin collect colleen college collie collin collins colnago colombia colonel colonial colony color colorado colors colt colt45 colton coltrane colts columbia columbus comanche combat comcast come comedy comein comet comets comfort comics coming command commande commando comment common comp compact company compaq compaq1 compass complete compton compute computer Computer conan concept concord concorde concrete condom condor confused cong connect conner connie connor conover conquest conrad constant consult consumer contact contains content contest contortionist contour contra contract control conway coochie cook cookie cookie1 cookies cooking cool coolcat coolcool cooldude cooler coolguy coolhand coolio coolman coolness cooper cooper1 coors cooter copenhag copper coral corbin core corey corina corinna corinne corky corleone corn corndog cornell corner cornhole cornwall corolla corona corps corrado corsair corvet07 corvette corwin cory cosmic cosmo cosmos costello cosworth cottage cotton coucou cougar cougars counter country county couples courage courier court courtney coventry cowboy cowboy1 cowboys cowboys1 cowgirl cows coyote cq2kph crack cracker crackers craft craig cramps crane crap crappy crash crash1 crave craven craving crawford crazy crazy1 crazybab crazyman cream creampie creamy create creation creative creature credit creed creepers creepy crescent crew cricket cricket1 criminal crimson crispy crissy cristian cristina critter crjhgbjy cromwell crosby cross crow crown cruise cruiser crumbs crunch crusader crusher crusty crysis crystal crystal1 csfbr5yy cthtuf cthulhu cthutq ctrhtn cuan cubbies cubs cubswin cucumber cuddles cuervo culinary cumcum cumm cummer cumming cummins cumshot cumslut cunt cunts cupcake cupoi curious curtis custer custom customer cute cutie cutiepie cutlass cutter cuxldv cvthnm cwoui cxfcnmt cyber cybersex cyborg cyclone cyclones cyclops cygnus cygnusx1 cynthia cypher cypress cyprus cyrano cyrus cyzkhw d6o8pm d6wnro d9ebk7 d9ungl dabears dabomb dabulls dad2ownu dada dadada daddy daddy1 daddyo daddys daedalus daemon daewoo daffy dagger dagmar daisey daisy daisy1 daisydog dakota dakota1 dale dalejr dallas Dallas dallas1 dalshe dalton damage daman damian damien dammit damn damned damnit damon dana dance dancer dancer1 dancing dandan dandfa dandy dang danger dani danie daniel Daniel daniel1 daniela daniele danielle daniels daniil danila dank danman danni danny danny1 dannyboy dante dante1 danzig daphne dapzu455 daredevi darian darina darius dark darkange darkangel darklord darkman darkness darkone darkside darkstar darlene darling darrel darrell darren darryl darth darthvad darwin dasani data datsun daughter dave dave1 davecole davedave david David david1 david123 davidb davide davids davidson davies davinci davis dawg dawgs dawn dawson daylight days dayton daytona dbnfkbr dbrnjh dbrnjhbz dddd ddddd ddddd1 dddddd dddddd1 ddddddd dddddddd de7mdf deacon dead deadhead deadly deadman deadpool deadspin deal dealer dean deanna death death1 death666 deaths debbie debbie1 deborah debra december decimal decker dede deedee deejay deep deeper deepthroat deer deerhunt deeznuts deeznutz default defender defense defiant deftones dehpye dejavu delaney delaware delboy delete delfin delight delilah dell delldell delmar delphi delpiero delta delta1 deltas deluxe demo demon demons dempsey denali deng denied deniro denis denise deniska denmark dennis density dental dentist denver depeche deputy derek derf derick derparol derrick descent desert design designer desire desiree deskjet desktop desmond destin destiny destiny1 destroy details detect detectiv detroit deuce deutsch devil devil666 devildog deville devilman devils devin devine devlt4 devo devon dewalt dexter dfcbkbq dfkthbz dfkthf dfktynby dfktynbyf dfvgbh dga9la dharma dhip6a diablo diablo2 dialog diamond diamond1 diamonds dian diana diane dianne diao diaper diapers dick dick1 dickdick dickens dicker dickhead dickie dicks dicky diego diehard diesel dietcoke dieter digger diggler digimon digital digital1 dilbert dilbert1 dildo dilligaf dillon dima dimas dimitri dimples dinamo dinara dindom ding dingbat dingdong dinger dingle dingo dinner dino dinosaur dipper dipshit dipstick direct director dirk dirt dirtbike dirty dirty1 dirtydog disaster disco discover discus disney disney1 ditto diva dive diver diver1 divers divine diving division divorce divx1 dixie dixie1 dizzy django dkflbckfd dkflbr dkflbvbh dmitriy dnsadm doberman doctor dodge dodge1 dodger dodgeram dodgers dodgers1 dodo dododo dog123 dogbert dogbone dogboy dogcat dogdog dogface dogfart dogfood dogg dogger dogggg doggie doggies doggy doggy1 doghouse dogman dogmeat dogpound dogs dogshit dogwood doit doitnow doktor dolemite doll dollar dollars dolly dolores dolphin dolphin1 dolphins domain dome domingo dominic dominick dominik dominion dominiqu domino donald dondon done dong donjuan donkey donna donna1 donner donnie donovan dontknow donut donuts doobie doodle doodles doodoo doofus doogie dookie dooley doom doomsday door doors dope doqvq3 doreen dorian doris dork dorothy dotcom dottie double doubled douche doudou doug dougal doughboy doughnut dougie douglas down downer downhill download downtown draco dracula drag0n drago dragon Dragon DRAGON dragon1 Dragon1 dragon12 dragon69 dragonba dragonball dragonballz dragonfl dragons dragoon dragster drake drakon draven dream dreamcas dreamer dreamer1 dreaming dreams dresden drevil drew drifter driller drinker drinks dripping drive driven driver drizzt droopy drop drowssap drpepper drum drummer drummer1 drums drunk drywall dshade dte4uw dthjybrf duan duane dublin ducati duchess duck duckie duckman ducks ducky dude dudedude dudeman dudes dudley duffer duffman duffy duke dukeduke dumb dumbass dummy dunbar duncan dundee dungeon dunhill dunlop dupont durango durham dust duster dustin dusty dusty1 dutch dutchess dvader dwayne dwight dylan dylan1 dynamic dynamite dynamo dynasty e5pftu eagle eagle1 eagle2 eagles eagles1 earl earnhard earth earthlin earthlink east easter eastern easton eastside eastwood easy eating eatme eatme1 eatme69 eatmenow eatpussy eatshit ebony echo eclipse eclipse1 eddie eddie1 eddy eded edgar edge edgewise edison edition editor edmonton edthom eduard eduardo edward edward1 edwards edwin eeee eeeee eeeee1 eeeeee eeeeee1 eeeeeee eeeeeeee eeyore efyreg egghead eggman eggplant eight eighteen eighty eileen einstein ejaculation ekaterina ekim elaine elcamino eldiablo eldorado eleanor electra electric electro electron elefant elektra element elements elena eleonora elephant eleven elijah elisabet elite elizabet elizabeth elizaveta ella ellen ellie elliot elliott ellis elmer elmo elodie eloise elpaso elvira elvis elvis1 elvisp elway elway7 elwood embalmer emerald emerson emil emilia emilie emilio emily emily1 eminem emma emmanuel emmett emmitt emperor empire encore ender energy enforcer engage engine engineer england england1 english enigma enjoy enough enrico enrique enter enter1 enterme enternow enterpri enterprise enters entrance entropy entry envelope epsilon epson epvjb6 equinox eraser erasure erection eric eric1 erica ericsson erik erika erin ernest ernesto ernie erotic erotica errors erwin escalade escape escort eskimo espana espresso esquire estelle esther estrella eternal eternity ethan etvww4 eugene eunice eureka europa europe evan evangeli evangelion evans evelyn everest everett everlast everton evgeniy evil evilone evolutio evolution ewtosi ewyuza excalibu excalibur excess exchange excite exeter Exigen Exigent exodus exotic experience experienced expert explore explorer export express express1 extensa extra extreme eyes eyphed f**k f00tball fabian fabie Fabie fabio fabric face facebook facial factory faggot fairlane faith faith1 faithful falcon falcon1 falcons fallen falling fallon fallout family famous fanatic fandango fang fanny fantasia fantasies fantasy fantomas farley farm farmboy farmer farrell farscape farside fart fartman fashion fast fastball faster fatass fatboy fatcat father fatima fatluvr69 fatman fatty faust favorite favorite2 favorite6 fdm7ed fdsa fduecn fear fearless feather feathers february federal federico feelgood feeling feet felicia feline felipe felix felix1 fellatio fellow female females fender fender1 feng fenris fenway fergie fergus ferguson fernand fernando ferrari ferrari1 ferret ferris fester festival fetish fettish fever ffff fffff fffff1 ffffff ffffff1 fffffff ffffffff ffvdj474 fghtkm fgtkmcby fick ficken fiction fiddle fidelio fidelity field fields fiesta figaro fight fighter fighting figure fihdfv film films films+pic+galeries filter filthy final finally finance finder fine finger fingerig fingers finish finland fire fire1 fireball firebird fireblad firedog firefigh firefire firefly firefox firehawk fireman firenze firewall first fischer fish fishbone fishcake fisher fisherma fishes fishfish fishhead fishin fishing fishing1 fishman fishon fishtank fishy fist fister fisting fitness fitter five fkbyjxrf fktrcfylh Fktrcfylh fktrcfylhf fktrctq fktyrf flame flamengo flames flamingo flanders flange flanker flash flash1 flasher flashman flathead flatron fleming flesh fletch fletcher flex flexible flicks flight flint flip flipflop flipmode flipper floppy florence flores florian florida florida1 flounder flow flower flower1 flower2 flowers floyd flubber fluff fluffy flyboy flyer flyers flyers88 flyfish flying fmale focus follow foobar food fool foolish foot footbal football Football FOOTBALL football1 footjob forbes force ford fordf150 foreman foreplay foreskin forest foreve forever forever1 forfun forget forgetit forgot forlife format forme formula formula1 forrest forsaken fortress fortuna fortune fortune12 forum forumwp forward foryou fossil foster fosters fountain four fowler foxfire foxtrot foxy foxylady fozzie fqkw5m frame fran franc france frances francesc francesco francine francis francisc franco francois frank frank1 frankie frankie1 franklin franks franky fraser frazier freak freaked freaks freaky freckles fred freddie freddy freddy1 frederic frederik fredfred fredrick free freebird freedom freedom1 freee freefall freefree freeman freepass freeporn freesex freeuser freeway freewill freeze french frenchie frenchy fresh fresno friday fridge friend friendly friends friendster fright fringe frisbee frisco frisky fritz frodo frodo1 frog frogfrog frogger froggie froggy frogman frogs front242 frontier frosch frost frosty frozen fruit fruity fuaqz4 fubar fubar1 fucing fuck fuck1 fuck123 fuck69 fucked fucker fucker1 fuckers fuckface fuckfuck fuckhead fuckher fuckin fucking fuckinside fuckit fuckme fuckme1 fuckme2 fuckoff fuckoff1 fucks fuckthis fucku fucku2 fuckyo fuckyou FUCKYOU fuckyou1 fuckyou2 fuck_inside fucmy69 fudge fugazi fujitsu fuking fulham full fullback fuller fullmoon function funfun fungus funk funky funny funny1 funstuff funtime funtimes furball furious fusion fussball futbol futurama future fuzzball fuzzy fuzzy1 fwsadn fx3tuo fyfcnfcbz fyfnjkbq fylhtq fytxrf fyutkbyf fyutkjxtr fzappa g3ujwg g9zns4 gabber gabby gabrie gabriel gabriel1 gabriela gabriele gabriell gadget gaelic gagged gagging galant galary galaxy galeries galileo galina gallaries galore galway gambit gamble gambler game gameboy gamecock gamecube gameover games gamma gandalf gandalf1 ganesh gang gangbang gangbanged gangsta gangster ganja gannibal garage garbage garcia garden gardner gareth garfield gargoyle garion garland garlic garner garnet garrett garrison gary gasman gaston gate gates gateway gateway1 gateway2 gatit gator gator1 gatorade gators gators1 gatsby gavin gawker gayboy gaymen gbhcf2 gbhfvblf gbpltw gecko geezer gegcbr geheim geil gemini gene general general1 generals generic genesis genesis1 geneva geneviev geng genius gentle geoffrey georg george George GEORGE george1 georgia georgie gerald gerard gerber gerbil gerhard gerhardt german germany geronimo gerrard gerry gertrude geryfe gesperrt getit getmoney getoff getout getsdown getsome getting gfhjkm gfhjkm1 gfhjkm123 gfhjkmgfhjkm gforce gfxqx686 gggg ggggg ggggg1 gggggg gggggg1 ggggggg gggggggg ghbdtn ghbdtn123 ghbdtnbr ghblehjr ghbrjk ghbywtccf ghetto ghhh47hj7649 ghjcnbnenrf ghjcnj ghjcnjgfhjkm ghjcnjnfr ghjuhfvvf ghost ghost1 ghostrider ghosts gianni giant giants giants1 gibson gibson1 gideon gidget giggle giggles gigi gilbert gilles gillian gilligan gilmore gina ginger ginger1 ginscoot giorgi giorgio giovanna giovanni giraffe girfriend girl girlie girlies girls girls1 girsl giuseppe giveitup giveme gizmo gizmo1 gizmodo gizmodo1 gizzmo gjkbyf glacier gladiato gladiator gladys glamour glasgow glass glasses gldmeo glen glenda glendale glenn glennwei glitter global globus glock gloria glory glotest glover gloves glow gman gmoney gn56gn56 gnasher23 goal goalie goat goats goaway gobears goblin goblue gobucks gocats gocubs godboy goddess godfathe godfather godiva godlike godsmack godspeed godzilla gofast gofish goforit gogators gogo gogogo gohan gohome goirish goku gold goldberg golden golden1 goldeney goldeneye goldfing goldfish goldie goldstar goldwing golf GOLF golf1 golfball golfer golfer1 golfgolf golfgti golfing golfman golfnut golfpro goliath gollum gomets gomez gonavy gone gong gonzales gonzalez gonzo gonzo1 goober goochi good Good123654 goodboy goodbye goodday goodfell goodgirl goodie goodluck goodman goodsex goodtime goodyear goofball goofy goofy1 google googoo gooner goose goose1 gooseman gopack gopher gordo gordon gordon24 gore gorgeous gorilla gotcha goten gotenks goth gotham gothic gotmilk gotohell gotribe gotyoass govols grace grace1 gracie graduate graham gramma grammy granada grand grandam grande grandma grandpa granite granny grant grapes graphics grass grateful gratis graves gravity gray graywolf grease great great1 greatest greatone greece greedy greek green green1 green123 greenbay greenday greene greenman greens greg gregor gregory gregory1 gremlin grendel gretchen gretzky greywolf griffey griffin griffith grils grimace grin grinch grinder gringo grisha grizzly gromit groove groovy groucho ground groups grover grumpy grunt gryphon gspot gstring gsxr1000 gsxr750 gthcbr gtnhjdbx guai guan guang guard guardian gubber gucci guess guest guido guiness guinness guitar guitar1 guitars gumbo gumby gundam gunnar gunner gunners guns gunther guru gustav gustavo gutter guyver gwju3g gymnast gymnastic gypsy h2slca ha8fyp habibi hack hacked hacker hacking haggis haha hahaha hahahaha hailey hair hairball hairy hakr hal9000 haley halflife halifax hall hallie hallo hallo123 hallowee hambone hamburg hamilton hamish hamlet hammer hammer1 hammers hammond hamper hampton hamster hancock hand handball handsome handyman hang hank hanna hannah hannah1 hannes hannibal hans hansen hansol hansolo hanson hanuman happines happiness happy happy1 happy123 happy2 happyday happydog happyman harald harbor harcore hard hardball hardcock hardcore harddick harder hardon hardone hardrock hardware hardwood hardy harlem harley Harley HARLEY harley1 harman harmony harold harper harpoon harrier harriet harris harrison harry harry1 harrypotter hart hartford hartley harvard harvest harvey hassan hastings hate hatred hatter hattrick havana havefun having hawaii hawaii50 hawaiian hawk hawkeye hawkeyes hawkins hawks hawks1 hawkwind hawthorn hayabusa hayden hayley hazard hazmat hcleeb head health heart hearts heat heater heather Heather heather1 heaven heavy heckfy hector hedgehog hedges heeled heels hehehe heidi heidi1 heineken heinrich hejsan heka6w2 helen helena helene helium hell hellas hellboy hellfire hellno hello hello1 hello12 hello123 hello2 hellohel hellokitty helloo hellos hellsing hellyeah helmet helmut help helper helpme hemlock hendrix heng henrik henry henry1 hentai henti herbert herbie hercules here heretic herewego heritage herman hermes hero heroes herring hershey hesoyam hester hetfield hevnm4 hewitt hewlett heyhey heynow heyyou hfytnrb hgfdsa hhhh hhhhh hhhhh1 hhhhhh hhhhhh1 hhhhhhh hhhhhhhh hidden higgins high highbury higher highheel highland highlander highlife highway hihihi hihje863 hiking hilary hill hillary hillbill hills hillside hilltop hilton hiphop hippie hippo history hitachi hithere hitler hitman hitter hiziad HIZIAD hjccbz hjkl hjvfirf hobbes hobbit hockey hockey1 hoes hoffman hogan hogtied hohoho hokies hola holden hole holein1 holes holger holiday holidays holla holland hollie hollow holly holly1 hollywoo hollywood holmes holycow holyshit home homeboy homely homemade homepage homepage- homer homer1 homerj homers homerun homework honda honda1 hondas honesty honey honey1 honeybee honeydew honeys hong hongkong honolulu honor hoochie hook hookem hooker hookers hookup hooligan hooper hoops hoosier hoosiers hooter hooters hooters1 hootie hoover hooyah hope hopeful hopeless hopkins hopper horace hores horizon horn horndog hornet hornets horney horny Horny horny1 hornyman horror horse horse1 horseman horsemen horses horton hose hoser hotass hotbox hotboy hotdog hotel hotel6 hotgirl hotgirls hothot hotlegs hotlips hotmail hotmail0 hotmail1 hotone hotpussy hotrats hotred hotrod hotsex hotshot hotspur hotstuff hott hotter hottest hottie hotties houdini houhou hound hounddog hounds house house1 houses housewife housewifes houston houston1 hover howard howdy howell howie hpk2qc hr3ytm hrfzlz htubcnhfwbz huai huan huang hubert hudson hufmqw huge hugetits hughes hugo hugohugo hulk humbug hummer humphrey hun999 hung hungry hunt hunte hunter Hunter hunter1 hunting hurley hurrican hurricane husband husker huskers huskers1 huskies husky hustler hybrid hydro hyperion hyundai hzze929b i62gbq iamgod iawgk2 ib6ub9 ibanez ibilltes ibxnsm iceberg icecream icecube icehouse iceland iceman iceman1 icu812 idefix idiot idontkno idontknow idunno iforget iforgot igor iguana ihateyou iiii iiiii iiiiii iiiiii1 iiiiiii iiiiiiii ijrjkfl iklo ilikeit ilikepie illini illinois illmatic illusion ilove ilovegod iloveit iloveme ilovesex iloveu iloveyo iloveyou iloveyou! iloveyou1 iloveyou2 image imagine imation imback immortal impact impala imperial implants impreza incest incubus indain india indian indiana indians indigo indon indy indycar infantry inferno infinite infiniti infinity info ingrid inlove innocent insane insanity insert insertion insertions inside insider insight insomnia inspiron install instant instinct integra intel inter interacial intercourse interest intern internal interne internet intj3a intrepid intruder inuyasha invest invis iomega ipswich iqzzt580 ireland irene irina irinka irish irish1 irishka irishman iriska iron ironmaiden ironman irving isaac isabel isabell isabella isabelle isacs155 isaiah iscool ishmael island islander israel istanbul istheman italia italian italiano italy itisme itsme ivan ivanov ivanova iverson iverson3 iwantu jabber jabroni jachin jack jack1 jackal jackass jackass1 jacket jackie jackjack jackoff jackpot jackson jackson1 jackson5 jacob jacob1 jacobs jacques jade jaeger jagger jaguar jaguar1 jaguars jaime jajaja jakarta jake jakejake jamaica james james007 james1 james123 jamesbon jamesbond jameson jamess jamie jamie1 jammer jammin jane janelle janet janice janine january japan japanees japanes japanese jared jarhead jarjar jarrett jarrod jarvis jasmin jasmine jasmine1 jason jason1 jasons jasper java javelin javier jaybird jayden jayhawk jayhawks jayjay jayman jayson jazz jazzman jazzy jean jeanette jeanne jeannie jedi jediknig jeep jeeper jeepers jeepjeep jeepster jeff jefferso jeffery jeffjeff jeffrey jello jelly jellybea jellybean jenifer jenjen jenkins jenn jenna jennaj jennie jennife jennifer Jennifer jennings jenny jenny1 jensen jeremiah jeremy jeremy1 jericho jerk jerkoff jerky jermaine jerome jerry jerry1 jersey jesper jess jesse jesse1 jessic jessica Jessica jessica1 jessie jester jesus jesus1 jeter2 jethro jets jetski jetta jewel jewell jewels jewish jezebel jian jiang jiao jigga jiggaman jill jillian jimbeam jimbo jimbo1 jimbob jimi jimjim jimmie jimmy jimmy1 jimmys jing jingle jingles jiong jjjj jjjjj jjjjj1 jjjjjj jjjjjj1 jjjjjjj jjjjjjjj jktxrf jktymrf jo9k2jw2 joan joanna joanne jocelyn jockey joe123 joeblow joebob joecool joejoe joel joelle joemama joey johann johanna johannes john john1 john123 john316 johnboy johncena johndeer johndoe johngalt johnjohn johnmish johnnie johnny johnny1 johnny5 johnson johnson1 johnston jojo jojojo jojojojo joke joker joker1 jokers jolene jolly jomama jonas jonathan jonathon jonboy jones jones1 jonesy jonjon jonny jorda jordan Jordan JORDAN jordan1 jordan23 Jordan23 jordon jorge jose joseph joseph1 josephin josh joshu joshua Joshua joshua1 josiah josie joung journey joyce joyjoy joystick jrcfyf jsbach juan juanita jubilee judge judith judy juggalo jughead juice juicy juju jules julia julian juliana julie julie1 julien juliet juliette julio julius july jumbo jump jumper june juneau junebug jungle junio junior junior1 juniper junk junkie junkmail jupiter jupiter1 jupiter2 jurassic just4fun just4me justdoit justice justin justin1 justine justme justus juventus jys6wz k.lvbkf k2trix kaboom kahlua kahuna kaiser kaitlyn kajak kakashka kaktus kalina kamasutra kamikaze kamila kane kang kangaroo kansas kappa kara karachi karaoke karate karen karen1 karin karina karine karl karma karolina kashmir kasper katana katarina kate katerina katherin katherine kathleen kathryn kathy kathy1 katie katie1 katrin katrina kawasaki kayla kaylee kayleigh kazantip kcchiefs kcj9wx5n keegan keenan keeper keepout keisha keith keith1 keksa12 keller kelley kellie kelly kelly1 kelsey kelvin kendall kendra keng kenken kennedy kenneth kenny kenny1 kenobi kenshin kent kentucky kenwood kenworth kenzie kermit kermit1 kernel kerouac kerry kerstin kestrel kevin kevin1 keyboard keystone keywest kfgjxrf kfhbcf khan kick kickass kicker kidney kidrock kids kieran kiki kikiki kikimora kill killa killah killbill kille killer KILLER killer1 killer12 killer123 killers killian killjoy killkill killme kilroy kimball kimber kimberly kimkim kimmie kimmy kinder king kingdom kingfish kingking kingkong kingpin kingrich kings kingston kinky kipper kirby kirill kirk kirkland kirsten kirsty kismet kiss kisses kissing kisskiss kissme kissmyass kitchen kiteboy kitkat kitten kittens kittie kitty kitty1 kittycat kittykat kittys kiwi kjkszpj kjrjvjnbd kkkk kkkkk kkkkkk kkkkkkk kkkkkkkk klaatu klaste klaster klaus kleenex kleopatra klingon klizma klondike knickerless knickers knicks knife knight knight1 knights knock knockers knopka knuckles knulla kobe kodiak kojak koko kokoko kokomo kolbasa kolobok kolokol komodo kong konstantin konyor kool koolaid kordell1 Kordell1 korean korn koroleva korova koshka kosmos kostya kotaku kotenok kram kramer krasotka kris krishna krissy krista kristen kristi kristian kristie kristin kristin1 kristina kristine kristy krokodil krolik kronos krusty krypton krystal ksenia ksusha kswbdu ktyjxrf kuai kuan kuang kubrick kugm7b kume kungfu kurt kyle l2g7k3 l8v53x labia labrador labtec lacrosse ladder laddie ladies lady ladyboy ladybug laetitia lager lagnaf laguna lake lakers lakers1 lakeside lakewood lakota lala lalakers lalala lalalala lambda lambert lamer lamont lance lancelot lancer lancia land lander landmark landon lane lang lansing lantern laptop lara large larisa larissa larkin larry larry1 larson laser laser1 laserjet lassie last lasvegas latex latin latina latinas latino laughing laura laura1 laurel lauren lauren1 laurence laurent laurie lavalamp lawman lawrence lawson lawyer lazarus lazy lback leader leanne leather leavemealone lebowski ledzep leeann leeds leedsutd leelee left lefty legacy legend legends legion legman legolas legos legs leigh leinad lekker leland lemans lemmein lemon lemonade lemons lena leng lennon lennox lenny leoleo leon leonard leonardo leonid leopard leopold leroy lesbain lesbean lesbens lesbian lesbians lesbos lesley leslie lespaul lestat lester lethal letme1n letmei letmein letmein1 Letmein1 letmein2 letmein22 letmeinn letmesee letsdoit letsgo letter letters lewis lexingky lexmark lexus lfitymrf lfybbk lgnu9d lhfrjy lian liang liao liberty library lick licker licking lickit lickme life lifehack lifetime light light1 lighter lighthou lighting lightnin lightning lights lilbit lilian liliana lilith lillian lillie lilly lily limewire limited limpone lincoln lincoln1 linda linda1 linden lindros lindsay lindsey line lineage lineage2 ling link linkin links linux lion lionel lionhear lionking lions lips lipstick liquid lisa lisalisa list listen lister lite lithium litle little little1 littlema live liverpoo liverpool liverpool1 lives living lizard lizzard lizzie lizzy lkjh lkjhg lkjhgf lkjhgfds lkjhgfdsa llamas llll lllll llllll lllllll llllllll lloyd load loaded lobo lobster lock lockdown lockerroom lockout locks loco locust locutus logan logan1 logger logitech loki lokiloki lokomotiv lol123 lola lolipop lolita lollipop lollol lollypop lolo lolol lololo loloxx lombard london london1 lonely lonesome lonestar lonewolf long longbow longdong longer longhair longhorn longjohn longshot lonnie look looker lookin looking lookout loomis looney loop loose looser lopez lord loren lorena lorenzo loretta lori lorraine losangel loser loser1 losers lost lotion lottie lotus loud louie louis louise loulou lounge love LOVE love1 love12 love123 love69 lovebug loveit lovejoy loveless lovelife lovelove lovely loveme lover lover1 loverboy loverman lovers loves lovesex loveya loveyou loving lowell lowrider loyola ltybcrf luan luca lucas lucas1 lucia luciano lucifer lucille luck lucky lucky1 lucky13 lucky7 luckydog luckyone luckys lucy ludmila ludwig luetdi luft4 luis luke lulu lululu lumber lumina luna lunatic lunchbox lust luther luv2epus lvbnhbq lydia lynn m123456 m5wkqf macaroni macbeth macdaddy macgyver machine macintos mack mackie macleod macmac macman macross mad madcat madcow madden maddie maddog maddux madeline madina madison madison1 madmad madman madmax madness madonna madrid maestro mafia magazine magelan magellan magenta maggi maggie maggie1 maggot magic magic1 magic32 magical magician magick magicman magnet magneto magnolia magnum magnus magpie magpies mahalo mahler maiden mail Mailcreated5240 mailman maine mainland maison majestic major makaka makaveli makayla maker maksim maksimka malachi malaka malcolm malibu malice malina malish mallard mallorca mallory mallrats malone mama mama123 mamacita mamama mamamama mamamia mamapapa mamas mammoth manager manchest manchester mancity mandarin manders mandingo mandrake mandy mandy1 manfred mang manga mango mangos manhatta maniac manila mankind manman mann manning mannn manny manolo manowar manson mantis mantle mantra manu manuel manuela manutd maple maradona marathon marauder marble marbles marc marcel marcello marcelo march marcia marcius2 marco marcos marcus margaret margarit margarita margaux margie mari maria maria1 mariah mariam marian mariana marianna marianne marie marie1 marijuan marijuana marika marilyn marin marina marine marine1 mariner mariners marines marines1 marino marino13 mario mario1 mario66 marion mariposa marisa marissa marius mariya marjorie mark mark1 marker market markie markiz markus marlboro Marlboro marlene marley marlin marlins marlon marma marquis marriage married mars marseill marseille marsh marsha marshal marshall mart martha marti martian martin Martin martin1 martina martine martinez martini marty marvel marvin mary maryann maryjane maryland masamune maserati mash4077 mason mason1 massage massimo massive maste master Master MASTER master1 Master1 master12 masterbaiting masterbate masterbating masterp masters masturbation matador matchbox mate material mathew matilda matrix matrix1 matt matteo matter matthe matthew Matthew matthew1 matthews matthias mattie matty mature maureen maurice maveric maverick Maverick max123 max333 maxdog maxell maxi maxim maxima maxime maximo maximum maximus maxine maxmax maxwell maxwell1 maxx maxxxx maya mayday mayfair mayhem maynard mazafaka mazda mazda6 mazda626 mazdarx7 mccabe mccarthy mcdonald mckenzie mclaren mdogg meadow meagan meat meatball meathead meatloaf mechanic media medic medic1 medical medicine medina medion medusa medved meeting mega megadeth megaman megan megan1 megane megapass megatron meghan meier meister melanie melanie1 melina melinda meliss melissa melissa1 mellon mellow melody melons melrose melvin member members meme mememe memorex memory memphis menace meng mental menthol mentor meow meowmeow mephisto mercede mercedes Mercedes mercer merchant mercury mercury1 meredith meridian merlin Merlin merlin1 merlot merlyn mermaid merrill mersedes message messiah messier met2002 metal metal1 metall metallic metallica meteor method methos metoo metro metroid mets mexican mexico miami miami1 mian miao michae michael Michael MICHAEL michael1 Michael1 michael2 michaela michaels michal micheal michel michele michell michelle Michelle michigan mick mickey mickey1 micky micro microlab micron microphone microsof microsoft middle midget midland midnight midnite midori midway mierda mighty miguel mihail mikael mike mike1 mike123 mike23 mike69 mikemike mikey mikey1 milamber milan milana milano mildred milena miles miles1 milfnew military milk milkman millenium miller miller1 millie million millions millwall milo milton mimi mind mindy mine minecraft minemine minerva ming mingus mini minime minimoni ministry minnesot minnie minute miracle mirage miranda miriam mirror mischief misery misfit misfit99 Misfit99 misfits misha mishka misses missie mission mississi mississippi missouri missy missy1 mister mistral mistress misty misty1 mitch mitchell mittens mizuno mizzou mmmm mmmmm mmmmmm mmmmmmm mmmmmmmm mnbv mnbvc mnbvcx mnbvcxz mobile mobydick mocha model models modelsne modem modena modern modles mogwai mohamed mohammad mohammed mohawk mojave mojo mollie molly molly1 mollydog moloko molson mommy mommy1 momo momomo momoney momsuck mona monaco monalisa monarch monday monday1 mondeo mone money money1 money12 money123 moneyman moneys mongo mongoose monic monica monica1 monies monika monique monitor monk monke monkey monkey1 monkey12 monkeybo monkeys monopoly monroe monsoon monster monster1 monsters montag montana montana1 monte montecar monterey montgom240 month montreal montrose monty monty1 moocow mookie moomoo moon moonbeam moondog mooney moonligh moonlight moonman moonshin moore moose moose1 mooses mopar morales mordor more moreno morgan morgan1 morgana morgoth moritz morning moron morpheus morris morrison morrow morrowind mortal morten mortgage morticia mortimer mortis morton moscow moses moskva mother mother1 motherfucker motherlode mothers motion motley moto motocros motor motorola motors motown mounta1n mountain mouse mouse1 mouser mouses mousey mouth movie movies mozart mp8o6d mpegs mrbill msnxbi mudvayne mufasa muff muffdive muffin muffin1 muffy muhammad mulder mullet mulligan multiplelo munch munchkin munich munster muppet murder murphy murphy1 murray murzik musashi muschi muscle muscles mushroom music music1 musica musical musician musicman muslim mustafa mustang MUSTANG Mustang mustang1 Mustang1 mustang2 mustang5 mustang6 mustangs mustard mutant mutley mwq6qlzo mybaby mydick mygirl mykids mylife mylove myname mynameis mypass mypassword myporn myrtle myself myspace1 mystery mystic mytime myxworld mzepab nacked nadia nadine naked namaste name nana nancy nancy1 nancy123 nang nanook napalm napass napoleon napoli napster narnia naruto nascar nascar1 nascar24 nash nastia nasty nasty1 nastya natali natalia natalie natalie1 nataly natasha natasha1 natchez nate natedogg nathalie nathan nathan1 nathanie nation national native natural nature naughty nautica nautilus navajo navigator navy navyseal nazgul nbvibt nbvjatq ncc1701 ncc1701a ncc1701d ncc1701e ncc74656 ndeyl5 ne1469 neal nebraska necklace needle needles negative neil nellie nelson nemesis nemrac58 neng neon neptune nermal nero nestle netscape nettie network neutron nevada never nevermin nevermind nevermore nevets neville newark newbie newcastl newcastle newlife newman newness newone newpass newpass6 newport news newton newyear newyork newyork1 nextel nexus6 nfnmzyf nfytxrf nguyen nian niang niao nice niceass niceguy nicetits nicholas nichole nick nickel nickie nicky nico nicol nicola nicolas nicole nicole1 nigga nigger nigger1 night nightmar nightmare nightowl nights nightwin nightwish nike niki nikita nikitos nikki nikki1 niko nikola nikolai nikolas nikolay nikon nimbus nimda2k nimitz nimrod nina nine nineball nineinch niners nineteen ning ninguna ninja ninja1 ninjas nintendo nipper nipple nipples nirvana nirvana1 nissan nissan1 nitram nitro nitrox nittany nixon njqcw4 nnnn nnnnn nnnnnn nnnnnnn nnnnnnnn noah nobody nocturne noel noelle nofear nogard nokia nokia1 nokia6233 nokia6300 nokian73 nolan noles noles1 nolimit nomad nomore noname noname123 none nonenone nong nono nonono noodle noodles nookie nopass nope norbert norfolk normal norman normandy norris north northern norton norway norwich norwood nose nostromo notebook nothing notnow notredam notused nounours nova novell november novifarm noway nownow nt5d27 nthvbyfnjh ntktajy nuan nuclear nude nudes nudist nudity nugget nuggets null number number1 nurse nurses nursing nutmeg nuts nutter nuttertools nwo4life nygiants nyjets nylons nymets nympho nyyankee oakland oakley oaktree oasis oatmeal obelix oberon obiwan objects oblivion obsidian ocean oceans october octopus odessa odin odyssey oedipus oemdlg office officer offroad offshore ohio ohmygod ohshit ohyeah oicu812 oilers okinawa oklahoma okokok oksana older oldman oldone oleg olemiss olenka olesya olga olive oliver oliver1 olivia olivier ollie olympia olympic olympus omar omega omega1 omicron onelove oneone onetime onetwo onion onions online only onlyme onlyone ontario oooo ooooo oooooo ooooooo oooooooo open opendoor openit opennow openup operator ophelia opiate optimist optimus option options opus oracle oral orange orange1 oranges orchard orchid oregon oreo orgasm orgasms orgy orient original orioles orion orion1 orlando orpheus orwell osama oscar oscar1 oscars osiris osprey othello otis ottawa otter otto ou812 ou8122 ou8123 out3xf outback outkast outlaw outoutout outside outsider ov3ajy over overkill overlord owen oxford oxygen oyster ozlq6qwm ozzy p0015123 p0o9i8u7 p3wqaw p4ssw0rd pa55w0rd pa55word pablo pacers pacific pacino pack packard packer packers packers1 pacman paco paddle paddy padres page paige pain paint paintbal paintball painter painting paisley pajero pakistan palace paladin paladin1 palermo pallmall palmer palmtree paloma pamela panama panasoni panasonic pancake pancakes pancho panda panda1 pandas pandora pang panhead panic panter pantera pantera1 panther panther1 panthers pantie panties pants pantyhos panzer paolo papa papabear papamama paper papers papillon papito pappy paradigm paradise paradox paragon paramedi paranoid paris paris1 park parker parol parola parrot parsons partner party pasadena pascal pass pass1 pass123 pass1234 passat passes passion passmast passme passpass passport passthie passw0rd Passw0rd passwd passwor passwor1 Passwor1 password Password PASSWORD password1 Password1 password12 password123 Password123 password2 password9 password99 passwords passwort pasta pastor pasword patch patches patches1 pathetic pathfind patience patric patrice patrici patricia patrick Patrick patrick1 patriot patriots patrol patti patton patty paul paula paulie paulina pauline paulpaul pavel pavement pavilion pavlov paxton payday payton pdiddy pdtplf peabody peace peace1 peach peaches peaches1 peachy peacock peanut peanut1 peanuts pearl pearl1 pearljam pearls pearson peavey pebble pebbles pecker peddler pedro pedros peekaboo peepee peeper peepers peewee pegasus peggy pelican pencil penelope penetrating penetration peng penguin penguin1 penguins penis penis1 penny penny1 pennywis pentagon penthous pentium people pepe pepito peppe pepper pepper1 peppers pepsi pepsi1 perfect perfect1 perkins perrin perry persian persik person persona personal pertinant pervert pescator pete peter peter1 peterbil peternorth peterpan peters peterson petra petrov petrova petunia peugeot peyton pfloyd phaedrus phantom phantom1 pharao pharmacy phat pheonix phialpha phil philip philippe philips phillies phillip phillips philly phish phish1 phoebe Phoeni phoenix Phoenix phoenix1 phone phones photo photo1 photoes photon photos phpbb phrases phreak phyllis physics pian piano pianoman pianos piao piazza picard picasso piccolo picher pick pickle pickles picks pickup picnic pics pictere pictuers picture pictures picturs pic\'s pierce piercing pierre pigeon piggy piglet pigpen pikachu pilgrim pillow pilot pilot1 pilots pimp pimpdadd pimpdaddy pimpin pimping pinball pinch pine pineappl pineapple pinetree ping pingpong pinhead pink pinkfloy pinkfloyd pinky pinky1 pinnacle pintail pinto pioneer pipe pipeline piper pippen pippin pippo piramida pirate pirates pisces piss pissed pisser pissing pissoff pistol piston pistons pitbull pitch pitcher pitchers pitt pitures pixie pixies pizdec pizza pizza1 pizzaman pizzas pktmxr pkxe62 place placebo places placid plane planes planet plants plasma plaster plastic plastics platinum plato platon platypus play playa playball playboy playboy1 playboy2 player player1 players playing playmate playoffs playstat playstation playtime pleasant please please1 pleasure plokij ploppy plum plumber plus pluto plymouth pn5jvw pobeda pocket poetry poets point pointer points poipoi poison poiu poiuy poiuyt poiuytre poiuytrewq pokemon pokemon1 poker poker1 pokey poland polaris police polina polish pollux polly PolniyPizdec0211 polo polopolo polska pommes pompey poncho pong pontiac pony poobear poochie poodle pooh poohbear pookey pookie pooky pool pool6123 poon poontang poop pooper poophead poopie poopoo pooppoop poopy pooter popcorn popeye popo popopo popova popper poppop poppy poppy1 poptart pork porkchop porky porn porn4life pornking porno porno1 pornographic pornos pornporn pornstar porsche porsche1 porsche9 port portal porter portia portland portugal poseidon positive possum post postal postman postov1000 potato pothead potter pounded pounding powder powell power power1 power123 powers pppp ppppp pppppp ppppppp pppppppp prague praise prayer prayers preacher precious predator pregnant prelude prelude1 premier premium presario presiden president presley pressure presto preston pretty pretzel prick pride priest prima prime primetime21 primus prince prince1 princes princess Princess princeto pringles printer printing prissy privacy private private1 privet probes prodigy producer product profit program progress project promise property prophecy prophet prospect prosper protect proton proview prowler proxy prozac psycho ptbdhw ptfe3xxp public puck puddin pudding puddles puff puffer puffin puffy pufunga7782 pugsley pulled pulsar pump pumper pumpkin pumpkin1 pumpkins punani punch punisher punk punkass punker punkin punkrock puppet puppies puppy puppy1 puppydog pupsik purdue purpl purple purple1 pushkin puss pussey pussie pussies pusssy pussy PUSSY pussy1 Pussy1 pussy123 pussy2 pussy4me pussy69 pussycat pussyeat pussyman pussys pusyy puta putter puzzle pvjegu pwxd5x pxx3eftp pyf8ah pyon pyramid python q11111 q12345 q123456 q1234567 q123456789 q1q1q1 q1q2q3 q1w2e3 q1w2e3r q1w2e3r4 q1w2e3r4t5 q1w2e3r4t5y6 q2w3e4 q2w3e4r5 q9umoz qawsed qawsedrf qawxcv qaz123 qazqaz qazws qazwsx qazwsx1 qazwsx12 qazwsx123 qazwsxed qazwsxedc qazwsxedc123 qazwsxedcrfv qazxcv qazxsw qazxswedc qazzaq qbg26i qcfmtz qcmfd454 qguvyt qhxbij qian qiang qiao qing qiong qn632o qqh92r qqq111 qqqq qqqqq qqqqq1 qqqqqq qqqqqqq qqqqqqqq qqqwww qsd123 qsd222 qsdaze qsdaze123 qsddsq qsdf qsdf12 qsdf123 qsdf1234 qsdffdsq qsdfg qsdfgh qsdfgh01 qsdfgh1 qsdfghj qsdfghjk qsdfghjkl qsdfjkl qsdfqsdf qsdfwxcv qsdfzxcv qsdqsd qsdqsdqsd qsdwxc qsdzxc quake quality quan quant4307s quantum quartz quasar quattro quebec queen queen1 queenie queens quentin quest quest1 question quick quincy quinn qwaszx qwe123 qwe123qwe qwe321 qweasd qweasd123 qweasdzxc qwedsa qweqwe qweqweqwe qwer qwer12 qwer123 qwer1234 qwerasdf qwerasdfzxcv qwerqwer qwert qwert1 qwert123 qwert12345 qwert40 qwerty QWERTY qwerty1 Qwerty1 qwerty11 qwerty12 qwerty123 qwerty1234 qwerty12345 qwerty123456 qwerty7 qwertyqwerty qwertyu qwertyui qwertyuio qwertyuiop qwertz qwertzui qwqwqw qwqwqwqw r29hqq r2d2 r2d2c3po rabbit rabbit1 rabbits rabota race racecar racer racer1 racers racerx rachael rachel rachel1 rachelle racing radar radar1 radical radio radiohea radiohead rafael rage ragnarok raider raiders raiders1 railroad rain rainbow rainbow1 rainbow6 rainbows rainer raining rainman rainyday raistlin raleigh ralph ralph1 ralphie ramada rambler rambo rambo1 ramirez ramjet rammstein ramona ramones rampage ramrod rams ramses ramsey ranch rancid randall randolph random randy randy1 rang ranger Ranger ranger1 rangers rangers1 raphael rapier rapper raptor rapture rapunzel raquel rascal rasdzv3 rasmus rasputin rasta rasta220 rasta69 rastaman ratboy rated ratman rats raul raven raven1 ravens raymond rayray razor razz rbhbkk rctybz reader readers reading ready reagan real reality really realmadrid reaper reason rebecca rebecca1 rebel rebel1 rebels rebelz reboot recall reckless recon record records recovery red123 redalert redbaron redbird redbone redbull redcar redd reddevil reddog reddwarf redeye redfish redfox redhat redhead redheads redhot redleg redlight redline redman redneck redone redred redrose redrum reds redshift redskin redskins redsox redsox1 redstar redstorm redwine redwing redwings redwood reebok reed reefer reeves referee reflex reggae reggie regina reginald register reilly reindeer reject release relief reload remember remingto remote renata renate renato renault rene renee renee1 renegade reng reno rental repair report reptile republic request requiem rereirf rescue research reserve resident respect restart retard retire retired return reveal revenge review revoluti revolution revolver rewq reynolds reznor rfgecnf rfhbyf rfhfylfi rfhnjirf rfnthbyf rfntymrf rfrfirf rfrnec rfvfcenhf rhbcnbyf rhfcjnrf rhiannon rhino rhino1 rhinos rhjrjlbk rhodes rhonda rhtdtlrj rhubarb rhythm ribbit ricard ricardo riccardo rice rich richard Richard richard1 richards riches richie richmond richter rick ricky ricky1 rico riddle ride rider riders ridge riffraff right rightnow righton riley riley1 rimmer ring ringer ringo ripken ripley ripped ripper ripple riptide rising rita river rivera riverrat rivers riversid rjhjkm rjhjktdf rjirfrgbde rjntyjr rjw7x4 rjycnfynby roach road roadkill roadking roadrunn roadrunner roadster roadway robbie rober robert Robert ROBERT robert1 roberta roberto roberts robin robin1 robins robinson robocop robot robotech robotics robots robyn rocco rochard rochelle rock rocker rocket rocket1 rockets rockey rockford rockhard rockie rockies rockin rocknrol rocknroll rockon rockrock rocks rockstar rockwell rocky rocky1 rocky2 rodeo rodman rodney roger roger1 rogers rogue rogue1 roland rolex roll roller rollin rolling rollins rolltide roma roman romance romano romans romantic romashka romeo romeo1 romero rommel romulus ronald ronald1 ronaldo rong ronnie roofer rookie room rooney rooster root rootbeer rootedit rosa rosario roscoe rose rosebud rosemary roses rosie ross roswell rotary rotten rough round route66 rover rovers rowing roxanne roxy royal royals royalty rqsdwv3 rrpass1 rrrr rrrrr rrrrrr rrrrrrr rrrrrrrr rsalinas rt6ytere rtyuehe ruan rubber rubble ruby rudeboy rudolf rudolph rudy rufus rufus1 rugby rugby1 ruger rugger rugrat rules rulez RuleZ rulz rumble runaway runescape runner running rupert rush rush2112 rushmore ruslan russ russel russell russell1 russia russian rustam rusty rusty1 rusty2 rustydog ruth ruthie rxmtkp ryan s123456 saab sabbath sabina sabine sable sabre sabres sabrina sabrina1 sacred saddle sadie sadie1 safari safety safeway saffron sage sahara saigon sail sailboat sailing sailor saint saints sairam saiyan sakura salamander salami salasana saleen salem sales salesman sally sally1 salman salmon salome salomon salope salsa salsero Salsero salvador sam123 samadams samanth samantha Samantha samara sambo samdog same samiam samira samm sammie sammy sammy1 sammy123 sammys samoht sampson samsam samson samsun samsung samsung1 samtron samuel samuel1 samurai sanchez sancho sand sandals sandberg sander sanders sandie sandiego sandman sandr sandra Sandra sandra1 sandrine sandro sandwich sandy sandy1 sanford sanfran sang sanity sanity72 sanjose santa santafe santana santiago santos sapper sapphire sara sarah sarah1 saratoga sarge sasasa sascha sasha sasha1 sasha_007 saskia sassy sassy1 sasuke satan satan666 satana satchmo satin saturday saturn sauce SaUn sauron sausage sausages savage savanna savannah save13tx savior sawyer saxman saxophon sayang scamper scandinavian scania scanner scarab scarface scarlet scarlett schalke schatz scheisse schmidt school science scirocco scissors scooby scooby1 scoobydo scoobydoo scooter scooter1 score scorpio scorpio1 scorpion scotch scotland scott scott1 scottie scotts scotty scout scout1 scouts scrabble scrapper scrappy scratch scream screamer screen screw screwy screwyou script scroll scrotum scruffy scuba scuba1 scully scumbag scxakv seabee seadog seadoo seagull seahawk seahawks seal sealteam seaman seamus sean searay search seaside season seattle seaweed seawolf sebastia sebastian sebring second secret secret1 secrets secure security sedona seductive seeker seeking segblue2 seinfeld sekret select selena selina seminole semper semperfi senate senator senators seneca seng senior senna sensei sentinel sentnece sentra sentry septembe september serega serena serenity sergbest sergeant sergei sergey sergi sergio series serious serpent sersolution server service services sesame seth seven seven7 sevens seville seviyi sex sex1 sex123 sex4me sex69 sexe sexgod sexman sexo sexpot sexsex sexsexsex sextoy sexual sexx sexxx sexxxx sexxxy sexxy sexy sexy1 sexy123 sexy69 sexybabe sexyboy sexygirl sexylady sexyman sexyone sexysexy seymour sf49ers shado shadow Shadow SHADOW shadow1 shadow12 shadows shady shaft shag shaggy shai shaker shakes shakira shakur shalom shaman shampoo shamrock shamus shan shane shane1 shaney14 shang shanghai shania shanna shannon shannon1 shanti shao shaolin shark shark1 sharks sharky sharon sharp sharpe shasta shaun shauna shaved shawn shawna shawnee shazam shearer sheba sheba1 sheeba sheena sheep sheepdog shei sheila shelby shelby1 sheldon shell shelley shells shelly shelter shemale shen sheng shepherd sheridan sheriff sherlock sherman sherri sherry sherwood sheryl shibby shield shiloh shimmer shine shiner shinobi ship shirley shit shitface shithead shitshit shitty shiva shock shocker shodan shoe shoes shogun shojou shonuf shoot shooter shop shopper shopping short shorty shot shotgun shou shovel show shower showing showme showtime shrimp shrink shroom shua shuai shuan shuang shui shun shuo shuttle shutup shyshy sick sickboy sickness side sidekick sidney siemens sienna sierra sierra1 sigma sigmachi sigmar signal sigrid silence silent silicon silk silly silver silver1 silverad silvia simba simba1 simhrq simmons simon simon1 simona simone simons simple simple1 simpson simpsons sims simsim sinatra sinbad sinclair sinful singapor singer single sinister sinned sinner siobhan sirius sissy sister sisters site sites sithlord sixers sixpack sixsix sixteen sixty sixty9 sixtynin sizzle skate skateboard skater skeeter skeeter1 skelter skibum skidoo skiing skilled skillet skin skinhead skinner skinny skins skip skipper skipper1 skippy skirt skittles skolko skooter skorpion skunk skydive skydiver skyhawk skylar skylark skyler skyline skywalke skywalker slacker slam slamdunk slammed slammer slap slapnuts slapper slappy slapshot slash slater slave slave1 slavik slayer slayer1 sledge sleep sleeper sleeping sleepy slick slick1 slider slim slimed123 slimjim slimshad slimshady slinky slipknot slipper slippery sliver slonik sloppy slow slowhand slugger sluggo slut sluts sluttey slutty smack smackdow smackdown small smalls smart smart1 smartass smashing smeghead smegma smeller smelly smile smile1 smiles smiley smirnoff smirnov smith smith1 smithers smiths smithy smitty smk7366 smoke smoke1 smoker smokes smokey smokey1 smokie smokin smoking smooth smoothie smother smudge smurf smut smutty snacks snake snake1 snakes snapon snapper snapple snappy snapshot snatch sneakers sneaky snicker snickers sniffer sniffing sniper sniper1 snooker snoop snoopdog snoopy snoopy1 snow snowball snowbird snowboar snowboard snowflak snowman snuffy snuggles snyder sobaka sober socce soccer soccer1 soccer10 soccer11 soccer12 socks socrates sofia soft softail softball softtail software Software Sojdlg123aljg solace solar solaris soldier soleil solitude solnce solo solomon solution some someday someone somerset somethin something sometime sommer sonata song sonia sonic sonics sonja sonne sonny sonoma sonora sony sonyericsson sonyfuck sonysony sooner sooners sooners1 sophi sophia sophie sophie1 soprano sopranos sorrow Soso123aljg soul soulmate sound sounds soup south southern southpar southpark southpaw sowhat space spaceman spades spain spam spanish spank spanker spanking spankme spanky spanky1 spanner sparhawk spark sparkle sparkles sparks sparky sparky1 sparrow sparta spartak spartan spartan1 spartans sparty spawn speaker speakers spears special special1 specialk spectre spectrum speed speed1 speedo speedway speedy spence spencer spencer1 sperm sperma sphere sphinx spice spice1 spider spider1 spiderma spiderman spidey spiffy spike spike1 spiker spikes spikey spinner spiral spirit spitfire spjfet splash spleen spliff splinter splurge spock spock1 spoiled sponge spongebo spongebob spooge spook spooky spoon spooner spoons sport sporting sports sporty spot spotty spread spring springer springs sprint sprinter sprite sprocket sprout spud spunk spunky spurs spurs1 sputnik spyder sqdwfe squall square squash squeak squeeze squerting squid squirrel squirt squirts srinivas ssptx452 ssss sssss ssssss sssssss ssssssss stacey stacey1 stacie stacy staff stafford stalin stalker stallion stan standard standby stanford stang stanislav stanley stanley1 stanton staples star star1 star12 star69 starbuck starcraf starcraft stardust starfire starfish stargate starligh starlite starman starr stars starship starstar start start1 starter startrek starwar starwars stasik state states static station status stayout stealth steam steaua steel steele steeler steelers stefan stefanie stefano steffen steffi stella stellar stepan steph stephan stephane stephani stephanie stephen stephen1 stephens stereo sterlin sterling stern sterva steve steve1 steven steven1 stevens stevie stewart stewart1 stick stickman sticks sticky stiffy stigmata stiletto stimpy sting stinger stingray stinker stinks stinky stinky1 stirling stjabn stock stocking stocks stockton stokes stolen stone stone1 stone55 Stone55 stonecol stonecold stoned stoner stones stonewal stoney stooge stooges stop stopit stoppedby storage store stories storm storm1 storms stormy story storys straight strange stranger strap strat stratfor strato stratus strawber strawberry streak stream streaming street streets strength stress stretch strider strife strike striker string strip striper stripes stripper stroke stroker strong stryker stuart stubby stud student studio studly studman stuff stuffer stumpy stunner stupid stupid1 style styles stylus suan subaru sublime submit suburban subway subzero success success1 suck suckcock suckdick sucked sucker suckers sucking suckit suckme suckmydick sucks suede sugar sugar1 sugars suicide sukebe sullivan sultan summer summer1 summer69 summer99 summers summit sundance sunday sundevil sundown sunfire sunflowe sunflower sunlight sunny sunny1 sunnyday sunrise sunset sunshin sunshine super super1 super12 super123 superb superfly superior superma superman Superman superman1 supernov supernova supersta superstar support supra supreme surf surfer surfer1 surfing surgery surprise survey surveyor survival survivor susan susan1 susana susanna susanne sushi susie susieq suslik sutton suzanne suzuki sveta svetik svetlana svoboda swallow swampy sweden swedish sweeney sweet sweet1 sweetie sweetnes sweetness sweetpea sweets sweety swift swifty swim swimmer swimming swing swinger swingers swinging swiss switch swoosh sword swordfis swordfish swords sxhq65 sydney sylveste sylvia sylvie symow8 syncmaster synergy syracuse system system1 systems syzygy t26gn4 tabasco tabatha tabitha table taco tacobell tacoma tadpole taekwondo taffy tahiti tahoe taichi tail tailgate tainted takehana talbot tales talisman talk talks talon tamara tammie tammy tammy1 tampabay tanaka tang tangerin tango tango1 tank tanker tanner tantra tanya tanya1 tara tarakan tardis target tarheel tarheels tarpon tartar tarzan tasha tasha1 tasty tatarin tatiana tattoo tatyana taurus taxman taylor taylor1 tazman tazmania taztaz tazz tbird tbone tdutybq tdutybz teacher team teaser tech technics techniques techno teddy teddy1 teddybea teddybear teen teenage teenie teens teensex tekken telefon telephon telephone teller temp temp123 tempest templar temple temppass temptress tenchi tender teng tennesse tennis tennis1 tequila terefon teresa terminal terminat terminator termite terra terran terrapin terrell terri terrier terror terry terry1 tessie test test1 test12 test123 test1234 test2 tester testerer testibil testing testing1 testme testpass testtest tetsuo texaco texas texas1 thailand thanatos thanks thankyou theater theatre thebear theboss thecat thecrow thecure thedog thedon thedoors thedude theend theforce thegame thegreat their thekid theking thelma theman thematri theo theodore theone therapy there theresa therock therock1 these theshit thesims thethe thething thetruth thewho thick thierry thighs thing things think thinking thirteen thirty this thisisit thistle thoma thomas Thomas THOMAS thomas1 thompson thong thongs thor thought thrasher three threesom thriller throat thrust thuglife thumb thumbnils thumbs thumper thumper1 thunder thunder1 thunderb thursday thx1138 tian tiao tiberius tiburon tical ticket tickle tickler tickling ticklish tictac tiff tiffany tiffany1 tiger tiger1 tiger123 tiger2 tiger7 tigercat tigers tigers1 tigge tigger Tigger tigger1 tigger2 tight tights timber time timeout times timmy timosha timothy timtim tina ting tinker tinkerbe tinkerbell tinman tintin tiny tipper tires titan titanic titanium titans titfuck titi titleist titman tito tits titten titties titts titty tkbpfdtnf tmjxn151 toad toast toaster tobias toby tobydog today today1 todd toejam toes toffee together toilet tokiohotel tokyo toledo tolkien tomahawk tomas tomato tomcat tommie tommy tommy1 tommyboy tomorrow tomtom tong tongue toni tonight tonton tony toocool toohot tool toolbox toolman tools toomuch toon toonarmy toons tooth tootie tootsie topaz topcat topdog topgun tophat topher topper topspin toriamos torino tornado toronto torpedo torres torture toshiba tosser total toto totoro tototo tottenha tottenham touch touching tower towers town toyota trace tracer tracey tracie track tracker tracks tractor tracy tracy1 tracy71 trader traffic trailer trailers train trainer training trains tralala tramp trample trance tranny trans transam transexual transfer transit Translator trapper trash trauma travel traveler travis travis1 treasure treble trebor tree treefrog trees treetop trek trent trenton trevor trewq trey trfnthbyf tri5a3 trial triangle tribal tribble tribe tricia trick tricky trident trigger trinidad trinitro trinity trinity1 trip triple tripleh triplex tripod tripper trish trisha tristan triton triumph trivia trixie trojan trojans troll trombone trooper trooper1 trophy tropical trotter trouble trouble1 troubles trousers trout trout1 troy trs8f7 truck truck1 trucker trucking trucks true trueblue truelove truman trumpet trumpet1 trunks trust trustme trustno1 Trustno1 truth tsunami tttt ttttt tttttt ttttttt tttttttt tuan tucker tucson tuesday tugboat tujhrf tulane tulip tulips tuna tunafish tundra tuning tunnel tupac turbo turbo1 turbos turk182 turkey turkey50 Turkey50 turner turnip turtle turtle1 turtles tuscl tusymo tuxedo twat tweety twelve twenty twiggy twilight twin twinkie twinkle twins twist twisted twister tycoon tyler tyler1 typhoon tyrant tyrone tyson tyson1 tyvugq tzpvaw ue8fpw ufkbyf ugejvp ukraine ulrich ulrike ultima ultimate ultra ulysses umbrella umpire unbelievable uncencored uncle undead under underdog underground undertak undertaker undertow underwear unicorn union unique united universa universal universe university unknown unlock unreal upnfmc uptown upyours uranus ursitesux ursula usa123 usarmy user username usmarine usmc usnavy ussy Usuckballz1 utopia uuuu uuuuu uuuuuu uuuuuuu uuuuuuuu uwrl7c uyxnyd vacation vader vader1 vadim vagabond vagina valdepen valdez valencia valentin valentina valentine valera valeri valeria valerie valhalla valiant valkyrie valley valleywa vamp vampir vampire vampire1 vampires vancouve vanessa vanessa1 vanguard vanhalen vanilla vantage varvara vasilisa vaughn vauxhall vbkfirf vbnm vcradq vdlxuc vector vectra vedder vegas vegeta vegitta vegitto vehpbr velocity velvet vendetta venera venice venom ventura venture venus vera verbatim verena veritas verizon vermont vernon verona veronica veronika versace vertigo verygood vette vette1 vfczyz vfdhif vfhbyf vfhecz vfhufhbnf vfibyf vfitymrf vfksirf vfndtq vfntvfnbrf vfrcbv vfrcbvrf vfvekz vfvfgfgf vfvjxrf vgirl vh5150 viagra vibrate vicki vickie vicky victor victor1 victoria Victoria victory video video1 videoes videos vides vienna vietnam view viewer viewsoni viewsonic viking vikings vikings1 viktor viktoria viktoriya villa village vince vincent vincent1 vinnie vintage violator violet violetta violin viper viper1 vipergts vipers virago virgil virgin virginia virginie virtual virus visa vision visited visitor visual vitalik vitamin vivian vivid vivitron vixen vjcrdf vjkjrj vjqgfhjkm vkaxcs vkontakte vlad vladik vladimir vladislav vodka volcano volcom volkov volkswag volley volleyba vols volume volvo voodoo voodoo1 vorlon vortex voyager voyager1 voyeur VQsaBLPzLa vsegda vSjasnel12 vulcan vulva vvvv vvvvv vvvvvv vvvvvvv vvvvvvvv w00t88 w4g8at wacker wade waffle wage wagner wahoo waiting waldo waldo1 walker walking wallace wallet walleye wally wally1 walmart walnut walrus walter walters walton wanda wanderer wang wanker wanking wannabe wanted wapapapa wapbbs waqw3p warcraft ward wareagle warez warhamme warhammer warlock warlord warner warning warren warrior warrior1 warriors wars warthog wasabi washburn washingt washington wasser wassup wasted watch watcher watching water water1 waterboy waterfal waterloo waterman waters waterski watford watson wave wavpzt wayer wayne wayne1 wealth weapon weare138 wearing weasel weather weaver webber webcam webhompas webmaste webmaster website webster wedding wednesda wednesday weed weed420 weekend weenie weewee weezer weiner weird welcom welcome welcome1 Welcome1 welder welkom weller wellingt wendell wendy wendy1 weng werder werdna werewolf werner wert werty wertyu wesley west western westham weston westside westwood wetpussy wetter wg8e3wjf whale whales what whatever whatsup whatthe whatup whatwhat whdbtp wheel wheeler wheels whiplash whiskers whiskey whisky whisper whistler white white1 whiteboy whiteout whites whitesox whitey whitney whkzyc whocares whore whynot wibble wiccan wicked widget wife wifes wifey wiggle wilbur wild wildbill wildcard wildcat wildcats wilder wildfire wildman wildone wildstar wildwood wilhelm will willard willem willi willia william William william1 williams willie willie1 willis willow willy willy1 wilma wilson wilson1 wind windmill window windows windows1 windsor windsurf wine winfield wing wingchun winger wingman wingnut wings winner winner1 winners winnie winona winston winston1 winter winter1 winter99 wireless wisdom wiseguy wishbone witch wives wizard wizard1 wizards wizzard wobble wolf wolf359 wolfen wolfgang wolfie wolfman wolfpac wolfpack wolverin wolverine wolves wolvie womam woman womans wombat womble women wonder wonderboy wonderfu wonderful wood wooden woodie woodland woodman woodrow woods woodstoc woodwork woody woody1 woof woofer woofwoof woohoo wookie woowoo word wordpass wordup work worker working workout world worlds worm wormix worship worthy wowwow wp2003wp WP2003WP wraith wrangler wrench wrestle wrestler wrestlin wrestling wright wrinkle1 wrinkle5 writer written wtcacq wu4etd wutang wvj5np wwww wwwww wwwwww wwwwwww wwwwwwww wxc123 wxcqsd wxcqsdaze wxcqsdaze123 wxcv wxcv123 wxcv1234 wxcvb wxcvbn wxcwxc wyatt wyoming wyvern w_pass x24ik3 x35v8l xanadu xander xavier xbox360 xerxes xfiles xian xiang xiao xing xiong xirt2k xjznq5 xman xmas xmen xngwoj xqgann xrated xray xtreme xuan xxx123 xxxpass xxxx xxxxx xxxxx1 xxxxxx xxxxxx1 xxxxxxx xxxxxxx1 xxxxxxxx xytfu7 xyz123 xyzzy yackwin yahoo yahoo1 yahooo yamaha yamaha1 yamahar1 yamato yang yankee yankee1 yankees yankees1 yankees2 yanks yasmin yaya ybrbnf ybrjkfq yeah yeahbaby yeahyeah year2005 yellow yellow1 yess yessir yesterda yesyes yfcntymrf yfnfif yfnfkb yfnfkmz yhwnqc ying yinyang yitbos yjdsqgfhjkm ynot yoda yogi yogibear yolanda yomama yong yosemite yoshi youknow young young1 your yourmom yousuck youtube yoyo yoyoma yoyoyo yqlgr667 ytrewq yuan yugioh yummy yumyum yvette yvonne yvtte545 ywvxpz yy5rbfsc yyyy yyyyy yyyyy1 yyyyyy yyyyyy1 yyyyyyy yyyyyyyy yzerman z1x2c3 z1x2c3v4 zach zachary zachary1 zack zalupa zander zang zanzibar zapata zaphod zappa zapper zaq123 zaq12wsx zaq1xsw2 zaqwsx zaqwsxcde zaqxsw zaqxswcde zaraza zardoz zarina zasada zazaza zebra zebra1 zebras zeke zelda zeng zenith zephyr zeppelin zero zerocool zeus zhai zhan zhang zhao zhei zhen zheng zhjckfd zhong zhou zhua zhuai zhuan zhuang zhui zhun zhuo zidane ziggy ziggy1 zigzag zildjian zimmer zipper zippo zippy zippy1 zlzfrh zodiac zoloto zombie zone zong zoom zoomer zoomzoom zooropa zorro zorro1 zouzou zsmj2v ztmfcq zuan zulu zurich zvezda zw6syj zxasqw zxasqw12 zxc123 zxcasd zxcasdqwe zxcasdqwe123 zxcv zxcv123 zxcv1234 zxcvb zxcvbn zxcvbnm zxcvbnm1 zxcvbnm123 zxczxc zxzxzx zyjxrf zzzxxx zzzz zzzzz zzzzz1 zzzzzz zzzzzz1 zzzzzzz zzzzzzzz free/data/spam-disallowed-terms.data 0000644 00001042473 15174670627 0013500 0 ustar 00 _123_ _abercrom _account _addon _adidas _advant _advert _afford _albion _alviero _annonce _article _asic _augmentin _autentic _authentic _babe _backup _barata _barato _bcbg _bene _bijoux _bitcoin _blackjack _blahnik _blog _bord _brace _build _bulgari _buyout _bvlgari _calvin _camiseta _campaign _cartier _casino _cassino _celine _chaus _cheap _cheat _christ _clear _click _clothes _coach _converse _coup _cpa _credit _data _default _diet _dior _disc _display _doc _dolce _domen _download _downtime _drmarten _dsqu _dunhill _duvetica _e/ _emerg _enter _entr _erotic _escort _estate _event _face _fake _farmacia _fashion _fausse _faux _femme _fendi _ferrag _fifa _football _forex _forte _free _fuck _furla _gabbana _generic _genuine _giub _gluten _golden _google _goose _gratis _gratuit _gucci _guest _hack _hand _hard _help _hemp _hermes _herpes _heuer _hollis _homme _htm _http _idea _income _index _info _instant _jack _java _jers _jord _key _kino _kit _kobe _kors _lacoste _lancel _latisse _lauren _lebron _link _list _load _loan _longchamp _loubou _lululemon _maillot _market _maxazria _mbt _mcm _men _milf _misc _mlb _moncler _montblanc _mulberry _naked _nba _news _nfl _nhl _nike _nordstrom _northface _note _nude _oakley _occh _occhiali _online _optim _orig _outlet _pandora _parafon _party _penis _pharm _ping _pliage _plug _poker _pokie _porn _post _ppv _prada _private _prod _prof _profil _profit _promo _qsymia _ray _regist _rehab _replica _replique _review _rolex _sac _sale _salomon _scarpe _schuh _select _sensual _seo _serv _sex _shirt _shoe _shop _site _sjs _sneak _soft _sold _spade _special _sportiv _store _strat _swaro _sys _tape _tattoo _taylor _temp _theme _timber _title _track _traff _tumi _ugg _uomo _upload _uptime _url _user _usuario _util _vape _vecaro _viral _virus _vod _voltaren _vti _vuitton _web _widget _wiki _window _women _woolr _www _xxx _youtube _zapat --- --and --bag --jack --of --phone --serv --shut --stor --that -( -+. -= -0.co -0.in -0.ro -0.ru -0.su -0.za -1.co -1.in -1.ro -1.ru -1.su -1.za -2.co -2.in -2.ro -2.ru -2.su -2.za -3.co -3.in -3.ro -3.ru -3.su -3.za -4-sale -4-u- -4-u. -4-u/ -4.co -4.in -4.ro -4.ru -4.su -4.za -4u- -4u. -4u/ -5.co -5.in -5.ro -5.ru -5.su -5.za -6.co -6.in -6.ro -6.ru -6.su -6.za -7.co -7.in -7.ro -7.ru -7.su -7.za -8.co -8.in -8.ro -8.ru -8.su -8.za -9.co -9.in -9.ro -9.ru -9.su -9.za -24. -abercrombie- -abercrombie. -abercrombie/ -abercrombiefitch- -abercrombiefitch. -abercrombiefitch/ -acne- -acne. -acne/ -adrenalin- -adsense- -adsense. -adsense/ -amateur- -amateur. -amateur/ -anal- -anal. -anal/ -anonymous- -anonymous. -anonymous/ -argente- -argente. -argente/ -armani- -armani. -armani/ -asics- -asics. -asics/ -asshole -auction- -auction. -auction/ -autentica- -autentica. -autentica/ -autentico- -autentico. -autentico/ -authentic- -authentic. -authentic/ -auto.in -auto.ro -auto.ru -auto.su -auto.za -avto- -avto. -avto/ -b2b -backpain- -backpain. -backpain/ -beneficial- -beneficial. -beneficial/ -bioactive- -bioactive. -bioactive/ -bioactives- -bioactives. -bioactives/ -bioactivity- -bioactivity. -bioactivity/ -bitcoin- -bitcoin. -bitcoin/ -blackjack. -blackjack/ -blog. -blog/ -blogs. -blogs/ -bluray- -bluray. -bluray/ -bodybuilding. -bodybuilding/ -boner- -boner/ -boners- -boners/ -boobs- -boobs. -boobs/ -breathalyzer- -broker. -broker/ -buy. -buy/ -cable-speaker- -cable-speaker. -cable-speaker/ -calvin-klein. -calvin-klein/ -calvinklein. -calvinklein/ -camiseta. -camiseta/ -camisetas. -camisetas/ -canada-goose -canadagoose -car.co -car.in -casas-en- -cash. -cash/ -casino- -casino. -casino/ -cassino. -cassino/ -cell-phone- -cell-phone. -cell-phone/ -charms. -charms/ -cheap- -cheap. -cheap/ -cheaper- -cheaper. -cheaper/ -cheapest- -cheapest. -cheapest/ -cheat- -cheat. -cheat/ -cia.co -cigar- -cigar. -cigar/ -cigarette- -cigarette. -cigarette/ -cigarettes- -cigarettes. -cigarettes/ -cigars- -cigars. -cigars/ -cigs- -cigs. -cigs/ -click- -click. -click/ -clit- -clit. -clit/ -co-jap -co-jp -co.jp -cock- -cock. -cock/ -coin. -coin/ -coins. -coins/ -comfort- -converse. -converse/ -cosmetics. -cosmetics/ -costume. -costume/ -costumes. -costumes/ -coupon- -data-recovery- -de-mbt -deluxe- -diagnostic- -dieta- -dieta. -dieta/ -directory. -disc -doktora- -doktora. -doktora/ -doku. -doku/ -dolce-gabbana. -dolce-gabbana/ -dolcegabbana. -dolcegabbana/ -domination- -domination. -domination/ -doorblog- -doorblog. -doorblog/ -download. -download/ -dre. -dre/ -drmarten- -drmarten. -drmarten/ -drmartens- -drmartens. -drmartens/ -duvetica. -duvetica/ -e-guide- -easily- -ebay -eguide- -enlargement. -enlargement/ -entries/ -escort. -escort/ -escorts. -escorts/ -essay. -essay/ -event. -event/ -executive. -executive/ -executives. -executives/ -family-member -fantasia- -fantasia. -fantasia/ -fantasy- -fantasy. -fantasy/ -farmacia- -farmacia. -farmacia/ -fashion- -fashion. -fashion/ -faux. -faux/ -filmi -filmy -finance. -finance/ -financing. -financing/ -fitch. -fitch/ -for-adult -for-sale -for-u- -for-u. -for-u/ -for-you -foreclose- -foreclose. -foreclose/ -foreclosed- -foreclosed. -foreclosed/ -foreclosure- -foreclosure. -foreclosure/ -forex- -forex. -forex/ -fotograf. -fotograf/ -fotografia. -fotografia/ -free-full- -free-movie- -free-shipping- -free-shipping. -free-shipping/ -fuck -full-movie- -fund- -fund. -fund/ -gambling- -gambling. -gambling/ -gaming- -gaming. -gaming/ -get-free- -gifts- -handbag- -handbag. -handbag/ -handbags- -handbags. -handbags/ -harley-davidson- -haziran- -health-related- -health-related. -health-related/ -herbal- -herbal. -herbal/ -hermes- -hermes. -hermes/ -hgh- -hgh. -hgh/ -homepage- -homepage. -homepage/ -iapple- -iapple. -iapple/ -ihover- -ihover. -ihover/ -income- -income. -income/ -infotain -insurance. -insurance/ -insuring. -insuring/ -iphone-4- -iphone-5- -iphone-6- -jacket- -jacket. -jacket/ -jackets- -jackets. -jackets/ -jeremy-scott- -jerking- -jerking. -jerking/ -jersey- -jersey. -jersey/ -jerseys. -jerseys/ -kabriolet- -kabriolet. -kabriolet/ -kartridj- -kartridj. -kartridj/ -kartridja- -kartridja. -kartridja/ -kartridzhej- -kartridzhej. -kartridzhej/ -kontakt- -kontakt. -kontakt/ -lacoste- -le-pliage -link. -link/ -loan- -loans- -longchamp- -longchamp. -longchamp/ -lottery- -lottery. -lottery/ -lotto- -lotto. -lotto/ -loveland- -loveland. -loveland/ -lover- -lover. -lover/ -lululemon. -lululemon/ -magazin- -magazini- -malware- -malware. -malware/ -marant- -marc-jacobs- -marcjacobs- -market.ru -mastery. -mastery/ -mbt. -mbt/ -mbts. -mbts/ -media-wiki- -media-wiki. -media-wiki/ -mediawiki- -mediawiki. -mediawiki/ -men-jers -mens-jers -mens. -metods- -mlm. -mlm/ -mlsp. -mlsp/ -model.co -model.in -model.pl -model.ro -model.ru -model.su -model.za -models.co -models.in -models.pl -models.ro -models.ru -models.su -models.za -moncler. -money. -montblanc. -montblanc/ -mortgage- -most-effective- -most-effective. -most-effective/ -most-expensive- -most-expensive. -most-expensive/ -mrwhite- -mrwhite. -mrwhite/ -new-car- -newports/ -news-explained- -npn. -npn/ -nude- -nudes- -nuestratienda- -nuestratienda. -nuestratienda/ -oakley. -oakley/ -occhiali- -occhiali. -occhiali/ -odejda- -off-your- -offshore- -onlayn- -online-0- -online-1- -online-2- -online-3- -online-4- -online-5- -online-6- -online-7- -online-8- -online-9- -online-free- -online. -online/ -order-at- -outlet. -outlet/ -parafon. -parafon/ -payday- -pharmacy- -pharmacy. -pharmacy/ -pictures. -plus.in -plus.pl -plus.ro -plus.ru -plus.su -plus.za -poker- -poker. -poker/ -pokie- -pokie. -pokie/ -pokies- -pokies. -pokies/ -post-1. -post-2. -pozew- -prada- -prada. -prada/ -premium- -premium. -premium/ -press-release. -press-release/ -pressrelease. -pressrelease/ -price. -price/ -programy- -programy. -programy/ -promo- -promo. -promo/ -promotions- -promotions. -promotions/ -purse. -purse/ -purses. -purses/ -pussie- -pussie. -pussies- -pussies. -pussy- -pussy. -pussys- -pussys. -qsymia- -qsymia. -qsymia/ -r.ru -r.su -r4i- -rakuten- -rakuten. -rakuten/ -ralphlauren- -ralphlauren. -ralphlauren/ -ray-ban- -rayban- -rayban. -rdnk- -real-estate- -realestate- -remote-access -remote-desktop -repair.co -review.co -review/ -rims- -roulette- -roulette. -roulette/ -rozwod- -ru. -sale. -salomon-snowcross- -salomon. -salomon/ -salvia- -scarpe- -search-engine -searchengine -secret- -secret. -secret/ -secrets- -secrets. -secrets/ -select-your- -selection. -selection/ -seo -service. -sexy- -shiatsu. -shoe. -shoe/ -shoes. -shoes/ -shop-for- -shop-for. -shop-for/ -shop. -shop/ -shoppe. -shoppe/ -shopper. -shopper/ -shoppes. -shoppes/ -shopping. -shopping/ -shops. -shops/ -sigareta- -sigareta. -sigareta/ -sigarety- -sigarety. -sigarety/ -sims-3. -sims-3/ -skque- -skque. -skque/ -sluts- -sluts. -sluts/ -soma. -soma/ -speed-up- -store. -store/ -strona- -strona. -strona/ -strony- -strony. -strony/ -sunglasses- -sunglasses. -sunglasses/ -suplementy- -suplementy. -suplementy/ -supplement- -supplement. -supplement/ -supplements- -supplements. -supplements/ -supremacy- -supremacy. -supremacy/ -symptoms- -systems.in -systems.ph -systems.pl -systems.ro -systems.ru -systems.su -systems.za -tattoo- -tattoo. -tattoo/ -tattoos- -tattoos. -tattoos/ -tax-attorney -tax-relief -tax-settlement -teen-in- -the-best- -the-best. -the-best/ -the-easiest- -the-greatest- -thunderstick- -tips/ -tits- -tits. -tits/ -titties- -titties. -titties/ -top-info -tours.co -traffic. -trainings. -trainings/ -transpiration- -treatment- -tw.in -uggs- -uggs. -uggs/ -uk.net -vaginal- -valkiriarf- -valkiriarf. -valkiriarf/ -vasion- -vasion. -vasion/ -vecaro- -vecaro. -vecaro/ -vendita- -vuitton- -want-it -watches. -watches/ -wayfarer. -wayfarer/ -web-pag -web-page -web-site -webhost. -webhost/ -website- -wedding-dress -wedding-photo -wheels- -wholesale. -wizard.co -women-jers -womens-jers -womens. -x- -xchange -yuwanshe. -yuwanshe/ ・ ・ ・・ , , , . , | ,, ,. ," ,| ,ag ,an ,en ,he ,in ,ka ,m, ,me ,no ,of ,on ,th ,ti ,we 、、 ;; http ;&#x ;# :)! :/// :adidas :blog :nike :post :user :usuario ! ! , ! ! ! . !,i !! you !!! !!} !!\ !?' !?’ !.. ![] !} !@# ???? ?/pag ?add ?adidas ?birkin ?browse ?bvlgari ?celine ?coach ?domain ?ducati ?fendi ?firma ?forex ?free ?gucci ?hermes ?hollis ?jp ?kat ?key ?lang ?longchamp ?mbt ?mod ?mulberry ?nike ?p_ ?p- ?p=404 ?pandora ?prada ?ray ?scarpe ?seo ?sexy ?shoe ?shop ?store ?t_ ?t- ?timber ?title ?tumi ?twid ?u_ ?u- ?url= ?user ?view ?youtube .-0 .-1 ..a .() .[] .% .2u4.us .acgi .adboard .adsboard .adultnet. .armani- .asp/ .azmya. .baidu .bbls.pl .be/, .bioactive .blogbox. .blogspace. .bluray- .bluray. .canadagoose- .cartier- .cause- .causes- .cfmx .cgi .chanel- .clit- .clit. .clit/ .coach- .com wild .com, .com/, .com/% .com/1- .com/1. .com/2- .com/2. .com/3- .com/3. .com# .com0 .com1 .com2 .com3 .com4 .com5 .com6 .com7 .com8 .com9 .coms .coupon .credopa.in .d.e.r .de/, .disposable- .disposable/ .doorblog- .doorblog. .doorblog/ .ee/ee/ .equal- .executive- .fake- .farmacia- .farmacia. .farmacia/ .fcgi .filmi- .filmy- .forum. .fr/, .freeforums.org .freeoda.com/0 .freeoda.com/1 .freeoda.com/2 .freeoda.com/3 .freeoda.com/4 .freeoda.com/5 .freeoda.com/6 .freeoda.com/7 .freeoda.com/8 .freeoda.com/9 .games.in .games.pl .games.ro .games.ru .games.su .games.za .gucci- .hard- .hermes- .herveleger- .htm/ .html/ .htn .iapple- .iapple. .iapple/ .in, .in/, .in/?entry. .in/1- .in/1. .in/2- .in/2. .in/3- .in/3. .in/catalog .in/content .in/forum .in/image .in/in/ .in/katalog .in/load .in/member .in/page- .in/profil .in/service .in/shop .in/sold .in/store .in/top .in/user .in# .info. .info/, .insanity- .internet- .it/?it .it/t/ .jpg.php .kartridj- .kartridj. .kartridj/ .kartridja- .kartridja. .kartridja/ .kartridzhej- .kartridzhej. .kartridzhej/ .kr/kr/ .lipoly.ru .longchamp- .louboutin- .m88- .magix.net .marant- .marvsz. .moncler- .mrwhite- .mrwhite. .mrwhite/ .mulberry- .net, .net/, .net/% .net/1- .net/1. .net/2- .net/2. .net/3- .net/3. .net/net/ .net/online .net/user .newbalance- .nike- .nikeshox .nl/nl/ .on-demand .online.pl .online.ws .or.kr .org/, .org/user .outlet.co .ph, .ph/, .ph/?entry. .ph/# .ph/1- .ph/1. .ph/2- .ph/2. .ph/3- .ph/3. .ph/content .ph/forum .ph/image .ph/katalog .ph/load .ph/member .ph/page- .ph/ph/ .ph/profil .ph/pub .ph/service .ph/shop .ph/sold .ph/store .ph/top .ph/user .ph# .pharmacy- .pharmacy. .pharmacy/ .pl, .pl/, .pl/?entry. .pl/# .pl/1- .pl/1. .pl/2- .pl/2. .pl/3- .pl/3. .pl/catalog .pl/content .pl/forum .pl/image .pl/katalog .pl/load .pl/member .pl/page- .pl/pl/ .pl/profil .pl/service .pl/shop .pl/sold .pl/store .pl/top .pl/user .pl# .plotexchange.us .press-release. .pressrelease. .ps: .pussie- .pussie. .pussies- .pussies. .pussy- .pussy. .pussys- .pussys. .qsymia- .qsymia. .qsymia/ .r.e.d .rar. .redirects. .rekla .remote-desktop .remotedesktop .ro, .ro/, .ro/?entry. .ro/# .ro/1- .ro/1. .ro/2- .ro/2. .ro/3- .ro/3. .ro/catalog .ro/content .ro/forum .ro/image .ro/katalog .ro/load .ro/member .ro/page- .ro/profil .ro/pub .ro/ro/ .ro/service .ro/shop .ro/sold .ro/store .ro/top .ro/user .ro# .roulette- .roulette. .roulette/ .royal- .rshacks. .ru, .ru/, .ru/? .ru/?entry. .ru/# .ru/1- .ru/1. .ru/2- .ru/2. .ru/3- .ru/3. .ru/aa- .ru/ab- .ru/ba- .ru/catalog .ru/content .ru/forum .ru/image .ru/katalog .ru/load .ru/member .ru/page- .ru/profil .ru/pub .ru/ru/ .ru/serial .ru/service .ru/shop .ru/sold .ru/store .ru/thumb .ru/top .ru/user .ru/uslug .ru/vid .ru/vip .ru# .s.a.k .secret- .secrets- .sexy. .sirenos.in .skque- .skque. .skque/ .smiley .su, .su/, .su/?entry. .su/# .su/1- .su/1. .su/2- .su/2. .su/3- .su/3. .su/aa- .su/ab- .su/ba- .su/catalog .su/content .su/forum .su/image .su/katalog .su/load .su/member .su/page- .su/profil .su/pub .su/ru/ .su/service .su/shop .su/sold .su/store .su/top .su/user .su/vip .su# .sunglasses- .supremacy- .supremacy. .supremacy/ .supreme- .t.a.p .t.wit .thearticle .top/? .top/thumb .travel.pl .twilight- .ua/? .ua/thumb .univer. .us/user .webs.co .worktop. .worktops. .yuwanshe. .yves .za, .za/, .za/?entry. .za/# .za/1- .za/1. .za/2- .za/2. .za/3- .za/3. .za/catalog .za/content .za/forum .za/image .za/katalog .za/load .za/member .za/page- .za/profil .za/pub .za/service .za/shop .za/sold .za/store .za/top .za/user .za/za/ .za# 。com '' ’’ »¿ »z »ż (buzzfeed) (linkedin) (no follow) (nofollow) (reddit) (stumbleupon) )- [... [/color [/move [/url [a...z] [a..z] [color [move [url {scarpe @123 @gmai. @goohle. @ma1l @mai1 @mail- @toke. @www. *@ / http /_t /-_ /?a /?b /?cat /?cl /?doc /?gid /?hid /?l1 /?l2 /?mid /?new /?opt /?pid /?tag /?tid /?tim /?tip /?top /?uid /.wd/ //| //123. //aaa- //aaa. //act- //act. //affordable //article //best //bitcoin //buy //cheap //compare //contact //eee //escort //forum //free //friend //gay //generic //groups //hgh //images/ //income //leverage //linkm //maestro //official //onsale //pagina //php //podcast- //podcast. //prosport //reality //royal- //ru. //shop //test //vpn- //vpn. //webpage //website //webstore //wp- //xxx /0.asp /00.asp /0.cfm /00.cfm /0.htm /00.htm /0.jsp /00.jsp /0.php /00.php /01.asp /1.asp /01.cfm /1.cfm /01.htm /1.htm /01.jsp /1.jsp /01.php /1.php /02.asp /2.asp /02.cfm /2.cfm /02.htm /2.htm /02.jsp /2.jsp /02.php /2.php /2ipe.tk /2u4.us /03.asp /3.asp /03.cfm /3.cfm /03.htm /3.htm /03.jsp /3.jsp /03.php /3.php /4-u- /4-u. /04.asp /4.asp /04.cfm /4.cfm /04.htm /4.htm /04.jsp /4.jsp /04.php /4.php /4ft.me /4u- /4u. /05.asp /5.asp /05.cfm /5.cfm /05.htm /5.htm /05.jsp /5.jsp /05.php /5.php /10.asp /10.cfm /10.htm /10.jsp /10.php /11.asp /11.cfm /11.htm /11.jsp /11.php /12.asp /12.cfm /12.htm /12.jsp /12.php /13.asp /13.cfm /13.htm /13.jsp /13.php /14.asp /14.cfm /14.htm /14.jsp /14.php /15.asp /15.cfm /15.htm /15.jsp /15.php /100-200. /100-300. /100-400. /100-500. /100mg /200-300. /200-400. /200-500. /300-400. /300-500. /400-500. /a- /a# /abc- /abc. /abcmart- /abcmart. /abercrombie- /abercrombie. /about- /about. /abouts- /abouts. /acai /accueil. /acidreflux- /acidreflux. /activity- /activity. /add-on/ /add-ons/ /add. /addfeed/ /addon/ /addons/ /adf.ly /admin/ /administration/ /administrator/ /administrators/ /admins/ /adorable- /adorable. /ads/ /affordable- /affordable. /airmax- /airmax. /alimenty/ /allimage/ /an0n.me /ano.gd /anonymous- /anonymous. /answer- /answer. /armani- /armani. /around. /asics- /asics. /asp/ /asthma- /asthma. /ateffa.ms /attachment/ /attachments/ /augmentin /autentica- /autentica. /autentico- /autentico. /authentic- /authentic. /avto- /avto. /b54.in /baby- /baby. /babydoll- /babydoll. /bag.sh/ /bags/ /banner- /banner. /banners- /banners. /bazar- /bazar. /bbqr.me /bbs/ /best- /best. /better- /better. /birkenstock- /birkenstock. /birkenstocks- /birkenstocks. /birkin- /birkin. /bit.ly /bitcoin- /blackjack- /blackjack. /blog. /blogid /bloglist/ /blogs/ /blogspot0. /blogspot1. /blogspot2. /board/ /boner- /boner. /boners- /boners. /boobs- /boobs. /boobs/ /bookmark- /bookmark. /bookmarked- /bookmarked. /bookmarks- /bookmarks. /borse- /borse. /brand1 /brand2 /brand3 /brand4 /brand5 /burberry- /burberry. /burberry1 /burberry2 /burberry3 /burberry4 /business-directory- /business-directory. /business-directory/ /businessdirectory- /businessdirectory. /businessdirectory/ /bustier- /bustier. /buy- /buy. /buyma. /bvlgari- /bvlgari. /cache- /cache. /cache/ /cached- /cached. /cached/ /caches- /caches. /caches/ /caching- /caching. /caching/ /caindex. /calvin-klein- /calvin-klein. /calvin-klein/ /calvinklein- /calvinklein. /calvinklein/ /camiseta- /camiseta. /camisetas- /camisetas. /campaign- /campaign. /campaign/ /campaigns- /campaigns. /campaigns/ /canada-goose- /canada-goose. /canada-goose/ /canadagoose- /canadagoose. /canadagoose/ /carinsur- /carinsur. /carinsurance- /carinsurance. /cartier- /cartier. /casino- /casino. /cassino- /cassino. /categories- /category- /cek.li /celine/ /cell-phone- /cell-phone. /cell-phone/ /cgi/ /chanel- /chanel. /chanel/ /chat/ /chaussure- /chaussure. /chaussures- /chaussures. /cheap- /cheap. /china. /chinese. /chrome/ /chrome= /cigar- /cigar. /cigarette- /cigarette. /cigarettes- /cigarettes. /cigars- /cigars. /cigs- /cigs. /ck- /ck. /ck/ /cl- /cl. /cl/ /clck.ru /cld- /cld. /clearance/ /clit- /clit. /clrb- /clrb. /coach- /coach. /coach/ /coin. /commodity- /commodity. /common/ /component/ /components/ /conf- /conf. /conf/ /config- /config. /config/ /configura- /configura. /configura/ /configuration- /configuration. /configuration/ /configure- /configure. /configure/ /content/ /control/ /converse- /converse. /cort.as /coupon- /coupon. /coupon/ /coupons- /coupons. /coupons/ /cp/ /credit- /credit. /crimea- /css/ /custom-control /custom/de/ /custom/en/ /custom/fr/ /customer-care. /customer-care/ /customercare- /customercare. /cutt.us /dat/ /data/ /datebase- /datebase. /db/ /dbase/ /ddrss. /dg- /dg. /diary- /diary. /diesel- /diet. /dieta. /dior. /dirigente/ /div/ /dlia/ /dmg/ /do. /doku- /doku. /dolce-gabbana- /dolce-gabbana. /dolce-gabbana/ /dolcegabbana- /dolcegabbana. /dolcegabbana/ /doorblog- /doorblog. /doudoun- /doudoun. /doudoune- /doudoune. /download. /drmarten- /drmartens- /drug- /drug. /dsquared- /dsquared. /duvetica- /duvetica. /easing- /easing. /ebay/ /ecig- /ecig. /economical- /edenic. /empower- /en-we/ /en. /engine/ /errorx/ /escort- /escort. /escorts- /escorts. /event- /event. /executive- /extreme- /f_0/ /f_1/ /f_2/ /f_3/ /f_4/ /f_5/ /farmacia- /farmacia. /farmacia/ /fashion- /faux- /faux. /fc.cx /features- /feed/ /feeds/ /fendi- /fendi. /ferragamo- /ferragamo. /file/ /files/ /filmi /filmy /finance. /financing. /flash/ /floodai- /floodai. /floodai/ /foot-care /for-u- /for-u. /forex- /forex. /forex/ /form. /forum/ /forum1 /fotka. /foto. /fpdb/ /free-shipping- /free-shipping. /free-shipping/ /free/ /fuck /function/ /functions/ /futbol- /futbol/ /ge.tt /gekiyasu /get-free /get/ /gh0/ /gh1/ /glowna. /gold-claim. /gold-yukon. /gold. /goo.gl /good. /goose/ /got.by /graphics/ /group1 /group2 /group3 /groups/ /gry- /gry. /gucci- /gucci. /guide- /gul.ly /haglofs- /haglofs. /handle/ /hats/ /headlines/ /health-related- /health-related. /health-related/ /hentai /hermes- /hermes. /herveleger /hfg.cc /hgh- /hgh. /hiend /hire- /hollister- /hollister. /hollister/ /homepage- /homepage. /homepage/ /hot. /hotdeal /how-to- /how-to. /howto- /howto. /html/ /http /hyundai. /iapple- /iapple. /iapple/ /ihover- /ihover. /ihover/ /ii/ /images- /images. /img/ /img1/ /img2/ /imgs/ /inc-meta /inc/ /include/ /includes/ /index-0 /index-1 /index-2 /index-3 /index-4 /index-5 /index.asp/ /index.aspx/ /index.cfm/ /index.htm/ /index.jsp/ /index.php/ /index0 /index1 /index2 /index3 /index4 /index5 /indexdic /info- /info. /infopage/ /infopages/ /install/ /insurance- /insurance. /internal/ /ip/ /iphone-case /is.gd /iss. /j.mp /jacket- /jacket. /jbs- /jbs. /jersey- /jersey. /jerseys- /jerseys. /job. /join. /joj. /joomla/ /jordan. /jordan1 /jordan2 /jordan3 /jordan4 /jordan5 /jotasyde- /jotasyde. /js. /js/ /just. /kabriolet- /kabriolet. /kartridj- /kartridj. /kartridj/ /kartridja- /kartridja. /kartridja/ /kartridzhej- /kartridzhej. /kartridzhej/ /kat, /kd6 /kitchenknife- /kitchenknife. /kitchenknives- /kitchenknives. /kommentar /kontakt- /kontakt. /kontakt/ /lacoste- /lacoste. /lang-es/ /latestmk- /latestmk. /layout1/ /layout2/ /legal/ /lib/ /libraries/ /library/ /librarys/ /libs/ /lijst/ /limewire /lingerie- /lingerie. /link- /link. /link/ /linkdetail /linknum /links- /links. /links/ /list- /list. /loans /local/ /locale/ /logos/ /logs/ /longchamp- /longchamp. /longchamp/ /loorg.de /louboutin- /louboutin. /love-fail /love-life /love-quote /loveland- /loveland. /lululemon- /lululemon. /lunette- /lunette. /lunettes- /lunettes. /lv- /lv. /lv/ /m88- /mailer/ /maillot- /maillot. /maillot/ /malware- /malware. /malware/ /manolo-blahnik /manoloblahnik /massage /mbt- /mbt. /mcm. /med- /media-wiki- /media-wiki. /media-wiki/ /mediawiki- /mediawiki. /mediawiki/ /medical- /medical. /medication- /medication. /member/ /members- /members. /members/ /membership- /membership. /menu- /menu. /middles. /mk-us/ /mkshop- /mkshop. /mlsp- /mlsp. /mlsp/ /mobile/ /module/ /modules/ /moncler- /moncler. /moncler/ /monindex/ /monster-cable- /monster-cable. /montblanc- /montblanc. /montblanc/ /mortgage /most-effective- /most-effective. /most-effective/ /most-expensive- /most-expensive. /most-expensive/ /mrwhite- /mrwhite. /mrwhite/ /mulberry- /mulberry. /mypage- /mypage. /mypage/ /myspace/ /n. /natverk/ /natwerk/ /natwork/ /netcat /netverk/ /netwerk/ /network/ /networks/ /new. /newbalance- /newbalance. /newbalance/ /neweb/ /newports- /newports. /news. /nihon /nike- /nike. /nike/ /niketn /ninnki /no-excuse /no-goose/ /nobis- /nobis. /nobis/ /nobisj- /nobisj. /nobisj/ /node/ /north-face /northface- /northface. /novinki- /novinki. /npn- /npn. /nuestratienda- /nuestratienda. /oakley- /oakley. /oakley/ /oakleycan /obsolete/ /occhiali- /occhiali. /offering/ /ok. /ok0. /ok1. /ok2. /ok3. /ok4. /ok5. /ok6. /ok7. /ok8. /ok9. /onip.it /online- /online. /online/ /ooindex. /open. /orderpage /orgasm /other- /other. /outils /outlet/ /ow.ly /p-laid- /p/ /page1. /page2. /page3. /page4. /page5. /pages- /pages. /pandorabead /pantie- /pantie. /panties- /panties. /panty- /panty. /parafon- /parafon. /parafon/ /past.is /pharmacy- /pharmacy. /pharmacy/ /php. /piece- /piece. /pierr- /pierr. /plotexchange.us /plug-in/ /plug-ins/ /plugin/ /plugins/ /plus. /popup /porn /postinfo- /postinfo. /posty/ /pozew /prada- /prada. /prada/ /premium- /premium. /press-release- /press-release. /pressrelease- /pressrelease. /pricing- /pricing. /privat- /privat. /privat/ /private- /private. /private/ /product/0/ /produkter/ /proficient- /profil- /profil? /profil. /profil/ /profile- /profile? /profile. /profile/ /prolist /property- /property. /prozac /public/ /pufy /pumps- /pumps. /pussie- /pussie. /pussie/ /pussies- /pussies. /pussies/ /pussy- /pussy. /pussy/ /pussys- /pussys. /pussys/ /q. /qsymia- /qsymia. /qsymia/ /r4i- /r4i. /ra. /rakuten- /rakuten. /rakuten/ /ralphlauren- /ralphlauren. /ralphlauren/ /ray-ban- /ray-ban. /ray-ban/ /rayban- /rayban. /rayban/ /readme. /readnews. /recap/ /refreshers. /rejuvenate- /rejuvenate. /remote-desktop /remotedesktop /res/ /resize/ /ri.ms /rlu.ru /roulette- /roulette. /roulette/ /rozwod /ru/news/ /sac- /sac. /sale- /sale. /sale/ /sales- /sales. /salomon- /salomon. /salvia/ /scarpe- /sconfig/ /scripts- /scripts/ /secret- /secrets- /secure/ /sendform/ /seo /sex /sheet/ /shop-for- /shop-for. /shop-for/ /shortcode- /shortcode/ /shortcodes- /shortcodes/ /show/ /sigareta- /sigareta. /sigarety- /sigarety. /sims-3. /sinsaku /site/ /site/ebay /sitemap- /sitemap. /siteold/ /skque- /skque. /skque/ /sluts- /sluts. /sluts/ /smsh.me /sn.im /snapback- /snapback. /snipurl.co /snowboot- /snowboot. /snowboots- /snowboots. /social-media/ /soft/ /soma- /soma. /soma/ /special- /special. /specialprojects- /specialprojects. /specials- /specials. /stan.to /start. /stats/ /strona- /strona/ /strony- /strony/ /stuff. /styles/ /stylesheet/ /stylesheets/ /su0.ru /suggestions- /sunglasses- /sunglasses. /suplementy- /suplementy. /suplementy/ /supremacy- /supremacy. /supremacy/ /swf/ /syaneru /system/ /t.co /tabid/ /tagged/ /tattoo- /tattoo. /tattoo/ /tattoos- /tattoos. /tattoos/ /tax-attorney /telecharger- /telecharger. /temp/ /temp0 /temp1 /temp2 /temp3 /template/ /templates/ /test. /th-th. /the-best- /the-best. /the-first /the-sale /the-secret /the-shock /themes/ /thesecret /thread- /tiffany- /tiffany. /timberland- /timberland. /tiny.cc /tinyls.net /tinyurl.co /title, /tits- /tits. /tits/ /titties- /titties. /titties/ /tmp/ /to.ly /toms- /toms. /top-0 /top-1 /top-2 /top-3 /top-4 /top-5 /top-6 /top-7 /top-8 /top-9 /top100. /tr.im /trac/ticket/ /trackback /trx/ /twilight- /txt/ /ugg- /ugg/ /uggs- /uggs. /uggsal/ /underc- /underc. /update/ /upload- /upload/ /uploadfile/ /uploadfiles/ /uploads- /uploads/ /url/ /urlms.co /urls/ /usa. /user- /user. /userid/ /userids/ /userprofile/ /userprofiles/ /userupload/ /useruploads/ /valkiriarf- /valkiriarf. /valkiriarf/ /vasion- /vasion. /vasion/ /vb/ /vecaro- /vecaro. /viewstory /vip. /visit. /visite. /voltaren /vuitton- /vuitton. /vuittonpascher- /vuittonpascher. /wayfarer- /wayfarer. /web-design/ /web0/ /web1/ /web2/ /web3/ /webalizer/ /webpage- /webpage. /webpage/ /wedding-reception- /wedding-reception. /wedding. /weddingreception- /weddingreception. /wholesale- /wholesale. /widget/ /widgets/ /wo. /woolrich- /woolrich. /wp-admin /wp-content /wp-image /wp-include /wp-list /wp-site /ww. /www.co/ /www.com/ /www.in/ /www.net/ /www.ru/ /www.su/ /www/ /x.co /xchange /xe/ /xml/ /xmlrpc/ /xurl.es /ya. /yabb /yours. /yukon-claim. /yukon-gold. /yuwanshe- /yuwanshe. /z. /zdev/ /zdjecia/ /zh-cn/ /zozo. /zumba- /zumba. \u003e\u003e \xa9 \xc3 FENDI Hermes Paul Smith timberland ω а г エアマックス トゥミ トリーバーチ ナイキ ニューバランス バッグ ブランド プロモ ポールスミス マークジェイコブス ミュウ レプリカ ローン ヴィトン 偽物 利 好評 激安 無料 特価 発売 財布 金 &~ &amp &x6 &x7 #/a #& #file #random %5b%5d %7c %$% %anchor% %blogurl% %d0%b0 %d0%b1 %d0%b2 %d0%b3 %d0%b4 %d0%b5 %d0%b6 %d0%b7 %d0%b8 %d0%b9 %d0%ba %d0%bb %d0%bc %d0%bd %d0%be %d0%bf %d1%8a %d1%8b %d1%8c %d1%8d %d1%8e %d1%8f %d1%80 %d1%81 %d1%82 %d1%83 %d1%84 %d1%85 %d1%86 %d1%87 %d1%88 %d1%89 %d8% %e0% %page% %pages% %titel% %title% %titlet% %u2019 %url% ‡a ‡o +.- +.asp +.cfm +.htm +.jsp +.php +abercrom +account +adidas +alviero +article +asic +babe +backup +bene +blog +bord +build +campaign +casino +cassino +celine +cheap +christ +clear +click +coach +converse +coup +cpa +data +dior +disc +doc +dolce +downtime +drmarten +e/ +emerg +enter +entr +event +fake +femme +fifa +free +fuck +furla +gabbana +genuine +giub +gluten +goose +gucci +hand +help +hermes +herpes +heuer +hollis +homme +htm +http +idea +index +info +iphone +java +jers +jord +key +kino +kit +kors +lacoste +lancel +lauren +lebron +link +list +load +loan +longchamp +loubou +market +mbt +men +misc +moncler +mulberry +naked +news +nike +nordstrom +note +oakley +occh +online +outlet +party +ping +pokie +porn +post +ppv +prada +prof +promo +ray +regist +review +sac +sale +scarpe +select +sensual +seo +serv +sex +shoe +shop +site +sjs +sneak +sold +spade +store +strat +swaro +tape +taylor +temp +theme +timber +title +track +tumi +ugg +uomo +upload +uptime +url +util +vti +vuitton +web +women +woolr ±o <a> =alviero =blog =brace =captcha =christ =com_ =forex =kobe =longchamp =loubou =mcm =nike =node =pandora =produit =profile =read =salomon =seo =space =thread =utf8 =view =youtube >.} | | || |chi |dal |det |for |per |porn |year |yr 00 pips 0-easy-way 0-generic 00-pips 0-the-best 0,5 mg 0,5mg 0.@ 0.5 mg 0.5mg 00% 00$ 0cigar 0day 0generic 0gram.ru 00mail 00mg buy 00mg cheap 00mg-buy 00mg-cheap 00mgbuy 00mgcheap 0ping 00pips 0taylor 0the best 0the-best 0title 00折 00歳 1-1. 1-easy-way 1-generic 1-the-best 1,0 mg 1,0mg 1,5 mg 1,5mg 1.@ 1.0 mg 1.0mg 1.5 mg 1.5mg 1.in 1@1 1@2 1@3 1big 1bodog 1c.in 1cigar 1generic 1hand 1health 1minute 1ping 1purse 1st-best 1stbest 1stop 1taylor 1test 1the best 1the-best 1title 1to1 1you 2-buy 2-easy-way 2-generic 2-global 2-the-best 2,0 mg 2,0mg 2,5 mg 2,5mg 2.@ 2.0 mg 2.0mg 2.5 mg 2.5mg 2.in 2@1 2@2 2@3 2bj.ru 2buy 2c.in 2cigar 2generic 2global 2hand 2health 2itb 2minute 2ping 2purse 2sale 2taylor 2test 2the best 2the-best 2title 2u.org 2you 3-easy-way 3-generic 3-the-best 3.@ 3.in 3@1 3@2 3@3 3cigar 3d моделе 3d-моделе 3generic 3hand 3health 3j3j3 3minute 3ping 3purse 3taylor 3test 3the best 3the-best 3title 3ww3 3you 4 cash 4 cheap 4 ever 4 fire 4 health 4 purse 4 quality 4 rent 4 sale 4 test 4-all 4-cash 4-cheap 4-easy-way 4-ever 4-fire 4-generic 4-gold 4-hand 4-health 4-juice 4-less 4-plus 4-purse 4-quality 4-rent 4-sale 4-silver 4-test 4-the-best 4-you 4.@ 4all 4cash 4cheap 4cigar 4ever 4fire 4generic 4gold 4hand 4health 4juice 4less 4minute 4plus 4purse 4quality 4rent 4sale 4silver 4test 4the best 4the-best 4u.club 4u.co 4u.in 4u.online 4u.pl 4u.ro 4u.ru 4u.su 4u.za 4you 5-easy-way 5-generic 5-the-best 5.@ 5cigar 5generic 5hand 5health 5minute 5purse 5test 5the best 5the-best 6-easy-way 6-generic 6-the-best 6.@ 6cigar 6generic 6hand 6purse 6the best 6the-best 7-easy-way 7-generic 7-the-best 7.@ 7cigar 7generic 7hand 7purse 7the best 7the-best 8-easy-way 8-generic 8-the-best 8.@ 8cigar 8generic 8hand 8purse 8the best 8the-best 9-easy-way 9-generic 9-the-best 9.@ 9cigar 9generic 9the best 9the-best 10% 10$ 10mg buy 10mg cheap 10mg-buy 10mg-cheap 10mg. 10mgbuy 10mgcheap 10折 10歳 20% 20$ 20mg buy 20mg cheap 20mg-buy 20mg-cheap 20mg. 20mgbuy 20mgcheap 20折 20歳 30% 30$ 30mg buy 30mg cheap 30mg-buy 30mg-cheap 30mg. 30mgbuy 30mgcheap 30折 30歳 40% 40$ 40mg buy 40mg cheap 40mg-buy 40mg-cheap 40mg. 40mgbuy 40mgcheap 40折 40歳 49ers jers 49ers online 49ers-jers 49ers-online 49ersjers 49ersonline 49折 50% 50$ 50mg buy 50mg cheap 50mg-buy 50mg-cheap 50mg. 50mgbuy 50mgcheap 50折 50歳 60% 60$ 60折 60歳 70% 70$ 70折 70歳 80% 80$ 80折 80歳 90% 90$ 90折 90歳 100% autentic 100% authentic 100% copy 100% legit 100% real 100mg. 111.co 111.in 111.pl 111.ro 111.ru 111.su 111.za 111@ 123.co 123.in 123.pl 123.ro 123.ru 123.su 123.za 123@ 222.co 222.in 222.pl 222.ro 222.ru 222.su 222.za 365.co 365.in 365.pl 365.ro 365.ru 365.su 365.za 404.asp 404.cfm 404.htm 404.jsp 404.php 911.co 911.in 911.pl 911.ro 911.ru 911.su 911.za 2012 longchamp 2012 prospect 2012-longchamp 2012-prospect 2012baby 2012longchamp 2012x. 2013 longchamp 2013 prospect 2013-longchamp 2013-prospect 2013baby 2013longchamp 2013x. 2014 longchamp 2014 prospect 2014-longchamp 2014-prospect 2014baby 2014longchamp 2014x. 2015 longchamp 2015 popular 2015 prospect 2015-longchamp 2015-popular 2015-prospect 2015baby 2015longchamp 2015x. 2016 longchamp 2016 popular 2016 prospect 2016-longchamp 2016-popular 2016-prospect 2016baby 2016longchamp 2016x. 2017 popular 2017-popular 123456789 ¤¤ $00 $10 $20 $30 $40 $50 $60 $70 $80 $90 ¥· ¥° ¥¦ ¥¤ ¥¢ ¥³ ¥ã ¥é ¥è ¥ì ¥ó ¥ö a http a lots 㨠a-cell-phone a-lots a-powerful-way a-web-designer a?a a.@ a.pri.l 㧠㶠㩠㉠a‡ ä± ã± ã¬ a0.asp a00.asp a0.cfm a00.cfm a0.htm a00.htm a0.jsp a00.jsp a0.php a00.php a01.asp a1.asp a01.cfm a1.cfm a1.co a01.htm a1.htm a01.jsp a1.jsp a01.php a1.php a02.asp a2.asp a02.cfm a2.cfm a02.htm a2.htm a02.jsp a2.jsp a02.php a2.php a03.asp a3.asp a03.cfm a3.cfm a03.htm a3.htm a03.jsp a3.jsp a03.php a3.php a04.asp a4.asp a04.cfm a4.cfm a04.htm a4.htm a04.jsp a4.jsp a04.php a4.php a05.asp a5.asp a05.cfm a5.cfm a05.htm a5.htm a05.jsp a5.jsp a05.php a5.php a06.asp a6.asp a06.cfm a6.cfm a06.htm a6.htm a06.jsp a6.jsp a06.php a6.php a07.asp a7.asp a07.cfm a7.cfm a07.htm a7.htm a07.jsp a7.jsp a07.php a7.php a08.asp a8.asp a08.cfm a8.cfm a08.htm a8.htm a08.jsp a8.jsp a08.php a8.php a09.asp a9.asp a09.cfm a9.cfm a09.htm a9.htm a09.jsp a9.jsp a09.php a9.php 㤠㢠㣠㥠†㹠㲠㪠aaa replica aaa-replica aaabbb aaik392m aall my aall-my aam happy abcwatch abdominaux abercrom abercrombie deutsch abercrombie kid abercrombie man abercrombie men abercrombie out abercrombie uomo abercrombie wom abercrombie_ abercrombie-deutsch abercrombie-ital abercrombie-kid abercrombie-man abercrombie-men abercrombie-out abercrombie-uomo abercrombie-wom abercrombiee abercrombieital abercrombiekid abercrombieman abercrombiemen abercrombieout abercrombieuomo abercrombiewom abilify about blog about marijuana about_ about-blog about-marijuana about-trillion about/date abouther abouttrillion abssice 360 abssice-360 abssice360 acai diet acai- acai-diet acai.diet acaiberry according your according-your accordion hurricane accordion-hurricane accouchement account receiv account-receiv accounts receiv accounts-receiv accupril accutane acel google acel-google acellphone acertemail acetazolamide acheter air acheter dolce acheter sac acheter-air acheter-dolce acheter-sac acheterdolce achetersac achilles pain achilles-pain acid-reflux acne cyst acne face acne prescript acne treatment acne-cyst acne-face acne-prescript acne-treatment acnecyst acneface acneprescript acnetreatment acomplia across thiss across-thiss activation code activity/p/ actonel actual celebrity actual effort actual submit actual thank actual-celebrity actual-effort actual-submit actual-thank actualcelebrity actually dont actually thank actually-dont actually-thank acutual acyclovir ad crack ad live ad-crack ad-live ad-wiki adbeat distrib adbeat expert adbeat trial adbeat-distrib adbeat-expert adbeat-trial adbeatdistrib adbeatexpert adbeattrial adcrack addarticle added agreeable adderall adderoll addicted-to addidas additionally fly additionay addon_ adenosyl adidas adizero adidas blanche adidas f5 adidas footbal adidas fotbal adidas futbol adidas jeremy adidas js adidas origin adidas out adidas porsche adidas schuh adidas shop adidas slip adidas super adidas uomo adidas wing adidas-adizero adidas-blanche adidas-corner adidas-f5 adidas-footbal adidas-fotbal adidas-futbol adidas-jeremy adidas-js adidas-orig adidas-out adidas-porsche adidas-schuh adidas-shop adidas-slip adidas-super adidas-uomo adidas-wing adidasblanche adidasf5 adidasfootbal adidasfotbal adidasfutbol adidashop adidasjeremy adidasjs adidasorig adidasout adidasporsche adidasschuh adidasshop adidasslip adidassuper adidasuomo adidaswing adidaz adipex adjust handbag adjust-handbag adlive admin.asp admin.cfm admin.htm admin.jsp admin.php admin5 ados populaire ados-populaire adospopulaire adremus viral adremus-viral adremusviral ads ad ads crack ads live ads-ad ads-crack ads-live adscrack adsjeremy adslive adsplus adsuse adswings adult base adult dat adult dvd adult erotic adult girl adult sex adult story adult toy adult video adult-base adult-dat adult-dvd adult-erotic adult-girl adult-net adult-sex adult-story adult-toy adult-video adultbase adultdat adultgirl adultsex adultstory adulttoy advanced hemp advanced-hemp advantages_ advantages-of advantages+of advert market advert research advert-market advert-research advertise market advertise your advertise-market advertise-your advertising market advertising research advertising-market advertising-research advice-adult advice.adult advok adwings adwokacka adwokat adwords æ÷ aff.asp aff.cfm aff.htm aff.php affiliate click affiliate link affiliate ppc affiliate review affiliate-click affiliate-link affiliate-ppc affiliate-review affiliateclick affiliatelink affiliateppc affiliatereview affliction jean affliction men affliction new affliction offer affliction-jean affliction-men affliction-new affliction-offer afflictionjean afflictionmen afflictionnew afflictionoffer afford afford afford-afford affordable afford affordable hand affordable-afford affordable-hand afl jers afl official afl replica afl shop afl-jers afl-official afl-replica afl-shop afl.asp afl.cfm afl.htm afl.jsp afl.php afljers aflofficial aflreplica aflshop african-canadian-model africancanadianmodel ムagario hack agario-hack agariohack age.webeden age| agedepot ageless female ageless male ageless men ageless women ageless-female ageless-male ageless-men ageless-women agen bola agen-bola agenbola agencje- agent denim agent direct agent-denim agent-direct agentdenim ago| âgrâve ahttp aint goin aint-goin air max air-jord air-max air-yeezy airjord airmaix airmax airmax_ airport-- airyeezy aitaiwan akad hilfe akad-hilfe akadhilfe akkuschrauber aklotr akril-panel akrilpanel aksudmias alarmingly grow alarmingly-grow albanian-travel albaniantravel albion craft albion gold albion online albion-craft albion-gold albion-online albioncraft albiongold albiononline albuterol alcohol-rehab alcohool alcoohol alendronate alien-wheel alienwheel alkotla all ahout all-about-the all-ahout allbestedmed allbestmed allianz/ allinone allowd allpay allright allso visit allso-visit alltheholi alluring escort alluring-escort allworkhome alone place alone-place alpha-hoverboard alphahoverboard alprazolam also eable also-eable alviero martini alviero-martini alvieromartini amateur-homemade amateur. amateurhomemade amateurs. amatuer amazing post amazing sex amazing-post amazing-sex amazingsex amazn.co amazon.asp amazon.cfm amazon.cm amazon.htm amazon.jsp amazon.php ambalaje plastic ambalaje-plastic ambalajeplastic ambien ametuer aminocare amitriptyline amorti dynamique amorti-dynamique amount food amount-food amountfood amour gratuit amour-gratuit amourgratuit amoxicil amoxil amulet coin amulet-coin amusement account amzon.co anabolic anal plug anal sex anal-plug anal-sex analplug analsex and bye. and shate and-shate and-special andcloth andcoupon anditan andjourn andmerciful android hack android-hack android-m4a android-mp3 androidclan androidhack androidm4a androidmp3 andtheir anelli cartier anelli oro anelli-cartier anelli-oro anellicartier anellioro aneswr angebote angel porn angel-porn angel.porn angelporn angels porn angels-porn angels.porn angelsporn anilinovyye kraski anilinovyye-kraski animalbased animated porn animated-porn animated.porn animatedporn ankle bootie ankle-bootie anklebootie anneed from annonce gratuit annonce-gratuit annoncegratuit annonces gratuit annonces-gratuit annoncesgratuit announce suggest announce-suggest announced suggest announced-suggest anonymity! anonymize your anonymous-download anonymousvpn another chernobyl another-chernobyl answertraff anti-aging antiage antiaging antispam antivirus/index antivirussen antykorozje beton antykorozje-beton anuncios anus live anus online anus-live anus-online anynihtg 㜠apartamentow aperfect apex bionic apex-bionic apexbionic apicpreate app.asp app.cfm app.htm app.jsp app.lk app.php appear conseq appear-conseq apple wedding apple-unlock apple-wedding appleunlock applewedding appro-chait appyourself apr.i.l aquarius-compat arab sex arab-girl arab-sex arab.girl arab.sex arabgirl arabsex arcteryx-jap arcteryx-jp arcteryx-sale arcteryxjap arcteryxjp arcteryxsale are added- are desirous are pround are-desirous are-pround areplica areshade aricept aripiprex arleitcs armagard.co armani cloth armani cost armani-cloth armani-cost armanicloth armanicost armanito aroma paradise aroma-paradise aromaparadise arsenal jers arsenal shirt arsenal-jers arsenal-shirt arsenaljers arsenalshirt artemfrolov article buzz article complet article content article dude article many article much article plus article pocket article post article pow article rewriter article sub article tag article-buzz article-complet article-content article-dude article-many article-much article-plus article-pocket article-post article-pow article-rewriter article-sub article-tag article,i article! article.asp article.cfm article.co article.htm article.in article.jsp article.many article.much article.net article.org article.php article.pl article.really article.ro article.ru article.su article.thank article.za article/post article/suggest articlebuzz articlecontent articledude articleplus articlepost articlepow articles buzz articles content articles dude articles plus articles post articles pow articles sub articles tag articles-buzz articles-content articles-dude articles-plus articles-post articles-pow articles-sub articles-tag articles,i articles! articles.asp articles.cfm articles.co articles.in articles.jsp articles.net articles.org articles.php articles.pl articles.really articles.ro articles.ru articles.su articles.thank articles.za articles/post articles/suggest articlesbuzz articlescontent articlesdude articlesplus articlespost articlespow articlessub articlestag articlesub articletag artikel artiklar snart artiklar-snart artisan plombier artisan-plombier artisanplombier artsilec artykul asd@ asdasd asdf asian porn asian sex asian-pic asian-porn asian-sex asian-teen asian.sex asian.teen asianpic asianporn asiansex asianteen asics gel asics online asics paris asics schuh asics_ asics-deutschland asics-gel asics-online asics-paris asics-schuh asicsdeutschland asicsgel asicsonline asicsparis asicsshoe asikkusu askjeeve aso expei asos coupon asos-coupon asoscoupon asp?folder asp?id asp?tag asphalt8cheat asphalt8hack asphyx dvd asphyx erotic asphyx video asphyx-dvd asphyx-erotic asphyx-video aspiring blog aspiring-blog aspnet_client ass small ass-lick ass-small asset grow asset-grow assets grow assets-grow asshole google asshole- assistance informatique assistance-inform assistance-informatique asslick associate link associate-link assortiment assured storm assured-storm astounding1 astounding2 astounding3 astounding4 astounding5 astral-diamond astute command astute-command asutralia aswenr at web, atempting athletica lululemon athletica-lululemon ationincome ativan atomoxetine atonemen's tip atonemen’s tip atonemens tip atonemens-tip atorvastatin atricky attraction market attraction-market aufsatz hilfe aufsatz-hilfe aufsatzhilfe augmentin_ augmentin. auspicious repl auspicious writ auspicious-repl auspicious-writ australia boot australia clearance australia jersey australia out australia-boot australia-clearance australia-jersey australiaboot australiaclearance australian poker australian pokie australian slot australian-poker australian-pokie australian-slot austria asic austria-asic austriaasic autentica cheap autentica hermes autentica jord autentica_ autentica-cheap autentica-jord autentica-ugg autenticahermes autenticajord autenticaugg autentico cheap autentico hermes autentico jord autentico_ autentico-cheap autentico-jord autentico-ugg autenticohermes autenticojord autenticougg auteur nike auteur-nike auteurnike authentic cheap authentic hermes authentic jord authentic stephen authentic_ authentic-cheap authentic-jord authentic-stephen authentic-ugg authentichermes authenticjord authenticstephen authenticugg authored subject authored-subject auto brand auto-brand auto.brand autobrand autoclinics.co. autocom automatic-pay automaticbackup. automaticpay autre artic autre-artic autres equip autres-equip available proper available-proper avatar-live avvessorie avvessory away your away-your awesome article awesome blog awesome page awesome post awesome weblog awesome-article awesome-blog awesome-page awesome-post awesome-weblog awesoome awokehim awordpress. awsome azria out azria-out azriaout azur-lv aρ aϲ aг aԁ aѕ aі aҟ aу aү aх aһ aь ɑb ɑc ɑd ɑe ɑl ɑs ɑt ɑv ɑw ɑy b http b-ig-event b.@ b.u.y b0.asp b00.asp b0.cfm b00.cfm b0.htm b00.htm b0.jsp b00.jsp b0.php b00.php b01.asp b1.asp b01.cfm b1.cfm b01.htm b1.htm b01.jsp b1.jsp b01.php b1.php b02.asp b2.asp b02.cfm b2.cfm b02.htm b2.htm b02.jsp b2.jsp b02.php b2.php b03.asp b3.asp b03.cfm b3.cfm b03.htm b3.htm b03.jsp b3.jsp b03.php b3.php b04.asp b4.asp b04.cfm b4.cfm b04.htm b4.htm b04.jsp b4.jsp b04.php b4.php b05.asp b5.asp b05.cfm b5.cfm b05.htm b5.htm b05.jsp b5.jsp b05.php b5.php b06.asp b6.asp b06.cfm b6.cfm b06.htm b6.htm b06.jsp b6.jsp b06.php b6.php b07.asp b7.asp b07.cfm b7.cfm b07.htm b7.htm b07.jsp b7.jsp b07.php b7.php b08.asp b8.asp b08.cfm b8.cfm b08.htm b8.htm b08.jsp b8.jsp b08.php b8.php b09.asp b9.asp b09.cfm b9.cfm b09.htm b9.htm b09.jsp b9.jsp b09.php b9.php babycanread babyliss backlink baclofen bad credit bad-credit badcredit bag cartier bag crash bag gucci bag jap bag jp bag out bag-cartier bag-cheap bag-crash bag-gucci bag-jap bag-jp bag-online bag-out bag.asp bag.cfm bag.htm bag.jsp bag.php bagcartier bagcheap bagcrash baggucci bagjap bagjp bagmirror bagonline bagonsale bagout bags afford bags cartier bags gucci bags jap bags jp bags online bags out bags sale bags uk bags-afford bags-cartier bags-cheap bags-gucci bags-jap bags-jp bags-online bags-out bags-uk bagsale bagscartier bagscheap bagsgucci bagsjap bagsjp bagsonline bagsout bagssale bagsstore bagstore bagsuk bague bulgari bague bvlgari bague-bulgari bague-bvlgari baguebulgari baguebvlgari baikal extreme baikal rest baikal-extreme baikal-rest baikalextreme baikalrest bailey ugg bailey-ugg baileyugg bajardepeso balance espa balance-espa balance-wheel balanceespa balancewheel balenciagasingapore ballgowns dress ballgowns-dress ballgownsdress bally store bally-store ballystore ban aviat ban barata ban barato ban handle ban händle ban jack ban lage ban mirror ban occhiali ban online ban prei ban prezzo ban price ban rb ban schwar ban sonnen ban sunglass ban verspie ban wayfare ban-barata ban-barato ban-jack ban-mirror ban-occhiali ban-online ban-prezzo ban-price ban-sunglass banbarata banbarato bancshare bang bros bang buddy bang-bros bang-buddy bangbros bangbuddy bangkok cosplay bangkok-cosplay bangkokcosplay banjack bank credit bank-credit bank24.ru bank24.su bankbybank bankcredit bankowoz bankrobber banonline banprezzo bansunglass barata online barata person barata ropa barata_ barata-online barata-person barata-ropa barataonline barataperson barataropa baratas new baratas online baratas person baratas_ baratas-new baratas-online baratas-person baratasnew baratasonline baratasperson barato online barato person barato ropa barato_ barato-online barato-person barato-ropa baratoonline baratoperson baratoropa baratos new baratos online baratos person baratos ropa baratos_ baratos-new baratos-online baratos-person baratos-ropa baratosnew baratosonline baratosperson baratosropa barbour cloth barbour coat barbour jack barbour out barbour-cloth barbour-coat barbour-jack barbour-out barbourcloth barbourcoat barbourjack barbourout barcelona sunglass barcelona-sunglass barcelonasunglass bardot lancel bardot-lancel bardotlancel barn timber barn-timber barntimber basket chanel basket isabel basket jord basket mbt basket shoe basket-chanel basket-isabel basket-jord basket-mbt basket-shoe basketball jord basketball word basketball-jord basketball-shoe basketball-word basketballjord basketballshoe basketballword basketchanel basketisabel basketjord basketmbt basketshoe bat-pro baykal extreme baykal otdykh baykal ozero baykal rest baykal-extreme baykal-otdykh baykal-ozero baykal-rest baykalextreme baykalotdykh baykalozero baykalrest bayswater bag bayswater-bag bayswaterbag baza dannyh baza-dannyh bazargorj bazooka app bazooka-app bazookaapp bɑ bbombr bbrand bcbg casual bcbg dress bcbg printed bcbg runway bcbg sleeve bcbg strapless bcbg_ bcbg-casual bcbg-dress bcbg-printed bcbg-runway bcbg-sleeve bcbg-strapless bcbgcasual bcbgprinted bcbgrunway bcbgsleeve bcbgstrapless bddv be benefited beamten beanies1 bears hat bears urlacher bears-hat bears-urlacher bearsurlacher beat gainer beat loser beat-gainer beat-loser beatbydre beats by beats monster beats pas beats studio beats-best beats-by beats-dr-dre beats-dre beats-monster beats-pas beats-studio beats+ beatsbest beatsbydrdre beatsbydre beatscustom beatsdrdre beatsdre beatsmonster beatsstudio beaut beaut beaut bio beaut-beaut beaut-bio beautiful-wom beautifulwom beauty-report beautyreport becausethe become-a-success bee-pollen. beeday beenpaid before-purchas behaviour-driven behind-knee behindknee beijing escort beijing massage beijing-escort beijing-massage beijingescort beijingmassage being pput being-pput belize propert belize-propert belizepropert belly-fat belstaff bota belstaff chaqueta belstaff cuero belstaff espa belstaff hand belstaff leder belstaff out belstaff-bota belstaff-chaqueta belstaff-cuero belstaff-espa belstaff-hand belstaff-leder belstaff-out belstaffbota belstaffchaqueta belstaffcuero belstaffespa belstaffhand belstaffleder belstaffout belt replica belt-replica beltreplica bengals merch bengals store bengals-merch bengals-store bengalsmerch bengalsstore benzo generat benzo-generat benzoclin benzogenerat benzoylmethyl berlin moncler berlin-moncler berlinmoncler berracom.ph besstet best cartier best cc best cyber best dumps best evance best forex best jers best mcm best online best pant best payment best suppl best tummy best website best wordpress best xxx best-cartier best-cc best-cyber best-diet best-direct best-disc best-dumps best-evance best-fake best-forex best-home-cinema best-jers best-mcm best-pant best-payment best-phone best-shop best-software-for best-suppl best-tummy best-way-to best-website best-wordpress best-xxx best4 bestad. bestads. bestbut bestcartier bestcase bestcontractor bestdirect bestdisc bestdrug bestevance bestfake bestfit bestforex bestgasmileage bestjers bestlaptop bestmcm bestpant bestpayment bestphone bestshop bestsuppl besttummy bestwebsite bestxxx betes0 betes1 betes2 betes3 betes4 betes5 betes6 betes7 betes8 betes9 betlist betterjudg betterserve betweenex bhttp bianca black bianca-black biancablack biaxin bieding laarzen bieding-laarzen biedinglaarzen bielizna big 100% big bling big cock big porn big pussy big replica big site big tits big titt big-100% big-bling big-cock big-porn big-pussy big-replica big-site big-tits big-titt big.site bigbling bigcock bigporn bigpussy bigreplica bigsite bigtits bigtitt bijoux best bijoux_ bijoux-best bijoux-fr bijouxbest bijouxfr bijsluiter tablet bijsluiter-tablet bikini outlet bikini sale bikini salg bikini udsalg bikini-outlet bikini-sale bikini-salg bikini-udsalg bikinier udsalg bikinier-udsalg bikinierudsalg bikinioutlet bikinisale bikinisalg bikiniudsalg billig billion yuan billion-yuan billionyuan binarnykh optsion binarnykh-optsion bio bronz bio skin bio-bronz bio-skin biobronz biofinite skin biofinite-skin biofiniteskin bioskin bird hack bird-hack birdhack birkenstock aus birkenstock boston birkenstock online birkenstock outlet birkenstock sale birkenstock sandal birkenstock shop birkenstock store birkenstock-aus birkenstock-boston birkenstock-online birkenstock-outlet birkenstock-sale birkenstock-sandal birkenstock-shop birkenstock-store birkenstockaus birkenstockboston birkenstockonline birkenstockonsale birkenstockoutlet birkenstocksale birkenstocksandal birkenstockshop birkenstockstore birkin bag birkin-bag birkinbag bisapp bitartrate bitcoin acc bitcoin add bitcoin depos bitcoin donat bitcoin wallet bitcoin_ bitcoin-acc bitcoin-add bitcoin-depos bitcoin-donat bitcoin-wallet bitcoinacc bitcoinadd bitcoindepos bitcoindonat bitcoinwallet biuro-solido biz.in biz.pl biz.ru biz.su biz.za biznes bizstore biztalk biztout biżuteria black gay black tight black tits black ugg black-friday black-gay black-tight black-tits black-ugg black-www blackfriday blackgay blackjack pit blackjack_ blackjack-pit blackjackpit blacklabelj blacktight blacktits blackugg blahnik out blahnik replica blahnik shoe blahnik shop blahnik store blahnik_ blahnik-out blahnik-replica blahnik-shoe blahnik-shop blahnik-store blahnikout blahnikreplica blahnikshoe blahnikshop blahnikstore blanc pen blanc starwalk blanc-pen blanc-starwalk blancstarwalk blazer chaus blazer-chaus blazerchaus blog based blog beast blog glance blog here blog layout blog link blog loading blog look blog occasion blog platform blog post blog quite blog site blog soon blog struct blog sys blog thus blog video blog viewer blog web blog world blog_ blog-based blog-beast blog-entry blog-glance blog-here blog-layout blog-link blog-loading blog-look blog-occasion blog-platform blog-post blog-quite blog-site blog-soon blog-struc blog-sys blog-thus blog-trick blog-video blog-viewer blog-web blog-world blog: blog.best blog/blog blog/comment blog/index blog/lala blog/online blog/owner blog/post blog/view blog< blogbased blogbeast blogentry blogger lover blogger_ blogger-lover bloggerlover blogging look blogging-look blogginglook bloghome blogid bloglayout bloglink blogloading bloglook blogów blogplatform blogrtui.ru blogs_ blogs-trick blogs.bl blogs/entry blogs/item blogs/post blogsite blogsoon blogsys blogtitle blogviewer blogweb blogworld blow-job blowjob blu-cig blucig blue ugg blue-ugg bluefin-trad bluefintrad bluehost-review bluehostreview bluetooth jammer bluetooth-head bluetooth-jammer bluetoothhead bluetoothjammer blueugg boaby board sale board-sale boards sale boards-sale boardsale boardssale bobet link bobet-link bobetlink bodog8 body erotic body-erotic bola terpercaya bolsos marc bolsos-marc bolsosmarc bon copie bon-copie boncopie bonne copie bonne-copie bonnecopie bono casino bono cassino bono-casino bono-cassino bonocasino bonocassino bontril bonus-code bonuscode boobs tumblr boobs-tumblr book-marked book-marks book-ot- bookmark thank bookmark web bookmark-thank bookmark-web bookmarked thank bookmarked web bookmarked-thank bookmarked-web bookmarked! bookmarks thank bookmarks web bookmarks-thank bookmarks-web bookmarksweb bookmarkweb boost you boost-you boost-your boostyou boostyour boot get boot online boot oxford boot queen boot sale boot schweiz boot ugg boot uk boot-get boot-online boot-oxford boot-queen boot-sale boot-schweiz boot-ugg boot-uk bootcojp bootget bootonline bootoxford bootqueen boots online boots oxford boots sale boots schweiz boots ugg boots uk boots-get boots-in-schweiz boots-online boots-oxford boots-queen boots-retail boots-sale boots-schweiz boots-ugg boots-uk bootsale bootschweiz bootsget bootsinschweiz bootsonline bootsoxford bootsqueen bootsretail bootss bootsugg bootsuk boottenngoku bootugg bootuk booty galler booty-galler borsa celine borsa chanel borsa ital borsa louis borsa luis borsa moncler borsa out borsa prada borsa-celine borsa-chanel borsa-ital borsa-louis borsa-luis borsa-moncler borsa-out borsa-prada borsaceline borsachanel borsaital borsalouis borsaluis borsamoncler borsaout borsaprada borse celine borse chanel borse ital borse louis borse luis borse moncler borse out borse prada borse prezzi borse-celine borse-chanel borse-ital borse-louis borse-luis borse-moncler borse-out borse-prada borse-prezzi borseceline borsechanel borseital borselouis borseluis borsemoncler borseout borseprada borseprezzi bot cheat bot-cheat bot's cheat bot’s cheat bota mujere bota-mujere botamujere botanical slim botanical-slim botas altas botas mujere botas ugg botas-altas botas-mujere botas-ugg botasdefutbol botasmujere botasugg botcheat both educative both-educative bots cheat bots-cheat botscheat botte femme botte-femme bottefemme bottega veneta bottega-veneta bottegaveneta bottes femme bottes paris bottes pas bottes ugg bottes-en-ligne bottes-femme bottes-paris bottes-pas bottes-ugg bottesenligne bottesfemme bottesparis bottespas bottesugg boutique balenciaga boutique chanel boutique chaus boutique mbt boutique moncler boutique ugg boutique-balenciaga boutique-chanel boutique-chaus boutique-mbt boutique-moncler boutique-ugg boutiquebalenciaga boutiquechanel boutiquechaus boutiquembt boutiquemoncler boutiques chanel boutiques chaus boutiques mbt boutiques moncler boutiques ugg boutiques-chanel boutiques-chaus boutiques-mbt boutiques-moncler boutiques-ugg boutiqueschanel boutiqueschaus boutiquesmbt boutiquesmoncler boutiquesugg boutiqueugg bow ugg bow-ugg bowling footwear bowling-footwear bowugg boxshopping.ru bracelet manchette bracelet shop bracelet store bracelet_ bracelet-manchette bracelet-sale bracelet-shop bracelet-store braceletmanchette braceletsale braceletshop braceletstore brain abundance brain-abundance brainabundance brand baseball brand bat brand engage brand iwc brand jap brand jp brand mlb brand nba brand nfl brand nhl brand wto brand-baseball brand-bat brand-corner brand-engage brand-iwc brand-jap brand-jp brand-mlb brand-nba brand-nfl brand-nhl brand-wto brandengage brandiwc brandjap brandjp brandpurse brands-jap brands-jp brandsjap brandsjp brandwto brasil bikini brasil-bikini brasilbikini bravemed braves jers braves-jers bravesjers brazil bikini brazil-bikini brazilbikini brazzer breakfast coming breakfast-coming breastactive bridal-gown bridalgown bridalshop brilliant content brilliant-content brinkjewel britanniahotel brittany-battle brittanybattle brittny-battle brittnybattle broncos hat broncos-hat broncos-jers broncos-official broncoshat broncosjers broncosofficial brothershit browns jersey browns-jersey brrip brugte mulberry brugte-mulberry bruiseviolet bruno cabas bruno sac bruno-cabas bruno-sac brunocabas brunosac btitish btn-phone btshoe buddie buddreview budynku wybrac budynku-wybrac bukmacher bulgari bague bulgari bijoux bulgari brand bulgari bulgari bulgari bzero bulgari out bulgari serpent bulgari shop bulgari store bulgari_ bulgari-bague bulgari-bijoux bulgari-brand bulgari-bulgari bulgari-bzero bulgari-out bulgari-serpent bulgari-shop bulgari-store bulgaribague bulgaribijoux bulgaribrand bulgaribulgari bulgaribzero bulgarie bijoux bulgarie-bijoux bulgariebijoux bulgariout bulgariserpent bulgarishop bulgarisshop bulgaristore bulk email bulk mail bulk-email bulk-mail bulkemail bulkmail bulletproof merced bulletproof-merced bullion-account bumpant burberry bag burberry belt burberry bors burberry brit burberry clear burberry coat burberry danmark burberry factor burberry lad burberry negozi burberry out burberry purse burberry sale burberry scarf burberry store burberry task burberry thai burberry uk burberry wallet burberry watch burberry-au burberry-belt burberry-bors burberry-brit burberry-clear burberry-coat burberry-danmark burberry-hand burberry-in burberry-lad burberry-negozi burberry-out burberry-purse burberry-sale burberry-scarf burberry-task burberry-too burberry-wallet burberry-watch burberry.bl burberryau burberrybelt burberrybi burberryblack burberrybors burberrybrit burberryclear burberrycoat burberrydanmark burberryhand burberryin burberrylad burberrynegozi burberryout burberrypurse burberrysale burberryscarf burberrysold burberrystore burberrysuto burberrytaschen burberrytask burberrytoo burberrywallet burberrywatch burch-out burch-sale burchout burchsale business catalyst business empire business know business train business-boss business-catalyst business-daily business-empire business-finder business-first business-in business-intel business-know business-loan business-market business-net business-network business-train businessboss businesscatalyst businessdaily businessfinder businessfirst businessintel businessknow businessloan businessmarket businessnetwork businesstrain busiunes buspirone busquemail busythumb butikk butnow butthe button ugg button-ugg buttonugg butwho bux cheap bux-cheap buy acrylic buy ageless buy blade buy canadagoose buy cheap buy didrex buy dump buy duvetica buy face buy fifa buy generic buy gig buy gold buy hair buy hermes buy hoverboard buy insta buy jap buy jord buy jp buy likes buy louis buy movie buy online buy privat buy qsymia buy silver buy toms buy traff buy women buy-acrylic buy-blade buy-canadagoose buy-cheap buy-cig buy-didrex buy-dump buy-duvetica buy-face buy-fifa buy-generic buy-gig buy-gold buy-gucci buy-hair buy-hoverboard buy-insta buy-jap buy-jord buy-jp buy-likes buy-louis buy-movie buy-now buy-online buy-pill buy-plus buy-privat buy-qsymia buy-run buy-silver buy-soma buy-toms buy-top buy-traff buy-women buy.top buycanadagoose buycheap buycig buyduvetica buyface buygeneric buygold buyhair buyhermes buyhoverboard buying face buying fifa buying insta buying likes buying rune buying traff buying-face buying-fifa buying-insta buying-likes buying-rune buying-traff buyingface buyingfifa buyinginsta buyinglikes buyingrune buyingtraff buyjap buyjord buyjp buylikes buylouis buymovie buyonline buyout keep buyout_ buyout-keep buyplus buyrun buysilver buysoma buytoms buytraff buywomen bvlgari bague bvlgari bijoux bvlgari brand bvlgari bvlgari bvlgari bzero bvlgari jap bvlgari jp bvlgari out bvlgari serpent bvlgari shop bvlgari store bvlgari uk bvlgari_ bvlgari-bague bvlgari-bijoux bvlgari-brand bvlgari-bvlgari bvlgari-bzero bvlgari-jap bvlgari-jp bvlgari-out bvlgari-serpent bvlgari-shop bvlgari-store bvlgari-uk bvlgaribague bvlgaribijoux bvlgaribrand bvlgaribvlgari bvlgaribzero bvlgarijap bvlgarijp bvlgariout bvlgariserpent bvlgarishop bvlgarisshop bvlgaristore bvlgariuk bxox.in by-dr-dre bymean byo.co byt.es bе bі bу bү bօ c http c.@ c.a.rol c.ar.ol c.aro.l c.he.a.p c.he.ap c.r.e.a.s.e c.r.e.a.se c.r.e.ase c.r.ea.se c.r.eas.e c.r.ease c.re.ase c.rea.se c.reas.e c.urr.ent c|pro ca.r.ol ca.ro.l cabas vanessa cabas-vanessa cabaser cabasvanessa cabergoline caberlin cabin lithu cabin-lithu cabins it cabins lithu cabins-it cabins-lithu caellis cagoose jack cagoose sale cagoose-jack cagoose-sale cagoosejack cagoosesale calitate super calitate-super callgirl calvin mujer calvin-mujer calvinmujer calzature mbt calzature-mbt calzaturembt cambogia camgirl camicie abercrom camicie negozi camicie-abercrom camicie-negozi camicieabercrom camicienegozi camisa hollis camisa-hollis camisahollis camisas ralph camisas-ralph camisasralph camiseta mlb camiseta nba camiseta nfl camiseta nhl camiseta_ camiseta-nba camiseta-nfl camisetas hollis camisetas mlb camisetas nba camisetas nfl camisetas nhl camisetas polo camisetas_ camisetas-hollis camisetas-nba camisetas-nfl camisetas-polo camisetashollis camisetaspolo can-help-you canada drug canada hgh canada loan canada pharm canada-drug canada-hgh canada-loan canada-pharm canadadrug canadagoose-ca canadagoose-factor canadagoose-fr canadagoose-online canadagoose-out canadagooseannka canadagoosebanff canadagooseca canadagoosefactor canadagoosejack canadagoosemen canadagooseonline canadagooseout canadagoosepark canadagooses canadagooses-factor canadagooses-out canadagoosesfactor canadagoosesout canadahgh canadaloan canadaout canadian drug canadian hgh canadian loan canadian pharm canadian-drug canadian-hgh canadian-loan canadian-pharm canadiandrug canadianhgh canadianloan candy crash candy-crash candy-crush candycrash candycrush cannabis buy cannabis grow cannabis oil cannabis seed cannabis-buy cannabis-grow cannabis-oil cannabis-seed cannabisbuy cannabisgrow cannabisoil cannabisseed capabilities also capabilities-also capecitabine cappelli boston cappelli new cappelli nfl cappelli-boston cappelli-new cappelli-nfl captain rank captain-rank captainrank captcha snip captcha-snip captchasnip car.o.l carb nite carb-nite card-debt carder board carder-board carders board carders-board cardy boot cardy-boot cardyboot care cream care-cream carecream carinsur carisoprodol carrera bat carrera lune carrera-bat carrera-lune carreralune carreramoinscher cars game cars insur cars-game cars-insur carsgame carsinsur cartier anelli cartier fidanza cartier love cartier replica cartier uomo cartier_ cartier-anelli cartier-fidanza cartier-love cartier-replica cartier-uomo cartieranelli cartierfidanza cartierlove cartierreplica cartieruomo cartuchos casaemail casebycase caserole plastic caserole-plastic caseroleplastic casesolution cash advance cash buzz cash extend cash generat cash loan cash-advance cash-buzz cash-extend cash-generat cash-loan cash4 cashadvance cashbuzz cashextend cashforgold cashforsilver cashgenerat cashloan cashout casino bonus casino enligne casino fish casino game casino hack casino online casino only casino phish casino_ casino-1 casino-bonus casino-enligne casino-fish casino-game casino-hack casino-online casino-only casino-phish casino.co casino1 casinobonus casinoenligne casinoer casinofish casinogame casinohack casinoonline casinoonly casinophish casinos- casolete plastic casolete-plastic casoleteplastic casque beats casque-beats casquebeats casquette cassino bonus cassino enligne cassino game cassino online cassino only cassino_ cassino-bonus cassino-enligne cassino-game cassino-online cassino-only cassino.co cassinobonus cassinoenligne cassinoer cassinogame cassinoonline cassinoonly cassinos_ cassinos- câsuâl casual sex casual-sex casualsex casub.co catalog-tabak catalog.asp catalog.cfm catalog.htm catalog.jsp catalog.php catalog/preview catalog/tabak catalogo/preview catalogs.asp catalogs.cfm catalogs.htm catalogs.jsp catalogs.php catalogue.asp catalogue.cfm catalogue.htm catalogue.jsp catalogue.php catalogues.asp catalogues.cfm catalogues.htm catalogues.jsp catalogues.php cavin-klein cavinklein cɑ cc shop cc-shop ce billet ce-billet celeb diet celeb-diet celebdiet celebrex celebrities-nude celebritiesnude celebrity diet celebrity-diet celebrity-nude celebritydiet celebritynude celexa celine bag celine bors celine boston celine boutique celine hand celine luggage celine paris celine port celine prezzi celine prix celine purse celine sac celine shop celine tote celine trapeze celine_ celine-bag celine-bors celine-boston celine-boutique celine-hand celine-luggage celine-paris celine-port celine-prezzi celine-prix celine-purse celine-sac celine-shop celine-tote celine-trapeze celine.gear celine/celine celinebag celinebors celineboston celineboutique celinehand celineluggage celineparis celineport celineprezzi celineprix celinepurse celinesac celineshop celinetote celinetrapeze cellskin cellulitefree cellulitereview celtics color celtics colour celtics-color celtics-colour celticscolor celticscolour celular cent whenever cent-whenever cent| cert line cert-line certain pronoun certain-pronoun certainly pronoun certainly-pronoun cgi-bin cgibin cgsaleca ch.e.ap ch.ea.p chack-tip chacktip chanclas hollis chanclas-hollis chanclashollis chandler baseball chandler-baseball chanel bag chanel faux chanel femme chanel hand chanel imitation chanel joaillerie chanel out chanel paris chanel purse chanel replica chanel replique chanel sac chanel spring chanel_ chanel-- chanel-bag chanel-faux chanel-femme chanel-hand chanel-imitation chanel-joaillerie chanel-online chanel-out chanel-paris chanel-purse chanel-replica chanel-replique chanel-sac chanel-sale chanel-spring chanel/chanel chanelbag chanelfaux chanelfemme chanelhand chanelimitation chaneljoaillerie chanelonline chanelout chanelparis chanelpurse chanelreplica chanelreplique chanelsac chanelsale chanelspring channel obtain channel-operator channels obtain chantix chapa shoe chapa-shoe chapashoe chaqueta belstaff chaqueta cuero chaqueta oferta chaqueta-belstaff chaqueta-cuero chaqueta-oferta chaquetabelstaff chaquetacuero chaquetaoferta chaquetas belstaff chaquetas cuero chaquetas oferta chaquetas-belstaff chaquetas-cuero chaquetas-oferta chaquetasbelstaff chaquetascuero chaquetasoferta charger drazen charger-drazen charlescave charms thomas charms-thomas charmsthomas chat-chat chatchat chaturbate chaussres boutique chaussres pascher chaussres-boutique chaussres-pascher chaussresboutique chaussrespascher chaussure adidas chaussure botte chaussure boutique chaussure jord chaussure loubou chaussure mbt chaussure nike chaussure paris chaussure supra chaussure_ chaussure-adidas chaussure-asic chaussure-botte chaussure-boutique chaussure-femme chaussure-homme chaussure-jord chaussure-loubou chaussure-mbt chaussure-nike chaussure-paris chaussure-puma chaussure-supra chaussureadidas chaussurebotte chaussureboutique chaussurembt chaussurenike chaussureparis chaussures adidas chaussures boutique chaussures christ chaussures de chaussures femme chaussures jord chaussures loubou chaussures mbt chaussures nike chaussures paris chaussures pascher chaussures salomon chaussures ski chaussures sport chaussures ugg chaussures_ chaussures- abil chaussures-adidas chaussures-asic chaussures-boutique chaussures-christ chaussures-de chaussures-femme chaussures-habil chaussures-homme chaussures-jord chaussures-loubou chaussures-mbt chaussures-nike chaussures-paris chaussures-pascher chaussures-puma chaussures-salomon chaussures-ski chaussures-sport chaussures-ugg chaussuresadidas chaussuresboutique chaussureschrist chaussuresfemme chaussuresfr chaussuresloubou chaussuresmbt chaussuresnike chaussuresparis chaussurespascher chaussuressport chaussuresupra chcoin.co cheap afl cheap air cheap atlanta cheap autentic cheap authentic cheap bag cheap basket cheap beat cheap bike cheap boot cheap carolina cheap carton cheap cheap cheap china cheap chinese cheap christ cheap denver cheap essay cheap ferrag cheap fifa cheap footbal cheap fotbal cheap futbol cheap ga cheap galaxy cheap gold cheap hockey cheap hotel cheap iphone cheap jack cheap jers cheap jersey cheap jord cheap lebron cheap longchamp cheap loubou cheap louis cheap mackage cheap mbt cheap michael cheap moncler cheap mont cheap mulberry cheap nfl cheap nhl cheap nike cheap north cheap oakley cheap paper cheap pokemon cheap price cheap proclip cheap ray cheap red cheap ring cheap sale cheap salvatore cheap sherr cheap silver cheap soccer cheap stephen cheap sunglass cheap supra cheap timber cheap toms cheap travel cheap ugg cheap uk cheap warrior cheap wed cheap wholes cheap youth cheap_ cheap-adobe cheap-afl cheap-atlanta cheap-autentic cheap-authentic cheap-bag cheap-basket cheap-beat cheap-bike cheap-boot cheap-carolina cheap-carton cheap-chanel cheap-cheap cheap-china cheap-chinese cheap-christ cheap-converse cheap-denver cheap-essay cheap-ferrag cheap-fifa cheap-footbal cheap-fotbal cheap-futbol cheap-ga cheap-gold cheap-gucci cheap-hat cheap-hermes cheap-hockey cheap-hotel cheap-jack cheap-jers cheap-jersey cheap-jord cheap-kobe cheap-lebron cheap-loubou cheap-mackage cheap-mbt cheap-michael cheap-moncler cheap-mont cheap-mulberry cheap-nfl cheap-nhl cheap-nike cheap-north cheap-oakley cheap-pandora cheap-paper cheap-pokemon cheap-prada cheap-price cheap-proclip cheap-red cheap-ring cheap-sale cheap-salvatore cheap-sex cheap-sherr cheap-silver cheap-soccer cheap-stephen cheap-sunglass cheap-supra cheap-timber cheap-tms cheap-toms cheap-travel cheap-ugg cheap-uk cheap-warrior cheap-web cheap-wed cheap-wholes cheap-youth cheap+ cheap< cheapadobe cheapafl cheapatlanta cheapautentic cheapauthentic cheapbag cheapbasket cheapbeat cheapbeatsbydre cheapbike cheapboot cheapchanel cheapchina cheapchinese cheapcoach cheapconverse cheapessay cheapest hockey cheapest pokemon cheapest price cheapest ray cheapest-hockey cheapest-pokemon cheapest-price cheapest-ray cheapest.co cheapesthockey cheapestprice cheapferrag cheapga cheapgold cheapgucci cheaphat cheaphermes cheaphockey cheapiphone cheapjack cheapjers cheapjersey cheapjord cheapkobe cheaplebron cheaploubou cheaplouis cheapmackage cheapmbt cheapmichael cheapmoncler cheapnfl cheapnike cheapnorth cheapoakley cheappandora cheappaper cheapprada cheapproclip cheapray cheapred cheapring cheaps ugg cheaps-ugg cheapsale cheapsalvatore cheapsex cheapsilver cheapstephen cheapsugg cheapsunglass cheapsupra cheaptimber cheaptms cheaptoms cheaptravel cheapugg cheapuk cheapwarrior cheapweb cheapwholes cheapyouth cheat master cheat_ cheat-master cheat.co cheatmaster cheats for cheats- cheats-for cheats.co cheatsfor cheatss check.asp check32attack cheeerd cheesefruit chefk chemise-ralph chemiseralph cher moncler cher prada cher-moncler cher-prada chermoncler cherprada chettelongchamp chez skechers chez-skechers chf iraq chf-iraq chic cassino chic mcm chic-casino chic-cassino chic-mcm chiccasino chiccassino chicmcm chid casino chik casino chik cassino chik-casino chik-cassino chikcasino chikcassino china cheap china dress china low china scarf china scarve china shop china wholes china-cheap china-dress china-low china-scarf china-scarve china-shop china-wholes chinacheap chinadress chinajord chinalow chinascarf chinascarve chinashop chinawholes chinese cheap chinese dress chinese low chinese scarf chinese scarve chinese shop chinese wholes chinese-cheap chinese-dress chinese-low chinese-scarf chinese-scarve chinese-shop chinese-wholes chinesecheap chinesedress chineselow chinesescarf chinesescarve chineseshop chinesewholes chinska chlamydia chloe boot chloe sunglass chloe-boot chloe-sunglass chloeboot chloejp chloemoment chloeoutsale chloesunglass chloie chlorzoxazone choo sale choo-boot choo-sale chooboot choose calorie choose-calorie chrismas.asp christian_ christian-loubou christian+ christianloubou chronic edge chronic-edge chrr-llc chttp chung hightop chung-hightop cɦ cialis ciallis cig buy cig holder cig-buy cig-holder cig-online cig-promo cig< cigar-cuba cigar-online cigar-store cigarcuba cigarette online cigarette-online cigarette.co cigarette< cigarettebuy cigarettes online cigarettes-online cigarettes.co cigarettes< cigarettesbuy cigaronline cigarrette cigars-cuba cigars-online cigarscuba cigarsonline cigarstore cigbuy cigonline cigpromo cigs buy cigs online cigs promo cigs-buy cigs-online cigs-promo cigs< cigsbuy cigsonline cigspromo cinco jers cinco-jers cinture gucci cinture out cinture-gucci cinture-out cinturegucci cintureout ciproanti city chung city-chung citychung cl-men claim yukon claim-yukon claim, yukon clans hack clans ipad clans iphone clans-hack clans-ipad clans-iphone clanshack clansipad clansiphone clarisonic clash hack clash-hack clash-of-clans clashhack clashofclans class web class-web classic-ugg classicugg classweb clavulanate clboot clean clear clean-clear cleanclear clear clarif clear clear clear-clarif clear-clear clearance mbt clearance michael clearance out clearance sale clearance-mbt clearance-michael clearance-on clearance-out clearance-sale clearance+ clearancembt clearancemichael clearanceout clearancesale clearclarif clearclear clenbuterol clentching cleveland jersey cleveland-jersey click affiliate click compan click here click_ click-affiliate click-compan click-here click2 click4 clickaffiliate clickbank clickcompan clickforu clicks4 clicksor clientarea climacool cliquant clomid clonazepam clone-key clonekey clothes bag clothes jack clothes online clothes_ clothes-bag clothes-jack clothes-online clothesbag clothesjack clothesonline clothing abercrom clothing online clothing-abercrom clothing-online clothingabercrom clothingonline clredheel clsale clshoe club-boy clubboy clusive vaca clusive-vaca clusivevaca clyel cms-temp cms-theme cn/lang cnoesfs co robic co-robic co.l.l.ect coach austr coach bag coach bay coach best coach black coach emboss coach factor coach flight coach hand coach jap coach jp coach kan coach legacy coach mean coach men coach mise coach new coach niho coach ninnki coach online coach out coach promo coach purse coach rouge coach shoe coach sneaker coach store coach suto coach syoppu coach thai coach tokyo coach wallet coach wrist coach ya coach_ coach-austr coach-bag coach-bay coach-best coach-black coach-emboss coach-factor coach-flight coach-hand coach-jap coach-jp coach-kan coach-legacy coach-mean coach-men coach-mise coach-new coach-niho coach-ninnki coach-online coach-out coach-promo coach-purse coach-rouge coach-shoe coach-sneaker coach-store coach-suto coach-syoppu coach-thai coach-tokyo coach-wallet coach-wrist coach-ya coach+ coach2you coachaustr coachbag coachbay coachbest coachblack coachemboss coachfactor coachflight coachhand coachjap coachjp coachkan coachlegacy coachmean coachmen coachmise coachnew coachniho coachninnki coachonline coachout coachpromo coachpurse coachrouge coachshoe coachsneaker coachstore coachsuto coachsyoppu coachthai coachtokyo coachwallet coachwrist coachya coast alva coast petite coast shift coast va coast-alva coast-petite coast-shift coast-va coastalva coastpetite coastshift coastva coat out coat-out coatout coats out coats-out coatsout code generat code promo code xbox code-generat code-promo code-xbox codegenerat codepromo codes generat codes promo codes xbox codes-generat codes-promo codes-xbox codesgenerat codesxbox codexbox coffee erectile coffee-erectile cohttp coin cheat coin foot coin fut coin game coin xbox coin-cheat coin-foot coin-fut coin-game coin-xbox coincheat coinfoot coinfut coingame coins foot coins fut coins game coins xbox coins-foot coins-fut coins-game coins-xbox coinsfoot coinsfut coinsgame coinss coinsxbox coinxbox colin-kaepernick collectif abssice collectif celine collectif moncler collectif-abssice collectif-celine collectif-moncler collectifabssice collectifceline collectifmoncler collection celine collection effort collection moncler collection-celine collection-effort collection-moncler collectionceline collectioneffort collectionmoncler college-loan collegeloan collezione celine collezione moncler collezione-celine collezione-moncler collezioneceline collezionemoncler color nude color-nude colornude colour nude colour-nude colournude colts hat colts-hat com_install com-install com.asp com.com com// com/author com/autocom com/bilder com/boards com/brand com/cheap com/css com/doc com/dress com/forum com/ftp com/htm com/include com/jordans com/log com/mbt com/member com/mk com/moncler com/official com/online com/p-aid com/page1 com/page2 com/pharm com/profil com/publi com/rest com/site com/tiffany com/ugg com/user com/vuitton com/www com/ysl com%2c com0.asp com0.cfm com0.htm com0.jsp com0.php com1.asp com1.cfm com1.htm com1.jsp com1.php com2.asp com2.cfm com2.htm com2.jsp com2.php com3.asp com3.cfm com3.htm com3.jsp com3.php com4.asp com4.cfm com4.htm com4.jsp com4.php com5.asp com5.cfm com5.htm com5.jsp com5.php com6.asp com6.cfm com6.htm com6.jsp com6.php com7.asp com7.cfm com7.htm com7.jsp com7.php com8.asp com8.cfm com8.htm com8.jsp com8.php com9.asp com9.cfm com9.htm com9.jsp com9.php comdip comedyee comhttp coming frlm coming-frlm comment boost comment speak comment you comment-boost comment-pag comment-speak comment-you comment/bv comment/celine comment/chanel comment/nike comment/north comment/rolex commentabout commented here commented-here commenting any commenting-any comments/bv comments/celine comments/chanel comments/nike comments/north comments/rolex commentspeak commentsyou commentyou commerce money commerce retail commerce sale commerce wholes commerce-money commerce-retail commerce-sale commerce-wholes commercemoney commerceretail commercesale commercewholes comming from comming-from community.atom commutee como ganhar como-ganhar company coach company ppc company-coach company-ppc companycoach compare price compare-price competitors-google component/blog comprar comprasion compresion b compresion-b computer pc computer-gam computer-pc computer-perform computergam computerperform computerpressrelease computers.in comreview comunity conficker worm conficker-worm consalt- consent with consent-with consequently styl consequently-styl consolidation loan consolidation-loan constructionn construtor contact_ contact/contact contactus/contact content material content-material control diet control-diet controldiet converse jap converse jp converse_ converse-jap converse-jp conversejap conversejp convey her. conydot cooker ninja cooker-ninja cookerninja cool article cool-article coolarticle coordintaing copie ugg copie-ugg copieugg copy scape copy scrape copy-scape copy-scrape copy-wizard copyscape copyscrape copywizard cordarone core fuck core sex core-fuck core-sex corefuck coresex corporate-gift corporategift coskobo cosm tique cosmetic eye cosmetic kit cosmetic sale cosmetic whole cosmetic-eye cosmetic-kit cosmetic-sale cosmetic-whole cosmeticeye cosmetickit cosmetics eye cosmetics kit cosmetics sale cosmetics whole cosmetics-eye cosmetics-kit cosmetics-out cosmetics-sale cosmetics-whole cosmeticsale cosmeticseye cosmeticskit cosmeticsout cosmeticssale cosmeticswhole cosmeticwhole cosmetique eye cosmetique sale cosmetique whole cosmetique-eye cosmetique-sale cosmetique-whole cosmetiqueeye cosmetiques eye cosmetiques sale cosmetiques whole cosmetiques-eye cosmetiques-sale cosmetiques-whole cosmetiquesale cosmetiqueseye cosmetiquessale cosmetiqueswhole cosmetiquewhole cosmtique cost-effective costume ermen costume homme costume mari costume medi costume tendance costume versace costume-ermen costume-homme costume-mari costume-medi costume-tendance costume-versace costumeermen costumehomme costumemari costumemedi costumes homme costumes-homme costumeshomme costumetendance costumeversace couchey fr couchey-fr coucheyfr coumadin counter sex counter-sex countersex couple gratuit couple watches couple-gratuit couple-watches couplegratuit couples gratuit couples watches couples-gratuit couples-watches couplesgratuit coupleswatches couplewatches coupon sense coupon_ coupon-code coupon-pag coupon-sense coupon.bl couponcode coupons_ coupons-pag couponsense courses casino courses-casino couture avec couture-avec coverages cozy ugg cozy-ugg cozyugg cpa camp cpa click cpa ppv cpa traff cpa-camp cpa-click cpa-ppv cpa-traff cpacamp cpaclick cpappv cpatraff cr.e.a.s.e cr.e.a.se cr.e.as.e cr.e.ase cr.ea.se cr.eas.e cracked-pro crackedpro crane-hire cranehire crave of crave-of cre.a.s.e cre.a.se cre.as.e crea.s.e create-own create-product create-tee createown createproduct createtee credit direct credit repair credit report credit score credit_ credit-card credit-direct credit-repair credit-report credit-score credit.cc credit.eu creditcard.org creditcard.us creditdirect creditrepair creditreport creditscore cription.asp cristmas- crossof crow pose crow-pose cruise vaca cruise-vaca cruisevaca crush-candy crushcandy crystalmall csgo ak47 csgo free csgo skin csgo-ak47 csgo-free csgo-skin csgofree css.asp cszwyojl cuir mean cuir vanessa cuir-mean cuir-vanessa cuirmean cuirvanessa culo mega culo-mega culomega cultureparis cup jers cup-jers cure herpes cure-herpes cure.co. curehelp cureherpes cures.co. currency trad currency-trad currencytrad curry jersey curry-jersey curryjersey custom jersey custom nike custom rim custom wheel custom-control custom-jersey custom-nike custom-rim custom-wheel custom+ customcontrol customdesignedshirt customdesignedtshirt customnike customrim customwheel cut menthol cut-menthol cutting-machine cuttingmachine cuurent cyber monday cyber-monday cybermonday cygara cymbalta cyprus payment cyprus-payment cypruspayment cytotec cz/love czesci skody czesci-skody czesciskody cг cе cҟ cу cһ cօ d http ð¿ d.@ d.the d» ď» ð» ð½ ð¾ da man! dabloggs daddy site daddy-site dafeult dailly daily.bl dailyreview.co dailystrength dailyz dakotasuki dalle scarpe dalle-scarpe dallescarpe damen kaufen damen moncler damen schuh damen timber damen tote damen-kaufen damen-moncler damen-schu damen-schuh damen-timber damen-tote damen-von-schuh damenkaufen damenmoncler damenschu damenschuh damentimber damentote damenvonschuh dames kopen dames-kopen dameskopen damier azur damier-azur damierazur damskie dangerous post dangerous-post dangerouspost dannerjp dapoxetin dapoxetine darmowe ogloszenia darmowe ogłoszenia darmowe prog darmowe-ogloszenia darmowe-ogłoszenia darmowe-prog darmoweogloszenia darmoweogłoszenia darmoweprog darmowy darrellbox darrellwire darvocet data nonetheless data-nonetheless data-tools database.co datarecovery.co datarecoveryhospital dataz date class date-class dating casual dating class dating date dating direct dating guide dating site dating-advice dating-casual dating-class dating-date dating-direct dating-guide dating-site dating.advice datingcasual datingdirect datingguide datingsite daunen weste daunen-weste daunenweste day loan day loubou day-diet day-loubou day.did dayloubou daytona acier daytona-acier daytonaacier daytrad dɑ ddavp de site de-contacto de-luxe de-site deal.bl dealonline dealsuk dealsus dealuk dealus dear-lover dearlover debt_ debt-help debt-management debt-relief debt-solution debthelp debthit debtrelief decent blog decent page decent post decent site decent web decent-blog decent-page decent-post decent-site decent-web dedans longchamp dedans-longchamp dedanslongchamp dedrease default_ default/member default1 default2 defiantly brilliant defiantly-brilliant defiantlybrilliant deficitfight definate definitelly dehttp dekorativno prikladno dekorativno-prikladno delete_ deliver result deliver-result deliverresult delivers result delivers-result deliversresult delpha_ deltasone demo0 demo1 demo2 demo3 demo4 demo5 demo6 demo7 demo8 demo9 demoniakk démoniakk demontag denschlaf dental guru dental quote dental super dental that dental-guru dental-implant dental-quote dental-super dental-that dental-veneer dentalgu.ru dentalguru dentalimplant dentalquote dentalsuper dentalveneer depositbank depression-symptom depressionsymptom derm exclus derm-exclus dermexclus derniers modele derniers modèle derniers-modele derniers-modèle derniersmodele desconto design cheap design own design-cheap design-own designcheap designer-bag designer-brand designer-jewel designer-label designer-shoe designerbag designerbrand designerjewel designerlabel designershoe designown desire erotic desire-erotic desires erotic desires-erotic desmomelt desmopressin despaulsmith destenex destinex destiny power destiny-power destinypower desyrel detail though detail-though detail/www details though details-though detektiv detskie diskotek detskie-diskotek deutschland-online deutschlandonline devenir trade devenir-trade devil1. devil2. devilspite dewelop df! dg-schoene dg-shoe dhttp di droga di-droga dia-tips diablo3 diamanti cartier diamanti-cartier diamanticartier diamox diary_ diazepam diclofenac didrex online didrex-online dienst.in diesel denim diesel hot diesel jap diesel jean diesel jp diesel online diesel uk diesel watch diesel-denim diesel-hot diesel-jap diesel-jean diesel-jp diesel-online diesel-uk diesel-watch dieseldenim dieselhot dieseljap dieseljean dieseljp dieselonline dieseluk dieselwatch diet pill diet plan diet review diet solution diet suppl diet_ diet-food diet-pill diet-plan diet-review diet-solution diet-suppl diet.asp diet.cfm diet.co diet.htm diet.jsp diet.php dietpill dietplan dietreview dietsolution dietsuppl differin difficulties thus difficulties-thus difficulty thus difficulty-thus diflucan diggs.us digi person digi-person diiclfuf dili optim dili-optim dilioptim dilufcif dincob dinkypage.com dinner soup dinner-soup dior_ diorkan direct-fund direct-health direct-lend direct-lone directhealth directlend directlone directory submit directory-submit directorysubmit directt dirt-bike disclaim.asp discount afl discount bag discount jord discount mbt discount mulberry discount nba discount nfl discount nhl discount north discount reebok discount ugg discount_ discount- discount-afl discount-bag discount-cig discount-jord discount-mbt discount-north discount-reebok discount-ugg discount-wheel discount.co discount.co. discount.org discountafl discountbag discountcig discounted north discounted- discounted-north discounted-wheel discountednorth discountedwheel discountmbt discountnorth discountreebok discountt discountugg discountwheel discussion made discussion-made display_ disulfiram diva-com divulgaemail djstool djtool dlphone dnjurh ðº doable prog doable-prog doableprog dobrucki.co dobrucki.pl doc/soap docid= docter dre docter marten docter-dre docter-marten docterdre doctermarten docteur dre docteur marten docteur-dre docteur-marten docteurdre docteurmarten doctor verif doctor-verif document_ document/bv document/celine document/chanel document/new document/nike document/north document/rolex documents_ documents/bv documents/celine documents/chanel documents/new documents/nike documents/north documents/rolex docxdrive does green does-green doesgreen doesn t doesnt dokumenta dokumento dokumentó dolce bag dolce-bag dolcebag dollar service dollar-service dollarservice dolphins jers dolphins-jers dolphinsjers domain-123 domain123 domainbie domen kupi domen_ domen-kupi domeny dominate secret dominate seo dominate-secret dominate-seo dominatesecret dominateseo domination secret domination seo domination-secret domination-seo dominationsecret dominationseo don think don-think dondash donna donn donna-donn donnadonn donne donn donne-donn donnedonn dont cease dont know dont-cease dont-fit-me door-blog dopamine dora-scrum dorascrum dos-seios dostenex dostinex dosug intim dosug-intim dotcomsecret douching teen douching-teen douchingteen doudoune down-jack downjack download apk download brace download_ download-apk download-brace downloadbrace downloadism downloads_ doxycycline dragon avail dragon-avail dragons avail dragons-avail drayvera dre beat dre cheap dre head dre phone dre-beat dre-cheap dre-head dre-phone dre< drebeat drecheap drehead drephone dress herve dress link dress online dress shoe dress shop dress-herve dress-link dress-online dress-shoe dress-shop dress-uk dress.rent dresses bcbg dresses herve dresses-bcbg dresses-herve dresses-uk dresses.wed dressesbcbg dressesherve dressherve dresshop dresslink dressonline dresssale dressshop drink iconic drink-iconic drivewayservice drmarten jap drmarten jp drmarten uk drmarten-jap drmarten-jp drmarten-uk drmartenjap drmartenjp drmartens jap drmartens jp drmartens uk drmartens-jap drmartens-jp drmartens-uk drmartensjap drmartensjp drmartensuk drmartenuk drug buy drug cheap drug-buy drug-cheap drugbuy drugcheap druggz drugs buy drugs cheap drugs-buy drugs-cheap drugsbuy drugscheap drugz drupal-temp drupal-theme dsquared giub dsquared jean dsquared online dsquared out dsquared shoe dsquared uomo dsquared_ dsquared-giub dsquared-jean dsquared-online dsquared-out dsquared-shoe dsquared-uomo dsquared2 dsquaredgiub dsquaredjean dsquaredonline dsquaredout dsquaredshoe dsquareduomo dtech affiliate dtech-affiliate dtechaffiliate dubai-princess dubturbo ducati tumi ducati-tumi ducatitumi duchess satin duchess-satin duchesssatin dude.de dudes.de dummytest dump seller dump-seller dumps forum dumps online dumps seller dumps shop dumps track dumps with dumps-forum dumps-online dumps-seller dumps-shop dumps-track dumps-with dunhill fine dunhill menthol dunhill_ dunhill-fine dunhill-menthol dunhill/dunhill dunjakke durant shoe durant-shoe duvetica aristeo duvetica out duvetica_ duvetica-aristeo duvetica-out duveticaaristeo duveticaout dvd asphyx dvd erotic dvd-asphyx dvd-erotic dvdrip dwi-attorney dwiattorney dylongfa dynamic genital dynamic-genital dyubetika ðµ dг dе dу e http e-biz e-cig e-evance e-newsletter service e-newsletter-service e.@ e2by.in eâcute eairfix early-signs-of earthly lover earthly-lover ease.in easy.in easyloan easyrent eating hemp eating-hemp eatinghemp eaudiovideo ebaypic ebony porn ebony-porn ebonyporn ebook-reader-test ebook-test ebookreadertest ebooktest ecco out ecco-out eccoout ecent yeas ed-in-men edge rumor edge rumour edge-rumor edge-rumour edhardyt eema1l eemai1 eemail eevance effexor egemtr egoodshop egypt jersey egypt-jersey ehttp einkommen ejaculate help ejaculate pharm ejaculate pill ejaculate-help ejaculate-pharm ejaculate-pill ejaculatehelp ejaculatepharm ejaculatepill ejaculation help ejaculation pharm ejaculation pill ejaculation-help ejaculation-pharm ejaculation-pill ejaculationhelp ejaculationpharm ejaculationpill el-gordo.c elamale elavil elementary entry elementary-entry elite jers elite men elite model elite sample elite wom elite-jers elite-men elite-model elite-sample elite-wom elitejers elitemen elitemodel elitesample elitewom eloans elsa-dress elway-jers elwayjers email vip email-vip email.asp email.cfm email.htm email.jsp email.php email.tst emails sometime emails vip emails-sometime emails-vip emailsvip emailvip emarketing emilio-pucci emiliopucci empire hack empire-hack empirehack employee engage employee-engage employeeengage empower network empower-network empowernetwork empreendedor en private en-ligne-paris en-private en/formula en/oakley enceinte rapid enceinte-rapid enceinterapid enchere max enchère max enchere-max enchère-max encheremax enchèremax encherisseur enchérisseur endlaved energetic post energetic-post energeticpost eneugh enewsletter service enewsletter-service eng private eng-faq eng-index eng-private eng/dress eng/faq eng/index eng/private engine optim engine-optim engineoptim engprivate enhance pill enhance-pill enhance-your enhancement pill enhancement-pill enhancementpill enhancepill enhanceyour enhttp enjoy-more enjoymore enleverlescernes enlightening post enlightening-post enligneparis enormous article enormous blog enormous page enormous post enormous weblog enormous-article enormous-blog enormous-page enormous-post enormous-weblog enourmous enprivate entry_ entrykey entryview envisionforce eomarketplace ephedra ephedrine epica watch epica-watch epilare laser epilare-laser epo doping epo-doping epodoping eqbandz equipement manufact equipement-manufact equipment manufact equipment-manufact era afl era mlb era nba era nfl era nhl era uk era-afl era-mlb era-nba era-nfl era-nhl era-uk erectile erettile erinvestor erjersey.co erjersey.net erogenous picture erogenous-picture erolove erotic asphyx erotic desire erotic erotic erotic love erotic photo erotic pro erotic series erotic text erotic thrill erotic toy erotic workout erotic_ erotic-asphyx erotic-desire erotic-erotic erotic-love erotic-photo erotic-pro erotic-series erotic-text erotic-thrill erotic-toy erotic-workout eroticerotic eroticke erotické eroticlove eroticpro eroticseries eroticthrill erotictoy eroticworkout erotik erotyczna es.iodress es/content escitalopram escort zone escort_ escort-zone escorts zone escorts_ escorts-zone escorts. escortss escortszone escortzone eshop.co espana online españa online espana-online españa-online espanaonline españaonline essay-empire essay-help essay-intro essay-service essay-write essayempire essayer cette essayer-cette essayhelp essayintro essays empire essays help essays intro essays service essays write essays-empire essays-help essays-intro essays-service essays-write essaysempire essayservice essayshelp essaysintro essaysservice essayswrite essaywrite established blog established-blog estadounidense estate pro estate whether estate_ estate-pro estate-whether estatepro estradiol etherdq euille lancel euille-lancel euro ditalog euro million euro-ditalog euro-million euro-vid euroditalog euromillion europe girls europe nfl europe-girls europe-nfl european nfl european-nfl europeannfl europegirls europenfl eurovid evackuator evelyne bag evelyne-bag evelynebag event_ eventid everthing at everthing-at every advert every info every-advert every-info every-leg every-light every-pant every-sock every-think every-tight every1bet everyadvert everyinfo everyleg everylight everyone furthermore everyone-furthermore everypant everysock everything-dental everythingdental everythink everytight evryday evvery exboyfriend exceel excelent excellent article excellent blog excellent page excellent post excellent site excellent task excellent topic excellent weblog excellent website excellent written excellent-article excellent-blog excellent-page excellent-post excellent-site excellent-task excellent-topic excellent-weblog excellent-website excellent-written excellentarticle excellentblog excellentlunch excellentpage excellentpost excellentsite excellenttopic excellentweblog excellentwebsite exceptional blog exceptional-blog exchange link exchange paysafecard exchange-link exchange-paysafecard exchangelink exchanging link exchanging-link exchanginglink exclusive rendez exclusive-rendez exclusive.in exclusive.pl exclusive.ru exclusive.su exclusive.za executive coach executive search executive-coach executive-search executivecoach exelon exercice exgirlfriend exit.asp expensive haver expensive vaca expensive-haver expensive-vaca expensivehaver expensivevaca experience simply experience-simply expert suggestion expert-suggestion expert-writing expertwriting exploded extensive exploded-extensive explosive growth explosive-growth express index express-index expressindex extended essay extended-essay extensive article extensive blog extensive internet extensive page extensive site extensive web extensive-article extensive-blog extensive-internet extensive-page extensive-site extensive-web extra gry extract pill extract-pill extractpill extreme-sport extremely} extremley eye porn eye-porn eyeglass sale eyeglass-sale eyeglasses sale eyeglasses-sale eyeglasssale eyeporn eyes porn eyes-porn eyesporn eyewear present eyewear-present eρ eϲ eг eԁ eе eѕ eҟ eу eх eһ eь f http f.@ f.j.o.g.a f.j.o.ga f.j.og.a f.j.oga f.jo.ga f.jog.a f.joga faar more faar-more fabulous drug fabulous-drug fabulousdrug fac visit fac-visit face denali face jack face out face sale face store face terra face vest face women face-denali face-jack face-out face-sale face-store face-terra face-vest face-women facebok facebook ad facebook cash facebook fan facebook lawful facebook traff facebook_ facebook-ad facebook-cash facebook-fan facebook-lawful facebook-traff facebookad facebookcash facebookfan facebooklike facebooku facejack faceout facesale facestore faceterra facevest facial-hair facialhair fact awesome fact-awesome factory coach factory out factory-coach factory-out factorycoach factoryout facts-about factthat fagance fail drug fail-drug faildrug fajki fajna strona fajna-fotka fajna-strona fajnafotka fajnastrona fajne fake coach fake converse fake mirror fake oakley fake passport fake ray fake sunglass fake ugg fake watch fake_ fake-coach fake-converse fake-mirror fake-oakley fake-passport fake-ray fake-sunglass fake-ugg fake= fakecoach fakeconverse fakemirror fakeoakley fakeok fakepassport fakeray fakesunglass fakeugg fakewatch false passport false-doc false-passport falsedoc falsepassport fanatic shop fanatic-shop fanatics shop fanatics-shop fanaticshop fanaticsshop fanciful belly fanciful-belly fancyto fanshome fantastic blog fantastic entire fantastic job! fantastic layout fantastic page fantastic paragraph fantastic post fantastic read! fantastic site fantastic topic fantastic weblog fantastic website fantastic-blog fantastic-entire fantastic-job fantastic-layout fantastic-page fantastic-paragraph fantastic-post fantastic-read fantastic-site fantastic-topic fantastic-weblog fantastic-website fantasticblog fantasticentire fantasticjob fantasticlayout fantasticpage fantasticpost fantasticread fantasticsite fantastictopic fantasticweblog fantasticwebsite farmacia_ farmacias farming-secret farmingsecret fashion apparel fashion boot fashion brace fashion compan fashion hair fashion list fashion men fashion store fashion trend fashion women fashion_ fashion-apparel fashion-boot fashion-brace fashion-compan fashion-hair fashion-list fashion-men fashion-store fashion-trend fashion-women fashionable blog fashionable comment fashionable page fashionable post fashionable site fashionable web fashionable-blog fashionable-comment fashionable-page fashionable-post fashionable-site fashionable-web fashionapparel fashionboot fashionbrace fashioncompan fashionhair fashionist blog fashionist-blog fashionista blog fashionista-blog fashionistablog fashionistblog fashionlist fashionmen fashionstore fashiontrend fashionwomen fast cash fast loan fast money fast quick fast-cash fast-loan fast-money fast-quick fast|quick fastcash fastidious blog fastidious data fastidious dialog fastidious my fastidious page fastidious post fastidious repl fastidious site fastidious thou fastidious urg fastidious weblog fastidious website fastidious writ fastidious-blog fastidious-data fastidious-dialog fastidious-my fastidious-post fastidious-repl fastidious-site fastidious-thou fastidious-urg fastidious-weblog fastidious-website fastidious-writ fastidious, my fastidious= fastidiousblog fastidiousdialog fastidiouspage fastidiouspost fastidioussite fastidiousweblog fastidiouswebsite fastloan fastmoney fastquick fat loss fat quite fat-burn fat-loss fat-men fat-quite fat-women fatburn fatloss fausse montre fausse_ fausse-montre faussemontre faux bulgari faux bvlgari faux chanel faux coach faux femme faux hermes faux homme faux montre faux_ faux-bulgari faux-bvlgari faux-chanel faux-coach faux-femme faux-hermes faux-homme faux-montre fauxbulgari fauxbvlgari fauxchanel fauxcoach fauxfemme fauxhermes fauxhomme fauxmontre fav it fav the fav to fav-it fav-the fav-to favorable article favorable blog favorable page favorable web favorable-article favorable-blog favorable-page favorable-single favorable-web favorite justif favorite website favorite-justif favorite-website favourable article favourable blog favourable page favourable single favourable web favourable-article favourable-blog favourable-page favourable-single favourable-web favourite justif favourite website favourite-justif favourite-website fb ads fb cash fb fans fb gold fb like fb money fb sales fb traff fb-ads fb-cash fb-fans fb-gold fb-like fb-money fb-sales fb-traff fbads fbcash fbfans fbgold fblike fbmoney fbsales fbtraff fckt fcukfcuk feacute-deacute feacutedeacute feature christ's feature christ’s feature christs feature-christs feature.asp feature.cfm feature.htm feature.jsp feature.php features christ's features christ’s features christs features-christs features.asp features.cfm features.htm features.jsp features.php featuring christ's featuring christ’s featuring christs featuring-christs federl feelng feichang0 feihuang0 felpe hollis felpe moncler felpe-hollis felpe-moncler felpemoncler female hand female-hand femalehand females hand females-hand femaleshand femme chaus femme couche femme rolex femme-chaus femme-couche femme-sac femmecanadagoose femmechaus femmecouche femmes imitat femmes-imitat femmesac femmescanadagoose femmesimitat fendi belt fendi donna fendi_ fendi-belt fendi-donna fendi1 fendi2 fendibelt fendidonna fenoma fenteetum ferra-gamo ferragamo outlet ferragamo sale ferragamo tie ferragamo_ ferragamo-outlet ferragamo-sale ferragamo-tie ferragamolove ferragamooutlet ferragamosale ferragamoshop ferragamotie fetish xxx fetish-xxx fetishxxx ff15 shop ff15 store ff15-shop ff15-store ff15shop ff15store ff16 shop ff16 store ff16-shop ff16-store ff16shop ff16store ff17 shop ff17 store ff17-shop ff17-store ff17shop ff17store fg xpress fg-xpress fgxpress fhttp fibromyalgia fifa coin fifa ultimate fifa_ fifa-15 fifa-16 fifa-17 fifa-coin fifa-ultimate fifa-ut fifa15 fifa16 fifa17 fifacoin fifaultimate fifaut fifaworldhack figc fightmark filenamedat filenamesdat files/new filestube film erotic film porn film x film-erotic film-porn film-x filmerotic filmizle filmporn films porn films x films-porn films-x filmsporn filpan.in filpan.pl filpan.ro filpan.ru filpan.za filvce.in filvce.pl filvce.ro filvce.ru filvce.za finance blog finance cash finance debt finance emerg finance service finance solution finance- finance-blog finance-cash finance-debt finance-emerg finance-service finance-solution financeblog financecash financedebt financeemerg financeservice financesolution financial blog financial cash financial debt financial emerg financial service financial solution financial_ financial-blog financial-cash financial-debt financial-emerg financial-service financial-solution financialblog financialcash financialdebt financialemerg financialservice financialsolution finanse finansow finasteride find sex find-sex find-the-answer findarticle findmewom findsex fine blog fine wive fine-blog fine-wive fineblog finest blog finest-blog finestblog finewive fingering my fingering-my finite instant finite-instant finiteinstant fioricet firefox-setting firefoxik firefoxsetting firm.in firm.pl firm.ru firm.su firm.za firma sprzata firma-sprzata firma.asp firma.cfm firma.htm firma.jsp firma.php firme sprzata firme-sprzata first loan first-class web first-class-web first-loan firstclass-web firstclassweb fish casino fish-casino fishcasino fit website fit-website fitch gilet fitch grenoble fitch out fitch-gilet fitch-grenoble fitch-out fitchgilet fitchgrenoble fitchout fitflop fiverr method fiverr-method fivestardoll fix credit fix-credit fixcredit fixing credit fixing-credit fixingcredit fj.o.g.a fj.o.ga fj.og.a fj.oga fjo.g.a fjo.ga fjog.a fjoga fjrm flagyl flappy-bird flappybird flash-slideshow flashslideshow flats.webeden fler artiklar fler-artiklar flex-global flexglobal flik.us flirt fever flirt-fever flirtfever flixya floating-board floatingboard floridaflee floxacin fluccun fluconazole fluoxetine fluticasone fobur.in foglio prada foglio-prada foglioprada follower love follower-love followers love followers-love foncier assurance foncier-assurance foncierassurance foodpyramid foolproof trick foolproof-trick foot disc foot insole foot manche foot nike foot-disc foot-insole foot-manche foot-nike football shirt football tee football_ football-shirt football-tee footballshirt footballtee footdisc footmanche footnike footwear disc footwear jord footwear mbt footwear-disc footwear-jord footwear-mbt footweardisc footwearjord footwearmbt footwears for cheap for download. for gucci for interact for-cheap for-gucci for-interact for-men for-money for-sale-direct for-the-most for-whole-sale for-wholesale for-windows-8 for-your-need for| forball.top forboot forcheap forex pip forex pro forex rev forex sign forex_ forex-pip forex-pro forex-rev forex-sign forexpip forexpro forexrev forexsign forfree forgucci forhandlere forinteract forjp.co formaldress formidable act formidable-act formoney formula replica formula-replica formulareplica forsale go forsale jap forsale jp forsale uk forsale-go forsale-jap forsale-jp forsale-uk forsaledirect forsalego forsalejap forsalejp forsales forsaleuk forte dsc forte generic forte info forte muscle forte parafon forte_ forte-dsc forte-generic forte-info forte-muscle forte-parafon fortedsc fortegeneric forteinfo fortemuscle forteparafon forthcoming post forthcoming-post forum coach forum-coach forum?func forum.asp forum.cfm forum.htm forum.jsp forum.php forum.really forum.thank forum/1/topic forum/ftopic forum/func forum/member forum/post forum/topic forum/user forumcoach forums?func forums.asp forums.cfm forums.htm forums.jsp forums.php forums.really forums.thank forums/1/topic forums/ftopic forums/func forums/member forums/post forums/topic forums/user forwholesale forwindows8 foryou.co forzest fr canad fr-canad fr.fr/ fr/acheter fr/botte fr/canad fr/cost fr/index fr/longchamp fr/pascher fradidas frame cheap frame-cheap framecheap frames cheap frames-cheap frames/index framescheap francemaillot frankly it frankly-it fraudcenter frauen puma frauen-puma frauenpuma frdern frebvic.in frebvic.pl frebvic.ro frebvic.ru frebvic.za free casino free cassino free chat free csgo free download free gay free international free laranita free online free pokecoin free poker free prescript free private free proxy free real free simple free visit free weblog free website free xxx free-bbs free-bid free-brows free-casino free-cassino free-chat free-csgo free-download free-gay free-hemp free-international free-ipad free-iphone free-ipod free-laranita free-m4a free-mods free-movie free-mp3 free-offer free-online free-pokecoin free-poker free-prescript free-private free-prog free-proxy free-real free-simple free-weblog free-website free-xxx free.in freearticle freebbs freebid freebrows freecasino freecassino freechat freecsgo freedat freedownload freefor.co freegay freehub freeinternational freeipad freeiphone freeipod freelance buyer freelance-buyer freelaranita freem4a freembtrans freemovie freemp3 freeoffer freeonline freepokecoin freepoker freeprivate freeprog freeproxy freereal freesale freesalg freesimcard freeslotmachine freevideo freeweblog freewebsite freexxx french escort french-escort frenchescort fresh article fresh blog fresh page fresh post fresh review fresh seo fresh weblog fresh-article fresh-blog fresh-page fresh-post fresh-review fresh-seo fresh-weblog freshreview freshseo freshwaterpearl friday 2013 friday 2014 friday 2015 friday michael friday mlb friday moncler friday mulberry friday nba friday nfl friday nhl friday sale friday ugg friday watch friday-2013 friday-2014 friday-2015 friday-michael friday-mlb friday-moncler friday-mulberry friday-nba friday-nfl friday-nhl friday-sale friday-ugg friday-watch friday2013 friday2014 friday2015 fridaymlb fridaymoncler fridaynba fridaynfl fridaynhl fridaysale fridayugg frlongchamp frmoncler from subsequent from-subsequent frontier hack frontier-hack frontline commando frontline-commando frozen cloth frozen dress frozen heart frozen-cloth frozen-dress frozen-heart frozencloth frozendress frozenheart fruitful design fruitful-design fruta planta frutaplanta fuck down fuck hard fuck pic fuck video fuck your fuck_ fuck- fuck-down fuck-hard fuck-pic fuck-video fuck-your fuckdown fucked_ fucker_ fuckhard fucking_ fuckpic fucks down fucks hard fucks your fucks_ fucks-down fucks-hard fucks-your fucksdown fuckshard fucksyour fuckvideo fuckyour ful malware ful-malware fulmalware fun-sneak fun-with-window func.asp func.cfm func.htm func.jsp func.php fund market fund trend fund-market fund-trend fund.in fund.pl fund.ru fund.su fund.za funding.in funding.pl funding.ru funding.su funding.za fundmarket funds.in funds.pl funds.ru funds.su funds.za funsneak funwithwindow fur boot fur-boot furboot furiousguy furla bag furla candy furla hand furla out furla sac furla-bag furla-candy furla-hand furla-out furla-sac furlabag furlacandy furlahand furlaout furlasac furnituresale furosemide further former further-former furworld.ru furworld.su futbol barcelona futbol-barcelona futbolbarcelona futuristic-market futuristicmarket fx profit fx-profit fxprofit fе g http g-star jean g-star-jean g-starjean g-string g.@ g.o.a.d.k g.o.a.dk g.o.ad.k g.o.adk g.oa.d.k g.oa.dk g.oadk g00gle g0ogle gaaab.co gabapentin gabbana store gabbana-cheap gabbana-shop gabbana-store gabbanacheap gabbanashop gabbanastore gafas ray gafas-ray gafasray gaga jap gaga jp gaga milan gaga uk gaga-jap gaga-jp gaga-milan gaga-uk gagajap gagajp gagamilan gagauk gagner gain weight gain-weight gainers beat gainers outnumbered gainers-beat gainers-outnumbered gainweight galaxy nail galaxy-nail galaxynail galdi rebecka galdi-rebecka galerie world galerie-world galerieworld gallergrind galleries porn galleries-porn galleriesporn gallery porn gallery world gallery-porn gallery-world galleryporn galleryworld gamble online gamble-online gambleonline gambling casino gambling game gambling online gambling-casino gambling-game gambling-online gamblingcasino gamblinggame gamblingonline game bong game casino game cassino game ionline game online game wiki game-bong game-casino game-cassino game-copy game-game game-ionline game-jers game-online game-wiki gamebong gamecasino gamecassino gamecopy gamedesigndegree gamegame gameionline gamejers gameonline games casino games cassino games-casino games-cassino gamescasino gamescassino gamewiki gamma blue gamma-blue gammablue gamme reacute gamme-reacute gamyba gang-bang gangbang gangprofil ganhar dinheiro ganhar-dinheiro gaogb gaoland gapscent garcinia gawab.com gay redtube gay sex gay-redtube gay-sex gayredtube gaysex gayusa gel virage gel-virage gelatine free gelatine-free gelvirage gemmes illimit gemmes limit gemmes-illimit gemmes-limit gemmeslimit gemorroya gen porn gen-porn genemy general/genera generate cash generate money generate-cash generate-money generatecash generatemoney generating cash generating money generating-cash generating-money generatingcash generatingmoney generation algorithm generation-algorithm generator 2013 generator 2014 generator 2015 generator-2013 generator-2014 generator-2015 generator2013 generator2014 generator2015 generatorpro generic_ genital herpes genital-herpes genuine-pandora genuinely fast genuinely fruit genuinely-fast genuinely-fruit genuinepandora german jers german lesb german-jers german-lesb german-love germanlesb germanlove germany jers germany lesb germany-jers germany-lesb germanylesb germanylove gestione get $ get bitcoin get face get_rid get-bitcoin get-boot get-face get-hermes get-massive get-money-for get-pokego get-rid-of get-the-answer get-translate get-widget getaloan getastyle getbitcoin getface gethermes getjoy getmassive getpokego getridof getting $ getting knowledge getting-knowledge getting-men gettingknowledge gettranslate getwidget getyou.asp gfband gget set gginza ghd gold ghd pascher ghd straight ghd uk ghd-gold ghd-pascher ghd-straight ghd-uk ghdgold ghdpascher ghdstraight ghduk ghttp giant 100% giant-100% giants-jers giants-shop giantsjers giantsshop gift click gift singapore gift-click gift-singapore giftclick gifts click gifts singapore gifts-click gifts-singapore giftsclick giftsingapore giftssingapore gigantix.co gilet moncler gilet-moncler giletmoncler ginzza girl blog girl eblog girl escort girl jap girl jord girl jp girl spouse girl-blog girl-day-dress girl-eblog girl-escort girl-jap girl-jord girl-jp girl-spouse girlblog girldaydress girleblog girlescort girlie spouse girlie-spouse girliespouse girljord girljp girls blog girls eblog girls escort girls-blog girls-day-dress girls-eblog girls-escort girlsblog girlsdaydress girlseblog girlsescort girlspouse giubbino moncler giubbino-moncler giubbinomoncler giubbotti dsqu giubbotti invernali giubbotti moncler giubbotti official giubbotti online giubbotti out giubbotti prezzi giubbotti timber giubbotti uomo giubbotti woolrich giubbotti_ giubbotti-dsqu giubbotti-invernali giubbotti-moncler giubbotti-official giubbotti-online giubbotti-out giubbotti-prezzi giubbotti-timber giubbotti-uomo giubbotti-woolrich giubbottidsqu giubbottiinvernali giubbottimoncler giubbottiofficial giubbottionline giubbottiout giubbottiprezzi giubbottitimber giubbottiuomo giubbottiwoolrich giubbotto dsqu giubbotto invernali giubbotto moncler giubbotto official giubbotto online giubbotto out giubbotto prezzi giubbotto timber giubbotto uomo giubbotto woolrich giubbotto_ giubbotto-dsqu giubbotto-invernali giubbotto-moncler giubbotto-official giubbotto-online giubbotto-out giubbotto-prezzi giubbotto-timber giubbotto-uomo giubbotto-woolrich giubbottodsqu giubbottoinvernali giubbottomoncler giubbottoofficial giubbottoonline giubbottoout giubbottoprezzi giubbottotimber giubbottouomo giubbottowoolrich giuseppezanotti gkhk glad reading glad-reading gladreading glamorousbag glasses-tokyo glassess glassestokyo gleevec glitter ugg glitter-ugg glitterugg global-agenda globalagenda globalist agenda globalist-agenda globalistagenda globalnpn glubokoye glot glubokoye-glot glucophage glutamina glyburide gma1l gmai1 gmaiil gmaills gmailmirror gneuienly gnlaser go viral, go viral! go viral? go watchs go-watchs go.a.d.k go.a.dk go.ad.k go.adk go0gle goa.d.k goa.dk goad.k goadk goed jassen goed-jassen goedjassen goedkoop gogle for gogle-for going please going-please goingplease gointeractive gojp.co gold earring gold ira gold pill gold unobtain gold-account gold-and-silver gold-coin gold-earring gold-essay gold-ingot gold-ira gold-jewel gold-pill gold-price gold-seiko gold-unobtain gold.in gold.pl gold.ru gold.su gold.za goldbarren goldcoin goldearring golden_ goldendoll goldessay goldgeek goldingot goldira goldjewel goldpill goldplated goldseiko goldsuppl goldtruth golf access golf out golf plaza golf-access golf-out golf-plaza golf-promo golf-shop golfaccess golfout golfplaza golfpromo golfs gomaile.co good article good blog good click good free good hemp good page good post good site: good template good website good-article good-blog good-click good-for- good-free good-game good-hemp good-post good-site good-template good-website good} goodbye.asp goodfree goodgame goodnessknow goodsblog goodtemplate goog luck goog-luck google bind google for- google high google java google mapa google rank google us google_ google-bind google-high google-java google-mapa google-rank google-us googlebind googlehigh googleing googlejava googlerank googleus googoozuza gooogle goose calgary goose canada goose chill goose coat goose enfant goose expedit goose femme goose grise goose herre goose homme goose ital goose jack goose jacka goose jakke goose jas goose norge goose online goose out goose paris goose parka goose pas goose retail goose sale goose site goose toronto goose v goose york goose youth goose-calgary goose-canad goose-canada goose-coat goose-enfant goose-expedit goose-femme goose-grise goose-herre goose-homme goose-ital goose-jack goose-jacka goose-jakke goose-jas goose-norge goose-online goose-out goose-paris goose-parka goose-pas goose-retail goose-sale goose-site goose-toronto goose-v goose-vest goose-york goose-youth goose.asp goose.cfm goose.htm goose.jsp goose.php goose< gooseca goosecalgary goosecanad goosecanada goosecheap goosecoat goosedk gooseenfant gooseexpedit goosefemme goosegrise gooseherre goosehomme gooseital goosejack goosejacka goosejakke goosejas goosenorge gooseonline gooseout gooseparis gooseparka goosepas gooseretail goosesale goosescheap gooseshop goosesite goosetoronto goosevest gooseyork gooseyouth gorgeous escort gorgeous-escort gorgeousescort gosgov goshop. got idea got-idea gotowkowa gotowkowe gowatchs gown love gown-love gownlove gowns love gowns-love gownslove goyard bag goyard online goyard-bag goyard-online goyardbag goyardonline graduand granny porn granny-porn grannyporn grateest gratis sex gratis_ gratis-sex gratissex gratuit annonce gratuit roulette gratuit-annonce gratuit-roulette gratuitannonce gratuite annonce gratuite roulette gratuite-annonce gratuite-roulette gratuiteannonce gratuiteos gratuiteroulette gratuitos gratuitroulette gray-panther graypanther grayson bag grayson-bag graysonbag great acknow great article great bet great blog great doc great essay great good great keep great layout great post great publish great thing great weblog great website great writ great-acknow great-article great-bet great-blog great-doc great-essay great-good great-keep great-layout great-post great-publish great-thing great-weblog great-website great-writ great} greatacknow greatarticle greatbet greatblog greatdoc greatessay greatest doc greatest-doc greatestdoc greatgood greatkeep greatlayout greatpost greatpublish greatthing greatweblog greatwebsite greatwrit greatwrite greatwritten greece-holid greeceholid green smoker green-smoker greensmoker grise parka grise-parka griseofulvin griseparka grooming need grooming-need groomingneed grosen ray grösen ray grosen-ray grösen-ray grossen ray großen ray grössen ray größen ray grossen-ray großen-ray grössen-ray größen-ray grossrx group xxx group_ group-home group-review group-xxx groupxxx grow marijuana grow-cannabis grow-marijuana grow-your growing hemp growing-hemp growmens growth hormone growth-hormone growthhormone grsentas gruppmeddelanden gruppo gucci gruppo-gucci gruppogucci gruzoperevozki gruzowe gruzu gry flesh gry online gstar jean gstar-jean gstarjean gta online gta-online gtaonline guantes marshall guantes-marshall guantesmarshall guarantee_ guarantee-uptime guaranteed_ guaranteed-uptime gubdaily gucchi gucci bag gucci bors gucci brief gucci disc gucci envy gucci factor gucci gucci gucci guilt gucci hand gucci italia gucci milan gucci online gucci out gucci pour gucci sale gucci scen gucci seller gucci time gucci uomo gucci vintage gucci_ gucci-- gucci-bag gucci-bors gucci-brief gucci-disc gucci-envy gucci-factor gucci-glass gucci-gucci gucci-guilt gucci-hand gucci-italia gucci-milan gucci-online gucci-out gucci-pour gucci-purse gucci-replica gucci-sale gucci-scen gucci-seller gucci-time gucci-uk gucci-uomo gucci-vintage gucci-you gucci2 guccibag guccibors gucciden guccidisc guccienvy guccifactor guccifr gucciglass guccigucci gucciguilt guccihand gucciinstock gucciitalia gucciiuk guccij guccikan guccimilan guccinose guccionline gucciout gucciparis guccipour guccipurse guccireplica guccisale gucciseller guccisingapore gucciten guccitime gucciuk gucciuomo guccivintage gucciyu guerre gratuit guerre-gratuit guerregratuit guest goo guest test guest_ guest-book guest-goo guest-post guest-test guestbook guestgoo guestpost guesttest guiltfree gunstig kaufen günstig kaufen gunstig-kaufen günstig-kaufen gunstigkaufen günstigkaufen guru1 gurus1 gyslera h http h.@ habitof hack cydia hack face hack fb hack online hack tool hack_ hack-cydia hack-face hack-fb hack-online hack-pass hack-tool hack-zip hack< hackasphalt hackcydia hacker_ hackface hackfb hacking_ hackonline hacks_ hacktool hadheard hair-again hair-grow hair-remov hair-straight hairagain hairgrow hairmodel hairremov hairstraight hamilton norge hamilton-norge hand taschen hand-taschen handbag distrib handbag louis handbag out handbag sale handbag store handbag uk handbag wholes handbag wom handbag-distrib handbag-for handbag-louis handbag-out handbag-sale handbag-store handbag-wholes handbag-wom handbag+ handbagdistrib handbagfor handbaglouis handbagout handbags distrib handbags louis handbags out handbags store handbags uk handbags wholes handbags wom handbags-distrib handbags-for handbags-louis handbags-out handbags-store handbags-wholes handbags-wom handbags+ handbagsale handbagsdistrib handbagsfor handbagslouis handbagsout handbagssale handbagsstore handbagstore handbagsu handbagswholes handbagswom handbagu handbagwholes handbagwom handsome gold handsome-gold handtaschen hanging-with-friend hangingwithfriend haohao haraka black haraka-black hard_ hardness quantity hardness-quantity hardtool hardware_ hardy jean hardy-huppari hardy-jean hardyhuppari hardyjean harnessedthem hartmann repeated hartmann-repeated has cuisine has-cuisine hats carolina hats chicago hats denver hats indiana hats oakland hats-carolina hats-chicago hats-cincin hats-denver hats-indiana hats-new hats-oakland hatschicago hatsdenver hatsindiana hatsnap hatsoakland hatssnap hautschez have understand have-understand haveasite havegive havegone havve a hawks-jers hawksjers hɑ hcg boost hcg-boost hcgboost hd fuck hd muscle hd porn hd sex hd-fuck hd-muscle hd-porn hd-sex hd.hd hdfuck hdmuscle hdporn hdsex headset 2013 headset 2014 headset 2015 headset-2013 headset-2014 headset-2015 headset2013 headset2014 headset2015 healingindu health advis health how health stock health suppl health-advis health-how health-recipe health-stock health-suppl healthadvis healthcare advis healthcare how healthcare stock healthcare suppl healthcare-advis healthcare-how healthcare-stock healthcare-suppl healthcareadvis healthcarehow healthcarestock healthcaresuppl healthhow healthrecipe healthrelated healthstock healthsuppl heart site heart-site hearted web hearted-web heartsite hedge fund hedge-fund heel_ heelped heil hitler heil-hitler heilhitler hellllo helllo hello admin hello dress hello sex hello that hello this hello-admin hello-dress hello-sex hello-that hello-this hello!my helloadmin hellodress hellosex hellothat hellothis help cry help essay help tax help_ help-cry help-essay help-tax help.asp help.cfm help.htm help.jsp help.php helpcry helpessay helpful blog helpful-blog helpful-info helpful-method helpfulblog helplink.asp helpoful helptax hemorroide hemp braid hemp jewel hemp milk hemp oil hemp protein hemp tycoon hemp_ hemp-braid hemp-jewel hemp-milk hemp-oil hemp-protein hemp-tycoon hence choose hentai heook hepcinat herbal smok herbal tincture herbal-medicin herbal-smok herbal-tincture herbalmedicin herbalsmok herbaltincture heren timber heren-timber herentimber herhrh hermes abrasif hermes austr hermes bag hermes bangle hermes belt hermes birkin hermes buckle hermes comp hermes couch hermes enamel hermes evelyne hermes factor hermes hand hermes official hermes out hermes pink hermes scarf hermes scarve hermes store hermes uk hermes wallet hermes xl hermes-abrasif hermes-bag hermes-bangle hermes-belt hermes-birkin hermes-buckle hermes-comp hermes-couch hermes-disc hermes-enamel hermes-evelyne hermes-factor hermes-official hermes-out hermes-pink hermes-replica hermes-store hermes-uk hermes-wallet hermes-xl hermesabrasif hermesbag hermesbangle hermesbelt hermesbuckle hermescomp hermescouch hermesdisc hermesenamel hermesevelyne hermesfactor hermeshut hermesofficial hermesout hermespink hermesreplica hermesstore hermesuk hermeswallet hermesxl hernia support hernia surgery hernia-support hernia-surgery heroius herpes infect herpes treatment herpes-infect herpes-treatment herpes< herren kaufen herren moncler herren timber herren-kaufen herren-moncler herren-timber herrenkaufen herrenmoncler herrentimber heuer new heuer sale heuer shop heuer-new heuer-sale heuer-shop heuernew heuersale heuershop heya i hfgfh hgfj hggh hgh hgh dopa hgh enhance hgh natural hgh purchase hgh-dopa hgh-enhance hgh-natural hgh-purchase hgh-sup hgher hghsup hhttp hid_ hierbasmedicin high website high-grade content high-grade-content high-heel high-profile high-protein high-website high.cc highblood highgrade content highgradecontent highprofile highprotein hingenieur his website his-website hisslipper hiswebsite hit mp3 hit-mp3 hitfit hithat hithis hitmp3 hjblog hleepd hndeds hngnep hnrxkc hobo bag hobo fuchsia hobo-bag hobo-fuchsia hobobag hobofuchsia hoga out hogan disc hogan online hogan oulet hogan out hogan scarpe hogan shoe hogan shop hogan sito hogan store hogan time hogan uomo hogan-disc hogan-online hogan-oulet hogan-out hogan-scarpe hogan-shoe hogan-shop hogan-sito hogan-store hogan-time hogan-uomo hogandisc hoganoulet hoganout hoganshoe hoganshop hogansito hoganstore hogantime hoganuomo hogaout holdinga holdning holistic health holistic-health hollister berlin hollister bolsos hollister bras hollister braz hollister cloth hollister gr hollister it hollister jack hollister jap hollister jean hollister job hollister jp hollister logo hollister nantes hollister online hollister orig hollister out hollister pant hollister paris hollister pas hollister polo hollister prix hollister roma hollister sale hollister sandal hollister shirt hollister shop hollister short hollister sverige hollister swim hollister tiendas hollister uk hollister_ hollister-berlin hollister-bolsos hollister-bras hollister-braz hollister-cloth hollister-deutsch hollister-gr hollister-it hollister-jack hollister-jap hollister-jean hollister-job hollister-jp hollister-logo hollister-milan hollister-nantes hollister-online hollister-orig hollister-out hollister-pant hollister-paris hollister-pas hollister-polo hollister-prix hollister-roma hollister-sale hollister-sandal hollister-shirt hollister-shop hollister-short hollister-sverige hollister-swim hollister-tiendas hollister-uk hollisterberlin hollisterbolsos hollisterbras hollisterbraz hollistercloth hollisterdeutsch hollistergr hollisterit hollisterjack hollisterjap hollisterjean hollisterjob hollisterjp hollisterlogo hollistermilan hollisternantes hollisteronline hollisterorig hollisterout hollisterpant hollisterparis hollisterpas hollisterpolo hollisterprix hollisterroma hollistersale hollistersandal hollistershirt hollistershop hollistersverige hollisterswim hollistertiendas hollisteruk home-based home-loan home-remed homebased homeforsale homeloan homeremed homes smart homes-smart homesforsale homessmart homme chaus homme couche homme giorgio homme hugo homme mariage homme rolex homme sold homme-chaus homme-couche homme-giorgio homme-hugo homme-mariage homme-sac homme-sold hommecanadagoose hommechaus hommecouche hommesac hommescanadagoose hondavtx hoodia hoodie-cheap hoodiecheap hoody-cheap hoodycheap hookup0 hookup1 hookup2 hookup3 hookup4 hookup5 hookup6 hookup7 hookup8 hookup9 hoolgain hoolz horoscopes horoskop horsesimul host seller host-file host-seller host.in host.pl host.ro host.ru host.su host.za hosting community hosting deutsch hosting-community hosting-deutsch hostingdeutsch hosts-file hostseller hoststo.ru hoststo.su hot concern hot tech hot-babe hot-girl hot-love hot-pantie hot-panty hot-sale hot-tag hot-tags hot-tech hot.asp hot.cfm hot.htm hot.jsp hot.php hotburberry hotel deal hotel hermes hotel-deal hotel-hermes hotelbritannia hoteldeal hoteles madrid hoteles-madrid hotelesmadrid hotelhermes hoteli hotelmanchester hotgirl hotlove hotpantie hotpanty hotsale hotsunglass hottag hottest tech hottest update hottest-tech hottest-update hourpayday houseforsale housesforsale hover-glide hover-shop hoverboard buy hoverboard king hoverboard scoot hoverboard shop hoverboard-360 hoverboard-buy hoverboard-for hoverboard-king hoverboard-safe hoverboard-scoot hoverboard-shop hoverboard-stop hoverboard360 hoverboardbuy hoverboardfor hoverboardking hoverboardsafe hoverboardscoot hoverboardshop hoverboardstop hoverglide hovershop how_do how-do-you how-to-buy how-to-clean how-to-creat how-to-get how-to-have how-to-lose how-to-make how-to-medi how-to-quick how-to-reduc how-to-restor how-to-sell how-to-speed how-to-take how-to-teach how-to-unlock how-to-win however before however-before howtobuy howtocreat howtocure howtomake howtomedi howtoreg howtospeed howtounlock howtowin hppp hqsteroid htaccrss html-article html-link html-new htmlarticle htmlht htmllink htmlnew http:: http// httphttp huay-today huaytoday huge 100% huge cock huge great huge-100% huge-cock huge-great hugecock hugescock human site human-site hungurian huntingtexas huntingtx huperzine hyclate hydraulik- hydrocodone hydrotherapy hydroxatone hydroxy hygienistst hyper-fb hyper-link hyperlink hypothyroidism hyzaar ɦa ɦe ɦi ɦo i aam i aint i http i needs i-aint i-needs i-only-done i-will-be � i.@ i.g.o.r i.g.or i.go.r i'v got i’v got i2g ia€?ll iamimport icon/set icons/set idanmark ideea ifyou ig.o.r ihttp illions4u illusion origami illusion-origami im grateful im happy im not im please im very im wonder im-grateful im-happy im-not im-please im-very im-wonder image-old image/? image/bv image/cache image/celine image/chanel image/game image/image image/index image/layout image/old image/prada image/rolex image/table image/ugg images-old images/? images/blogs images/bv images/cache images/celine images/chanel images/game images/image images/index images/layout images/new images/nike images/north images/old images/prada images/rolex images/smilies images/table images/ugg imalook imitation chanel imitation femme imitation hermes imitation homme imitation-chanel imitation-femme imitation-hermes imitation-homme imitationchanel imitationfemme imitationhermes imitationhomme imitaugg imitrex immediate income immediate-income immediateincome immediatey immobilier lux immobilier-lux immobilierlux implanty impregnacji impressive article impressive blog impressive page impressive post impressive share impressive weblog impressive-article impressive-blog impressive-page impressive-post impressive-share impressive-weblog imptortant imtmdiae in delicious in gogle in youtube in-delicious in-disguise in-gogle in-youtube in' tremendous in’ tremendous inbeing inbox.ru inbto include priceless includeeng includes priceless income_ income.in increase traff increase-traff increasetraff incredible article incredible blog incredible layout incredible page incredible point incredible site incredible topic incredible weblog incredible website incredible-article incredible-blog incredible-layout incredible-page incredible-point incredible-site incredible-topic incredible-weblog incredible-website incredibleblog incrediblelayout incrediblepage incrediblesite incredibletopic incredibleweblog incrediblewebsite indelicious inderal indeutsch index-css index-old indexcss indexold indicators.co inetry infected almost infected crash infected-almost infected-crash infinity-2 infinity2 info base info you info-alcohol info-base info-you info/addict info/alcohol info/product info/tag info/user info/view infolist infonetcom informacyjne informasjon.asp informasjon.cfm informasjon.htm informasjon.jsp informasjon.php informatica-libri informaticalibri informatik information.asp information.cfm information.htm information.jsp information.php informatique enligne informatique-enligne informative article informative blog informative post informative site informative weblog informative website informative-article informative-blog informative-post informative-site informative-weblog informative-website informativeblog informativepost informativesite informativeweblog informativewebsite infos- infos/ infusionsoft ing forwad ing puter ing-forwad ing-puter ing/bak/ ingputer ington boot ington-boot ingtonboot ingugg inheritance cash inheritance-cash inheritancecash inhttp initial traffic, initial traffic! initial traffic? initiator_ injection fact injection-fact injectionfact injuries insur injuries lawyer injuries-insur injuries-lawyer injuriesinsur injurieslawyer injury lawyer injury-lawyer injurylawyer inndex innerestitg inotfmarion inportant inrtomaf insane journal insane sex insane workout insane-journal insane-sex insane-workout insanejournal insanesex insaneworkout insanity journal insanity workout insanity-journal insanity-workout insanity.asp insanity.cfm insanity.htm insanity.jsp insanity.php insanityjournal insanityworkout insdier inside reputation inside-reputation insomnia journal insomnia tip insomnia-journal insomnia-tip insomniajournal insomniatip inspired hand inspired-hand inspiredhand insta-appraisal instafab instagram in instagram-in install virtual install-virtual installvirtual instant blog instant cash instant loan instant pay instant paysafecard instant traff instant web instant week instant_ instant-appraisal instant-blog instant-cash instant-loan instant-pay instant-paysafecard instant-traff instant-web instant-week instantappraisal instantblog instantcash instantloan instantpay instanttraff instantweb instantweek instappraisal instruct car instruct-car instructcar instructor car instructor-car instructorcar insurance auto insurance car insurance home insurance house insurance quote insurance-auto insurance-car insurance-compan insurance-home insurance-house insurance-quote insuranceauto insurancecar insurancecompan insurancehome insurancehouse insurancequote insurances intagra intdrnation intelligently about intelligently-about interact internet interact-internet interesting blog interesting post interesting-blog interesting-post interferende internet article internet blog internet gambl internet lifestyle internet link internet lookup internet owe internet pag internet page internet poker internet post internet savvy internet site internet view internet web internet-article internet-blog internet-gambl internet-lifestyle internet-link internet-lookup internet-market internet-owe internet-page internet-poker internet-post internet-savvy internet-site internet-web internet.in internetblog internetgambl internetlifestyle internetlink internetmarket internetowe internetpage internetpoker internetsavvy internetsite internetu akcij internetu kvepalai internetu-akcij internetu-kvepalai internetview internetweb intersting invest-money invest-off invest.in invest.net invest.pl invest.ro invest.ru invest.su invest.za invest/stock invest+ investing/stock investing+ investinwell investir investmoney investoff investor.in investor.pl investor.ro investor.ru investor.su investor.za investors.in investors.pl investors.ro investors.ru investors.su investors.za inzest ip.ideal ipad tablet ipad-1 ipad-2 ipad-3 ipad-crack ipad-download ipad-repair ipad-suppl ipad-tablet ipad1 ipad2 ipad3 ipadrepair ipadsuppl iphone apple iphone case iphone crack iphone repair iphone suppl iphone-apple iphone-case iphone-crack iphone-repair iphone-suppl iphone.asp iphone.cfm iphone.htm iphone.jsp iphone.php iphone/iphone iphone+ iphone2me iphone2you iphone4 case iphone4 spy iphone4-case iphone4-spy iphone4case iphone4me iphone4s case iphone4s spy iphone4s-case iphone4s-spy iphone4scase iphone4spy iphone4sspy iphone4you iphone5 case iphone5 spy iphone5-case iphone5-spy iphone5c case iphone5c spy iphone5c-case iphone5c-spy iphone5case iphone5ccase iphone5cspy iphone5s case iphone5s spy iphone5s-case iphone5s-spy iphone5scase iphone5spy iphone5sspy iphone6 case iphone6 spy iphone6-case iphone6-spy iphone6case iphone6spy iphonecase iphonecrack iphonerepair iphones apple iphones-apple iphonesuppl ipod-repair ipod-suppl ipodrepair ipodsuppl iprofit ipアドレス ira kit ira-kit irakit irc-chat irresistible website irresistible-website is seo is-now-available is-seo is.and isabel marant isabel-marant isabelmarant island jack island-jack islandjack isn''t isnt isotretinoine isseo istanbul escort istanbul-escort istanbulescort istnieje oficjalna istnieje-oficjalna it duvetica it oakley it ordeno it ordenó it rite it truly it-duvetica it-oakley it-ordeno it-ordenó it-rite it-truly it''s italia scarpe italia-scarpe italiascarpe italy-shop italy-store italyshop italystore item.asp item.cfm item.htm item.jsp item.php itemnotfound itoakley its fastidious its helped its-fastidious its-helped itsfastidious itstree iwant2 iwc brand iwc-brand iwcbrand iρ iг iԁ iѕ iҟ i贸 j http j.@ j.i.mlard j.o.s.h j'ai tjrs j'ai-tjrs j’ai tjrs j’ai-tjrs jacke-online jacke-west jacken-online jacken-west jackenonline jackenwest jackeonline jacket 2013 jacket 2014 jacket 2015 jacket canad jacket jap jacket jp jacket out jacket sale jacket sunglass jacket_ jacket-2013 jacket-2014 jacket-2015 jacket-canad jacket-jap jacket-jp jacket-out jacket-sale jacket-sunglass jacket2013 jacket2014 jacket2015 jacketcanad jacketout jackets 2013 jackets 2014 jackets 2015 jackets jap jackets jp jackets out jackets sale jackets_ jackets-2013 jackets-2014 jackets-2015 jackets-for-kids jackets-for-men jackets-for-wom jackets-jap jackets-jp jackets-out jackets-sale jackets2013 jackets2014 jackets2015 jacketsale jacketsforkids jacketsformen jacketsforwom jacketsout jacketssale jacketsunglass jacketswom jackewest jacobs cartera jacobs geldb jacobs jap jacobs jp jacobs purse jacobs uk jacobs-cartera jacobs-dk jacobs-geldb jacobs-jap jacobs-jp jacobs-purse jacobs-uk jacobscartera jacobsdk jacobsgeldb jacobsinmilan jacobsjap jacobsjp jacobspurse jacobsuk jagody acai jagody-acai jagowho jam jord jam-jord japan converse japan dr japan marc japan monster japan mont japan new japan online japan swarovski japan-1 japan-converse japan-dr japan-marc japan-monster japan-mont japan-new japan-online japan-swarovski japanconverse japandrmarten japanese converse japanese dr japanese marc japanese mont japanese swarovski japanese-converse japanese-dr japanese-marc japanese-mont japanese-swarovski japaneseconverse japanesedrmarten japanesemarcjacobs japanesemontblanc japaneseswarovski japanmarcjacobs japanmonster japanmontblanc japannew japanonline japanswarovski jassen dames jassen neder jassen out jassen-dames jassen-neder jassen-out jassendames jassenneder jassenout jazz-jersey jazzjersey jcshoe jeacoma.co jean kaufen jean taste jean-kaufen jean-taste jeankaufen jeans good jeans kaufen jeans taste jeans-kaufen jeans-taste jeanskaufen jeanstaste jeantaste jeremy scott jeremy-scott jeremyads jeremyscott jeremyscottwing jerking-my jersetblack jersey 1 jersey 2 jersey 3 jersey 4 jersey cheap jersey free jersey from jersey nike jersey paypal jersey soccer jersey wholes jersey-1 jersey-2 jersey-3 jersey-4 jersey-cheap jersey-for jersey-free jersey-from jersey-nike jersey-paypal jersey-pro jersey-soccer jersey-wholes jersey.asp jersey.cfm jersey.htm jersey.jsp jersey.php jersey.us jersey+ jersey1 jersey2 jersey3 jersey4 jerseycheap jerseyfree jerseyfrom jerseynike jerseyonline jerseypro jerseyred jerseys 1 jerseys 2 jerseys 3 jerseys 4 jerseys cheap jerseys free jerseys from jerseys nike jerseys paypal jerseys soccer jerseys wholes jerseys-1 jerseys-2 jerseys-3 jerseys-4 jerseys-cheap jerseys-for jerseys-for-you jerseys-free jerseys-from jerseys-nike jerseys-paypal jerseys-soccer jerseys-wholes jerseys.us jerseys+ jerseys1 jerseys2 jerseys3 jerseys4 jerseyscheap jerseysforyou jerseysfree jerseysfrom jerseysnike jerseysusa jerseyswholes jerseyusa jerseywhite jerseywholes jewelery jeweline. jewellery.bl jewelry collect jewelry expens jewelry watch jewelry-collect jewelry-expens jewelry-watch jewelry/watch jewelry< jewelrybest jewelrycollect jewelryexpens jewelrys jhjhjh jhttp ji.m.lard ji.ml.ard ji.mla.rd ji.mlar.d jibajabs jibjabs jibsajab jibsjab jim.l.ard jim.la.rd jim.lar.d jiml.a.rd jiml.ar.d jimla.r.d jimmy-choo jimmychoo jjl jogging jord jogging-jord joggingjord joggingstroll jogos john-varvatos joint-pain jointpain jordan 1 jordan 2 jordan 3 jordan 4 jordan basket jordan brand jordan femme jordan fille jordan gamma jordan grise jordan milan jordan noir jordan out jordan pas jordan retro jordan sc jordan shoe jordan store jordan-1 jordan-2 jordan-3 jordan-4 jordan-basket jordan-brand jordan-femme jordan-fille jordan-gamma jordan-grise jordan-milan jordan-noir jordan-out jordan-pas jordan-retro jordan-sale jordan-sc jordan-shoe jordan-store jordan1 jordan2 jordan3 jordan4 jordanbrand jordangamma jordangrise jordankicks jordanmilan jordannoir jordanout jordanretro jordans 1 jordans 2 jordans 3 jordans 4 jordans cheap jordans for jordans out jordans-1 jordans-2 jordans-3 jordans-4 jordans-cheap jordans-for jordans-out jordans1 jordans2 jordans3 jordans4 jordansale jordansc jordanscheap jordanshoe jordansout jordansshoe jordanstore jordjev joselyn sleeve joselyn-sleeve joselynsleeve journal/item jovial salon jovial-salon jp-bag jp-best jp-sale jp/blog jp/my jp/shop jpbag jpbest jpcity.co jpconverse jpmarcjacob jpmonster jpsale jpsasic js wing js-wing jsadidas jswing juanjuan juice detox juice-detox juicedetox juicycouture jumpedup junior baby junior kid junior-baby junior-kid juniors baby juniors kid juniors-baby juniors-kid just wanna just-wanna justcloud justness k http k.@ k.a.t.h.leen k.a.t.hl.een k.a.t.hle.en k.a.t.hlee.n k.a.t.hleen k.a.th.leen k.a.thl.een k.a.thle.en k.a.thlee.n k.a.thleen k.at.h.leen k.at.hl.een k.at.hle.en k.at.hlee.n k.at.hleen k.ath.l.een k.ath.le.en k.ath.lee.n k.ath.leen k.athl.e.en k.athl.ee.n k.athl.een k.athle.e.n k.athle.en k.athlee.n ka.t.h.l.een ka.t.h.le.en ka.t.h.lee.n ka.t.h.leen ka.t.hl.een ka.t.hle.en ka.t.hlee.n ka.t.hleen ka.th.l.een ka.th.le.en ka.th.lee.n ka.th.leen ka.thl.e.en ka.thl.ee.n ka.thl.een ka.thle.e.n ka.thle.en ka.thlee.n kaepernick jers kaepernick wom kaepernick youth kaepernick-jers kaepernick-wom kaepernick-youth kaepernickjers kaepernickwom kaepernickyouth kaffee-maschine kaffeemaschine kamagra kameri kanadakommen kanalizacyjne kanyewestsun kardashian karenmillen-au karenmillenau karpaczwsieci karuteie kasino kat.h.l.e.en kat.h.l.ee.n kat.h.l.een kat.h.le.en kat.h.lee.n kat.h.leen kat.hl.e.en kat.hl.ee.n kat.hl.een kat.hle.en kat.hlee.n katalog katespadese kath.l.e.e.n kath.l.e.en kath.l.ee.n kath.l.een kath.le.en kath.lee.n kathl.e.e.n kathl.e.en kathle.e.n kaufen kawa strefa kawa-strefa kawastrefa kazino keep writing! kensington parka kensington-parka kensingtonparka key gen key prog key-gen key-prog keygen keyless-remote keylessremote keyprog keyword.txt keyword1 keyword2 keyword3 keywords.txt keywords1 keywords2 keywords3 kfcnfl khttp khumbu north khumbu-north kid baby kid-baby kids baby kids nike kids out kids ugg kids-baby kids-nike kids-ugg kidsnike killer blog killer page killer post killer site killer weblog killer-blog killer-page killer-post killer-site killer-weblog kinder moncler kinder-moncler kindermoncler kino online kino pozitiv kino prog kino winterthur kino-online kino-pozitiv kino-prog kino-winterthur kinoonline kinopozitiv kinoprog kinowinterthur kitai kitchen porn kitchen-porn kitchen-worktop kitchenporn kitchenworktop kjole salg kjole tilbud kjole udsalg kjole-salg kjole-tilbud kjole-udsalg kjolesalg kjoletilbud kjoleudsalg kleding winkel kleding-winkel kledingwinkel kleidung jack kleidung-jack kleidungjack klein felpa klein mujer klein prezzi klein_ klein-felpa klein-mujer klein-prezzi kleinfelpa kleinmujer kleinprezzi klonopin klub.in klub.pl klub.ro klub.ru klub.su klub.za knee-joint knee-pain kneejoint kneepain knewgedlo knicely knigki knigko knockoff eyewear knockoff hand knockoff-eyewear knockoff-hand knockoffhand knolckoff know-web knowing answer knowing-answer knowledgeable individual knowledgeable-individual known blog known-blog kobe ont kobe_ kobe-ont kobe-shoe kobeshoe kompanii komputer konkurs konsalting konsultan kontakta-oss kontrahenta koop dsqu koop-dsqu koopdsqu koopsted kope jassen kope-jassen kopejassen kopfhoerer kopia-zapasowa kor out kor-out korout kors austr kors baby kors bag kors brand kors bras kors braz kors canad kors charlton kors cheap kors crossbody kors diaper kors dillard kors factor kors france kors glass kors grayson kors hamilton kors hand kors laptop kors messenger kors milan kors now! kors online kors out kors puffer kors purse kors replica kors runway kors sale kors straw kors tonne kors tote kors uk kors vancouver kors wallet kors-baby kors-bag kors-brand kors-canad kors-charlton kors-cheap kors-crossbody kors-diaper kors-dillard kors-factor kors-glass kors-grayson kors-hamilton kors-hand kors-laptop kors-messenger kors-milan kors-now kors-online kors-out kors-puffer kors-purse kors-sale kors-straw kors-tonne kors-tote kors-vancouver kors-watch kors+ korsbaby korsbag korscanad korscrossbody korsdiaper korsglass korshand korslaptop korsmessenger korsmilan korsonline korsout korspuffer korspurse korsstraw korstote korswatch kosmetyc kosmetyki kosze kpkfprbrq krasivaya devushka krasivaya-devushka kreddit kreddyt kreddyyt kredit kredyt kristi longchamp kristi-longchamp kristilongchamp krossoverov ku42. kugelbahn kupit kurs yevro kurs-yevro kussin hartmann kussin-hartmann kussinhartmann kvartiry kvepalai internet kvepalai-internet kе l http l.@ l.uk.e.w.a.rm la pillule la-pillule laarzen kopen laarzen schoen laarzen-kopen laarzen-schoen laarzenkopen laarzenschoen labetalol laby boy laby-boy labyboy lacoste out lacoste_ lacoste-out lacosteout lady porn lady_top lady-porn lady.porn ladyporn lamborghini hover lamborghini-hover lamictal lamisil lancel ad lancel pas lancel sac lancel_ lancel-adjani lancel-pas lancel-sac lancelad lancelpas lancelsac land hack land-hack landhack lap lap lap-lap laplap laranita free laranita-free laranitafree large longchamp large tote large-longchamp large-tote largelongchamp largetote lariam las| lasart.es laser-therapy laseriv laserowe lasertherapy lasix last looker last-looker lastlooker latest blog latest-blog latestblog latisse generic latisse_ latisse-generic latonya. latvia stag latvia-stag latviastag lauren amster lauren aus lauren belg lauren cheap lauren dame lauren factor lauren femme lauren heren lauren home lauren homme lauren kleding lauren neder lauren norge lauren online lauren oslo lauren out lauren polo lauren ralph lauren sale lauren sandal lauren shirt lauren short lauren sverige lauren uk lauren-amster lauren-aus lauren-belg lauren-cheap lauren-dame lauren-factor lauren-femme lauren-heren lauren-home lauren-homme lauren-kleding lauren-neder lauren-norge lauren-online lauren-oslo lauren-out lauren-polo lauren-ralph lauren-sale lauren-sandal lauren-shirt lauren-short lauren-sverige lauren-uk laurenamster laurenaus laurenbelg laurencheap laurendame laurenfemme laurenhome laurenhomme laurennorge laurenonline laurenout laurenpolo laurenralph laurensale laurensandal laurenshirt laurenshort laurensverige laurent femme laurent sandal laurent-femme laurent-sandal laurentfemme laurentsandal laurenuk lawful page lawful-page lawoffice.net lɑ lead-sys leaked1 learn face learn help learn-face learn-health learn-help learn+health learnface learnhelp learningxchange learnpianohere leather levi leather-levi leatherlevi lebron shoe lebron_ lebron-shoe lebronshoe lebronxshoe led_down led_flood led_indust led_street leder jack leder-jack lederjack leg-wear legal bud legal cash legal hack legal steroid legal-bud legal-cash legal-hack legal-steroid legalbud legalcash legalhack legalsteroid legend hack legend-hack legendhack legends hack legends-hack legendshack leger band leger copies leger dress leger sale leger-band leger-copies leger-dress leger-sale legerband legerdress legersale legit drug legit graph legit pharm legit script legit-drug legit-graph legit-pharm legit-script legitdrug legitgraph legitimate drug legitimate graph legitimate pharm legitimate script legitimate-drug legitimate-graph legitimate-pharm legitimate-script legitimatedrug legitimategraph legitimatepharm legitimatescript legitpharm legitscript leibly nashivki leibly-nashivki lend-direct lenddirect lendsomemoney lenjerie lesben- lesbian bdsm lesbian fuck lesbian porn lesbian school lesbian-bdsm lesbian-fuck lesbian-porn lesbian-school lesbianbdsm lesbianfuck lesbianporn lesbianschool lesbos- lesbos. lespaulsmith less dress less peplum less-dress less-peplum lessdress lesson1 lesspeplum letrozole levaquin level 50 level market level-50 level-market levelmarket levitra levne lhttp liabaleles libraries.asp libraries.cfm libraries.htm libraries.jsp libraries.php library.asp library.cfm library.htm library.jsp library.php librarys.asp librarys.cfm librarys.htm librarys.jsp librarys.php librium licenzija lid pants lid-pants lifeinsur lifelock ligne medicament ligne médicament ligne-medicament ligne-médicament limited jers limited-jers limitedjers limo-service linguim.co link bait link build link camp link directory link download link exchange link issue link juice link market link pyramid link sale link seller link seo link serv link submit link track link vault link_ link-bait link-build link-camp link-directory link-download link-exchange link-issue link-juice link-market link-pyramid link-sale link-seller link-seo link-serv link-submit link-track link-vault linkbait linkbuild linkcamp linkdirectory linkexchange linking camp linking issue linking market linking seo linking serv linking_ linking-camp linking-issue linking-market linking-seo linking-serv linkingcamp linkingseo linkissue linkjuice linklegend linkman linkmarket linkpyramid links exchange links sale links seller links_ links-exchange links-sale links-seller linksale linkseller linkseo linkserv linksexchange linkssale linksseller linksubmit linktrack linkusup linkvault linkz lionsfan lionsjers lipitor lisinopril lisseur-pascher lisseurpascher listajp listasegment litte more litte-more littlegod live-article livearticle livesex liyrral llet alone llet-alone llifted llook loan canad loan compan loan fast loan online loan-canad loan-compan loan-direct loan-fast loan-online loan.co loan.in loan.net loanalys loancompan loandirect loanfast loanonline loans canad loans compan loans fast loans online loans-canad loans-compan loans-direct loans-fast loans-online loans.co loans.in loans.net loans1 loans2 loans3 loans4 loanscanad loanscompan loansdirect loansfast loansonline locate-cell-phone locatecellphone lociraj lock prezzi lock-prezzi lockprezzi lodz łódź log.se. log/? logcabins login widget login-widget login.asp login.cfm login.htm login.jsp login.php loginwidget logotipo hollis logotipo-hollis logotipohollis logout widget logout-widget logoutwidget lohan porn lohan-porn lohanporn loiknog loiusvuitton loknoig lol i london genuine london-genuine londongenuine long long longafter longchamp 2013 longchamp 2014 longchamp 2015 longchamp aus longchamp bag longchamp doctor longchamp fabric longchamp hand longchamp hobo longchamp martin longchamp moncler longchamp online longchamp out longchamp pas longchamp planet longchamp pliage longchamp purse longchamp sac longchamp sold longchamp tasche longchamp tote longchamp tourne longchamp uk longchamp yama longchamp_ longchamp-2013 longchamp-2014 longchamp-2015 longchamp-aus longchamp-doctor longchamp-fabric longchamp-hand longchamp-hobo longchamp-martin longchamp-moncler longchamp-online longchamp-out longchamp-planet longchamp-pliage longchamp-purse longchamp-sac longchamp-shop longchamp-sold longchamp-tasche longchamp-tote longchamp-tourne longchamp-uk longchamp-yama longchamp.asp longchamp.cfm longchamp.htm longchamp.jsp longchamp.php longchamp2013 longchamp2014 longchamp2015 longchampaus longchampbag longchampdoctor longchampfabric longchamphand longchamphobo longchamplondon longchampmartin longchampmoncler longchampoffici longchamponline longchampout longchampp longchampplanet longchamppurse longchampq longchamps_ longchampsa longchampsac longchampshop longchampsold longchamptasche longchamptote longchamptourne longchampuk longchampyama lookeach looked on-line looked-on-line lookin for lookin-for lookingfor lookk lorazepam lortab los replica los-replica lose-weight losers beat losers outnumbered losers-beat losers-outnumbered loseweight losreplica lot its lot-its loteprednol lottie maxi lottie-maxi lottiemaxi lotto-tip lottotip loubiton louboutin bianca louboutin canad louboutin cheap louboutin disc louboutin femme louboutin homme louboutin out louboutin pas louboutin platform louboutin pump louboutin red louboutin sale louboutin schuh louboutin shoe louboutin sneaker louboutin sold louboutin stiefel louboutin uk louboutin wedding louboutin wedge louboutin_ louboutin-bianca louboutin-chau louboutin-cheap louboutin-disc louboutin-femme louboutin-homme louboutin-out louboutin-pas louboutin-platform louboutin-pump louboutin-red louboutin-sale louboutin-schuh louboutin-shoe louboutin-sneaker louboutin-sold louboutin-stiefel louboutin-wedding louboutin-wedge louboutin+ louboutin8 louboutinbianca louboutindisc louboutinfr loubouting louboutinout louboutinpas louboutinpascher louboutins pas louboutins-pas louboutinsale louboutinschuh louboutinshoe louboutinsneaker louboutinsold louboutinspas louboutinstiefel louboutinuk louboutinwedding louboutinwedge loubouton loubutin loubuton louis vitton louis vuittone louis-vitton louis-vuitton louisvitton louisvuit. louisvuitton louiswuitton love connect love-connect love-lv loveconnect loved onein loved-onein lovelv lovely just lovely thong lovely-just lovely-thong lovelythong lovelyto lovemyhair loving natural loving-natural lovingnatural low priced low rake low-cost- low-priced low-rake lowest rake lowest-rake lowestrake lowpriced lowrake loyalty today loyalty-today loyaltytoday lublin. lublina. luck jers luck-jers luckjers luggage tote luggage-tote luggagetote luis vitton luis-vitton luisvitton lululemon cal lululemon loca lululemon out lululemon_ lululemon-cal lululemon-loca lululemon-out lululemoncal lululemonloca lululemonout lumigan luminor watch luminor-watch luminorwatch lunarglide lunarlon lunderground lunette oakley lunette ray lunette sold lunette-oakley lunette-ray lunette-sold lunetteoakley lunetteray lunettes oakley lunettes-de-soleil lunettes-oakley lunettesdesoleil lunettesoakley lunettesold lunettespaschere lupusinfo lux-replica luxottica ray luxottica-ray luxotticaray luxreplica luxury chanel luxury-brand luxury-chanel luxury-replica luxurybrand luxurychanel luxuryreplica lv austr lv bag lv bras lv braz lv factor lv france lv jap lv jp lv-bag lv-disc lv-hand lv-jap lv-jp lv-out lv-replica lv-uk lvbag lvdisc lvforsale lvhand lviv lvout lvreplica lvsale lvuk lyadhngxppq lі lо l猫 m http m.@ m4a-download m4a-player m4a-to-mp3 m4a2mp3 m4aplayer m4atomp3 m88 m88 m88-m88 m88day m88m88 m88ui mac cosmetic mac-cosmetic mac-makeup mac-mascara macha-slim machaslim mackage jack mackage-jack mackagejack maclipgloss macmakeup macmascara made-milf madeknown mademilf madeye30 magasin asic magasin chemise magasin hollis magasin longchamp magasin robe magasin-asic magasin-chemise magasin-hollis magasin-longchamp magasin-robe magasinasic magasincanadagoose magasinchemise magasinhollis magasinlongchamp magasinrobe magazine/page1 magazine/page2 magicmoncler magnificent article magnificent goods magnificent inform magnificent-article magnificent-goods magnificent-inform magnificentinform mail_ mail.in mail.pl mail.ro mail.ru mail.su mail.za maillot bundes maillot de maillot foot maillot ligue maillot maillot maillot man maillot psg maillot uk maillot_ maillot-bundes maillot-de maillot-enfant maillot-foot maillot-ligue maillot-maillot maillot-man maillot-psg maillot-uk maillotbundes maillotde maillotligue maillotman maillotpsg maillots de foot maillots ligue maillots_ maillots-de-foot maillots-ligue maillots-tenue maillots/tenue maillotsligue maillotuk main longchamp main-longchamp main.asp main.cfm main.htm main.jsp main.php main/main mainlongchamp mains lancel mains-lancel mains.asp mainslancel maintain prevent maintain-prevent maintainprevent majesticriver make csgo make hemp make money make-backup make-csgo make-hemp make-million make-money make-the-most makebaby makecsgo makeme makemoney makeownhood makeownshirt makeowntee makersnow makeup.bl making money making-money makingmoney maklare malaysia casino malaysia-casino male enhance male erotic male-enhance male-erotic maleenhance malepower mall store mall-store malls store malls-store mallsstore mallstore malwareremov man cheap man gaga man-cheap man-gaga manage_ manage/new management-software manche longue manche-longue mancheap manchesterhotel manchette hermes manchette-hermes manchettehermes mangaga mangoosteen manicshop manner puma månner puma manner-puma månner-puma mannerpuma månnerpuma manning jers manning-jers manningjers manor escort manor-escort manteau karen manteau-karen manteaukaren mar skin mar-skin maran-train marant boot marant shoe marant sneak marant-boot marant-shoe marant-sneak marantboot marantrain marantshoe marantsneak margreet. mariage hugo mariage orig mariage-hugo mariage-orig mariagehugo mariageorig marihuana marijuana bene marijuana fact marijuana-bene marijuana-fact marijuanabene marijuanafact marine gay marine-gay marinegay marines gay marines-gay marinesgay marke jean marke-jean markejean marker boss marker dude marker gaul marker-boss marker-dude marker-gaul markerboss markerdude markergaul market exchange market online market prices market pricing market research market samurai market xchange market-exchange market-online market-prices market-pricing market-research market-samurai market-xchange marketexchange marketing blog marketing book marketing exchange marketing hero marketing mark marketing online marketing prof marketing samu marketing service marketing tip marketing xchange marketing-blog marketing-book marketing-exchange marketing-hero marketing-mark marketing-online marketing-prof marketing-samu marketing-service marketing-tip marketing-xchange marketing.in marketingblog marketingbook marketingexchange marketinghero marketingmark marketingonline marketingprof marketingsamu marketingservice marketingtip marketingxchange marketnn marketonline marketprices marketpricing marketresearch marketsamurai marketxchange marlboro 100 marlboro cig marlboro gold marlboro-100 marlboro-cig marlboro-gold marlboro100 marshall guantes marshall-guantes marshallguantes martini cagliari martini-cagliari martinicagliari marvelous post marvelous-post mass article mass face mass-article mass-face massarticle massface massive-web masszaz mastablasta master.in master.pl master.ru master.su master.za mastermind-team mastermindteam masters.in masters.pl masters.ro masters.ru masters.su masters.za matchcash material stylish material-stylish matthewsjers matural max griffey max sale max-griffey max-sale maxalt maxazria plum maxazria_ maxazria-plum maxazriaplum maxgriffey maxi va maxi-va maxiva maxsale may-help-you mayari birkenstock mayari-birkenstock mayaribirkenstock mbt ayakkab mbt baridi mbt boot mbt chapa mbt clear mbt exercise mbt footwear mbt fora mbt haraka mbt jap mbt jp mbt men mbt online mbt out mbt panda mbt sale mbt sandal mbt scarpe mbt schuh mbt shoe mbt shop mbt sneaker mbt spaccio mbt special mbt tariki mbt tembea mbt train mbt tunisha mbt uk mbt unono mbt uomo mbt vanzari mbt women mbt_ mbt-ayakkab mbt-baridi mbt-boot mbt-carpe mbt-chapa mbt-clear mbt-exercise mbt-footwear mbt-fora mbt-haraka mbt-jap mbt-jp mbt-men mbt-online mbt-out mbt-panda mbt-sale mbt-sandal mbt-schuh mbt-shoe mbt-shop mbt-sneaker mbt-spaccio mbt-special mbt-tariki mbt-tembea mbt-train mbt-tunisha mbt-uk mbt-unono mbt-uomo mbt-vanzari mbt-women mbt+ mbtayakkab mbtbaridi mbtboot mbtchapa mbtclear mbtexercise mbtfootwear mbtfora mbtgun mbtjap mbtjp mbtmen mbtonline mbtout mbtpanda mbtsale mbtsandal mbtschuh mbtshoe mbtshop mbtsko mbtsneaker mbtspaccio mbtspecial mbttariki mbttembea mbttrain mbttunisha mbtuk mbtunono mbtuomo mbtvanzari mbtwomen mckinqi mcm backpack mcm bag mcm belt mcm hand mcm london mcm new mcm purse mcm shop mcm stark mcm_ mcm-backpack mcm-bag mcm-belt mcm-hand mcm-london mcm-new mcm-purse mcm-shop mcm-stark mcm.co mcmbackpack mcmbag mcmbelt mcmlondon mcmnew mcmpurse mcmshop mcmstark mcqueen club mcqueen dress mcqueen leopard mcqueen online mcqueen pashmin mcqueen shoe mcqueen silk mcqueen-club mcqueen-dress mcqueen-leopard mcqueen-online mcqueen-pashmin mcqueen-shoe mcqueen-silk mcqueenclub mcqueendress mcqueensilk me greatly me passionne me-greatly me-passionne meandyou meble gabinetowe meble ogrodowe meble-gabinetowe meble-ogrodowe meblowy mechpromo. meclizine med-shop medbaz medecine medeniz media marketing media-marketing media-palitra media/sys mediamarketing mediapalitra medicarefraud medicinez medicinz medieval cost medieval-cost medievalcost medigital medikal.co medizinische medphrase medshop meendo mega culo mega pezone mega teta mega tetona mega-culo mega-pezone mega-teta mega-tetona megaculo megapezone megateta megatetona meilleur casino meilleur cassino meilleur-casino meilleur-cassino meilleurcasino meilleurcassino meilleurs casino meilleurs cassino meilleurs-casino meilleurs-cassino meilleurscasino meilleurscassino meisterstuck pen meisterstuck-pen meisterstuckpen meizitang melatonin meloxicam member_ member.asp member.cfm member.htm member.jsp member.php memberlist.asp memberlist.cfm memberlist.htm memberlist.jsp memberlist.php members_ membership hack membership-hack membership.asp membership.cfm membership.htm membership.jsp membership.php membershiphack men barbour men bikini men boost men cheap men dating men gaga men hand men makeup men mbt men mizuno men nike men timber men-barbour men-bikini men-boost men-cheap men-date men-dating men-gaga men-hand men-makeup men-mbt men-mizuno men-nike men-sneaker men-timber men's timber men’s timber men+ menbarbour menboost mencheap mendate mendating mengaga menmbt menmizuno mennike mens barbour mens bathrobe mens bcbg mens boot mens coach mens fashion mens hollis mens jack mens nike mens puma mens sale mens shoe mens timber mens watch mens-bag mens-barbour mens-bathrobe mens-bcbg mens-boot mens-coach mens-fashion mens-hollis mens-jack mens-nike mens-puma mens-sale mens-shoe mens-timber mens-train mens-watch mensbarbour mensbathrobe mensbcbg menseekingmen menseekingwom mensjack mensnike menspuma menssale menstimber mentalprocess menthol dunhill menthol-dunhill mentimber menu/menu mequinol meratol mercantilism mercati di mercati-di mercenary.co mercurial 2013 mercurial 2014 mercurial 2015 mercurial vapo mercurial-2013 mercurial-2014 mercurial-2015 mercurial-vapo mercurial2013 mercurial2014 mercurial2015 mercurialvapo merely wanna merely-wanna merger-helper mergerhelper meridia mes hormone mes-hormone message erotic message_ message-erotic messages erotic messages_ messages-erotic messengerstyle metacam metallo methadone methionine method guy method-guy methode argent methode pour methode-argent methode-pour methodeargent methodepour methodguy metlifecare metode ociepl metode-ociepl metronidazole mezzo louis mezzo-louis mezzolouis mhttp mi40 review mi40-review miami escort miami-escort miamiescort michael-kors michaelkors mieszkania mieszkanie migliore replica migliore-replica migliorereplica milano jap milano jp milano scarpe milano uk milano-jap milano-jp milano-scarpe milano-uk milanojap milanojp milanos jap milanos jp milanos uk milanos-jap milanos-jp milanos-uk milanoscarpe milanosjap milanosjp milanosuk milanouk mild activ mild-activ milendress milf_ milfs_ military friendly military-friendly militaryfriendly millen au millen clear millen color millen colour millen dress millen factor millen jack millen neder millen out millen shop millen uk millen-au millen-clear millen-color millen-colour millen-dress millen-factor millen-jack millen-neder millen-out millen-shop millen-uk millenclear millencolor millencolour millenjack millenneder millenout millenshop million hit million-hit millionhit mine day mine-day minecraft free minecraft-free minecraftfree mineday mini credit mini-credit minicredit minirin mint cash mint coin mint-cash mint-coin mintcash mintcoin minumum miracle disc miracle-disc miracledisc mirapex mirror femme mirror-femme mirrorfemme mister-design misterboy mit rezept mit-rezept mittelx miu miu miu sunglass miu-miu miu-sunglass miumiu miusunglass mixed-race mixedrace mizuno shoe mizuno shop mizuno store mizuno-shoe mizuno-shop mizuno-store mizunoshoe mizunoshop mizunostore mkhand mking sure mking-sure mkout mlb apparel mlb cap mlb home mlb mlb mlb shop mlb_ mlb-apparel mlb-cap mlb-home mlb-mlb mlb-shop mlb.asp mlb.cfm mlb.htm mlb.jsp mlb.php mlbcap mlbmlb mlbshop mlm business mlm lead mlm-business mlm-lead mlmlead mlskev mlsp suit mlsp-suit mlspweapon mmcenter.in mmy page mobi/news mobilabonnementer mobile porn mobile sim mobile xxx mobile-phone mobile-porn mobile-xxx mobileporn mobilexxx mobistealth moczanowa modafinil models-of-france models.in modelsoffrance moderowany moduleinstance modules.asp modules.cfm modules.htm modules.jsp modules.php moldremov moncle site moncle sito moncle-site moncle-sito monclear moncler 2013 moncler 2014 moncler 2015 moncler amster moncler andorra moncler barata moncler barato moncler berriat moncler cap moncler cloth moncler coat moncler donn moncler espa moncler femme moncler firenze moncler france moncler giub moncler hand moncler homme moncler jack moncler jas moncler jassen moncler jura moncler klassiker moncler man moncler men moncler online moncler out moncler padova moncler paris moncler parka moncler pas moncler pascher moncler piumini moncler piumino moncler polo moncler pour moncler prezzi moncler pullover moncler quincy moncler site moncler sito moncler ski moncler sold moncler store moncler sweater moncler tibet moncler uk moncler uomo moncler vast moncler väst moncler vente moncler vest moncler vos moncler weste moncler westen moncler-- moncler-2013 moncler-2014 moncler-2015 moncler-amster moncler-andorra moncler-barata moncler-barato moncler-berriat moncler-cap moncler-cloth moncler-coat moncler-donn moncler-espa moncler-femme moncler-firenze moncler-france moncler-giub moncler-hand moncler-homme moncler-jack moncler-jas moncler-jassen moncler-jura moncler-klassiker moncler-man moncler-men moncler-online moncler-out moncler-padova moncler-paris moncler-parka moncler-pas moncler-pascher moncler-piumini moncler-piumino moncler-polo moncler-pour moncler-prezzi moncler-pullover moncler-quincy moncler-site moncler-sito moncler-ski moncler-sold moncler-store moncler-sweater moncler-tibet moncler-uk moncler-uomo moncler-vast moncler-väst moncler-vente moncler-vest moncler-vos moncler-weste moncler-westen moncler.arkis moncler.asp moncler.cfm moncler.htm moncler.jsp moncler.php moncler2013 moncler2014 moncler2015 moncleramster monclerandorra monclerbarata monclerbarato monclerberriat monclercap monclercloth monclercoat monclerdonn monclerespa monclerfemme monclerfirenze monclerfrance monclergiub monclerhand monclerhomme monclerjack monclerjas monclerjassen monclerjura monclerklassiker monclerman monclermen moncleronline monclerout monclerpadova monclerparis monclerparka monclerpas monclerpascher monclerpiumini monclerpiumino monclerpolo monclerpour monclerprezzi monclerpullover monclerquincy monclersite monclersito monclerski monclersold monclerstore monclersweater monclertibet moncleruk moncleruomo monclervast monclerväst monclervente monclervest monclervos monclerweste monclerwesten monclesite monclesito monday deal monday sale monday ugg monday-deal monday-sale monday-ugg mondaydeal mondaysale mondayugg monetize your monetize-your money adder money buzz money fast money generat money prim money robot money-adder money-buzz money-fast money-generat money-mak money-prim money-robot money-site money-with money.asp money.cfm money.htm money.jsp money.php moneybuzz moneyfast moneygenerat moneymak moneyprim moneyrobot monica-santa monohydrate monroussillon monster beat monster earphone monster jap monster jp monster_ monster-beat monster-earphone monster-head monster-jap monster-jp monster/beat monsterbeat monsterearphone monsterhead monsterjap monsterjp montaj elektro montaj kanali montaj-elektro montaj-kanali montaj-montaj montaj/montaj montazh elektro montazh kanali montazh obsluzh montazh-elektro montazh-kanali montazh-montazh montazh-obsluzh montazh/montazh montblanc ballpoint montblanc boutique montblanc franc montblanc jap montblanc jp montblanc kuge montblanc meister montblanc paris montblanc pen montblanc rollerball montblanc sold montblanc stylo montblanc uk montblanc_ montblanc-ballpoint montblanc-boutique montblanc-franc montblanc-jap montblanc-jp montblanc-kuge montblanc-meister montblanc-paris montblanc-pen montblanc-rollerball montblanc-sold montblanc-stylo montblanc-uk montblancballpoint montblancboutique montblancfranc montblancjap montblancjp montblanckuge montblancmeister montblancparis montblancpen montblancrollerball montblancsold montblancstylo montblancuk montre bulgari montre bvlgari montre femme montre rolex montre-bulgari montre-bvlgari montre-femme montre-rolex montrebulgari montrebvlgari montrefemme montrerolex montres femmes montres-femmes montresfemmes moore about moore-about more eventually more-deal more-eventually more-lv morelv morpg morre about morre-about mortgage.asp mortgage.cfm mortgage.htm mortgage.jsp mortgage.php mortgagecalc moscow model moscow-model moscowmodel mosteffective mostexpensive mostra video mostra-video mostravideo moustache play moustache-play moustacheplay movie-online movie-zone movie/? movieonline movies-online movies-zone movies/? moviesandfilm moviesdl moviesonline movieszone moviezone mp-escort mp3-download mp3-player mp3.ru mp3.su mp3la.ru mp3player mp4 sex mp4-sex mp4sex mpescort mpnth mrant sneak mrant-sneak mrantsneak msgnum much important much utile much-boot much-gucci much-important much-utile muchgucci mujer timberland mujer-timberland mulbeery mulberry alexa mulberry bag mulberry brand mulberry hand mulberry hobo mulberry oak mulberry out mulberry purse mulberry task mulberry top mulberry uk mulberry_ mulberry-alexa mulberry-bag mulberry-brand mulberry-fashion mulberry-hand mulberry-hobo mulberry-oak mulberry-purse mulberry-task mulberry-top mulberry-uk mulberrybag mulberrybrand mulberryfashion mulberryhand mulberryhobo mulberrypurse mulberrytask mulberrytop muscle-suppl muscles exercis muscles-exercis musclesuppl muscular abdomen muscular-abdomen muscularwom music viral music-viral musicviral mustlook mutuelle sante mutuelle-sante mutuelles sante mutuelles-sante muzi bily muži bílý muzi boty muži boty muzi modry muži modrý muzi-bily muži-bílý muzi-boty muži-boty muzi-modry muži-modrý muzibily mužibílý muziboty mužiboty muzimodry mužimodrý muzyczna muzyczny my bebo my bitcoin my hermes my myspace my online my sitte my ssite my web: my webb my wweb my-bebo my-bitcoin my-fashion my-hermes my-my my-new-ip my-online my-sitte my-ssite my-virus my-webb my-wweb mybitcoin mydead- mydomain myfashion myfitness mygiftcard myhermes myjke wysokocis myjke-wysokocis myknee myleadsys mylupus mynewsite mynfl myonline myowndomain myqrop myreview.co myspacee myssite mytest myvirus myweb mywebb mywebsite mywweb myy myyspace mzt-index mztindex m谩s n http n.@ n1-takeaway n1takeaway nail upon nail_art nail-art nail-fest nail-jap nail-jp nail-upon nailart nailfest nailjap nailjp nails-jap nails-jp nailsjap nailsjp nailupon najlepsze najsłynniejszym naked chicsk naked sex naked-chicsk naked-sex nakedsex nakedteen naltrexone namacalnie zalega namacalnie-zalega name-brand nanogold naproxen nashivki leibly nashivki-leibly natürlich nateddrink natura.it natural cure natural penis natural-cure natural-penis natural-way naturalcure naturally gain naturally-gain naturalovarian naturalpenis naturalway natureto natürlich naturlig penis naturlig-penis naturligpenis nba 2k nba apparel nba cappelli nba home nba houston nba jers nba nba nba shop nba utah nba_ nba-2k nba-apparel nba-cappelli nba-home nba-houston nba-jers nba-men nba-nba nba-shop nba-sko nba-utah nba.asp nba.cfm nba.htm nba.jsp nba.php nba%202k nba2k nbahouston nbajers nbamen nbanba nbashop nbasko nbautah nbshoe near near neat article neat blog neat page neat post neat site neat weblog neat website neat-article neat-blog neat-page neat-post neat-site neat-weblog neat-website neatarticle neatblog neatly favor neatly-favor neatlyfavor neatpage neatpost neatsite neatweblog neatwebsite need bitcoin need sex need-bitcoin need-sex needbitcoin needmoney needsex negozi alviero negozi burberry negozi milan negozi online negozi-alviero negozi-burberry negozi-milan negozi-online negozialviero negoziburberry negozimilan negozio-hollis negoziohollis negozionline net viewer net-viewer net/bilder net/fr/ net/members/ nethttp netload.in netviewer network buzz network scam network truth network-buzz network-scam network-truth networkbuzz networks buzz networks-buzz networks-scam networks-truth networksbuzz networkscam networksscam networkstruth networktruth neurontin nevertheless just nevertheless-just new gucci new jord new manolo new seo new-article new-balance new-era-hat new-gucci new-jord new-manolo new-oakley new-seo new-vibram new/dress new/ipad new/iphone new/prada newarrival newarticle newbalance1 newbalance2 newblance newerahat newest news newest-news newgucci newhong newjord newmanolo newoakley newport 100s newport-100s newport100s news.in newseo newsletter service newsletter-service newss. newvibram newwebsite nexopia nfc.lol nfl apparel nfl austr nfl home nfl italia nfl jers nfl nfl nfl replica nfl shop nfl-apparel nfl-beanie nfl-home nfl-italia nfl-jers nfl-nfl nfl-official nfl-replica nfl-shop nfl.asp nfl.cfm nfl.htm nfl.jsp nfl.php nfl+ nfljers nflnfl nflofficial nflreplica nflshop nfr.asp nfr.cfm nfr.htm nfr.jsp nfr.php nhl apparel nhl home nhl jers nhl nhl nhl replica nhl shop nhl_ nhl-apparel nhl-home nhl-jers nhl-nhl nhl-replica nhl-shop nhl.asp nhl.cfm nhl.htm nhl.jsp nhl.php nhljers nhlnhl nhlofficial nhlreplica nhlshop nhttp niaspan nice annd nice article nice blog nice designed nice info nice page nice paragraph nice post nice practice nice site nice understand nice weblog nice website nice-article nice-blog nice-designed nice-info nice-page nice-paragraph nice-post nice-practice nice-site nice-understand nice-weblog nice-website nicearticle niceblog nicedesigned niceinfo nicepage niceparagraph nicepost nicesite niceweblog nicewebsite nicki-mariah nicoban nicolasbit nieruchomosci nieuwe zonnebrillen nieuwe-zonnebrillen nieuwezonnebrillen nike 5.0 nike andy nike designa nike free nike jers nike jord nike kd nike mercurial nike roshe nike run nike sb nike schuh nike shoe nike shox nike store nike tn nike total nike vv nike_ nike-air-force nike-and-shoe nike-andy nike-blazer nike-designa nike-dunk nike-free nike-freerun nike-jers nike-jord nike-kd nike-main nike-mercurial nike-roshe nike-run nike-sb nike-schuh nike-shoe nike-shox nike-sko nike-store nike-tn nike-total nike-vv nike1 nike2 nikeairmax nikeairshop nikeandshoe nikeandy nikeause nikeblazer nikede nikedesigna nikedunk nikefr nikefree nikefreerun nikegb nikejers nikejord nikemain nikemercurial nikepasch nikeroshe nikerun nikesb nikeschuh nikese nikeshoe nikeshop nikeshox nikesko nikesportj nikestore niketn niketokyo niketotal nikeuk nikevv ninja sword ninja-sword ninjasword niselv.co nitraazepam nitrazepam niue pokemon niue-pokemon nizoral nm.ru nm.su nneed-from nnuauec no collateral no guarant no-cache no-collateral no-credit no-fuss no-guarant no-hassle no-prescript no1.co nobis yatesy nobis-yatesy nobisyatesy nocache nocredit nohassle noir homme noir-homme noirhomme nolvadex nordstrom moncler nordstrom_ nordstrom-moncler nordstrommoncler norge canad norge-canad norgecanad north_face north+face northface pascher northface-canad northface-jack northface-out northface-pascher northface-sale northface-uk northface-us northfacecanad northfacejack northfaceout northfacepascher northfacesale northfacetr northfaceuk northfaceus northfaceya northfce norvasc nos molesto nos molestó not fake not-dienst not-fake not| notdienst notfake noticeably plenty noticeably-plenty noticias noticia noticias-noticia noticias, noticia notraty nouveau maillot nouveau-maillot nouveaumaillot nouvelles sneaker nouvelles-sneaker novinki_ now.in nqed nude girl nude hot nude share nude sharing nude teen nude vid nude_ nude-ass nude-girl nude-hot nude-share nude-sharing nude-teen nude-vid nude.asp nude.cfm nude.htm nude.jsp nude.php nudeass nudegirl nudehot nudes_ nudeshare nudesharing nudeteen nudevid nuestra-tienda nufactur numerous numer numerous-numer numerousnumer nuovo hogan nuovo-hogan nuovohogan nxvideo nе nу nү nո nօ o http o.@ o.all o.for o.the o.two o‡ oakeys oakley active oakley canad oakley caveat oakley cheap oakley cross oakley five oakley frog oakley glass oakley juliet oakley medusa oakley out oakley pascher oakley polar oakley radar oakley sale oakley store oakley straight oakley sunglass oakley tokyo oakley twenty oakley vault oakley_ oakley-active oakley-canad oakley-caveat oakley-cheap oakley-cross oakley-five oakley-frog oakley-glass oakley-juliet oakley-medusa oakley-out oakley-pascher oakley-polar oakley-radar oakley-ru oakley-sale oakley-sj oakley-store oakley-straight oakley-sunglass oakley-tokyo oakley-twenty oakley-uk oakley-vault oakleyactive oakleycanad oakleycaveat oakleycheap oakleycross oakleyfive oakleyfrog oakleyglass oakleyjuliet oakleymedusa oakleyout oakleypascher oakleypolar oakleyradar oakleyru oakleys sunglass oakleys_ oakleys-sunglass oakleys-uk oakleys. oakleysale oakleysj oakleyssunglass oakleystore oakleystraight oakleysuk oakleysunglass oakleytokyo oakleytwenty oakleyuk oakleyvault öáøåâïïê obey posse obey-posse obmucwwbuc obrezka derev'yev obrezka derev’yev obrezka derevev obrezka-derevev obtain consol obtain-consol obulvosiy obuv mesh obuv nach obuv nike obuv run obuv seda obuv šedá obuv zeny obuv ženy obuv-mesh obuv-nach obuv-nike obuv-run obuv-seda obuv-šedá obuv-zeny obuv-ženy obuvmesh obuvnach obuvnike obuvrun obuvseda obuvšedá obuvzeny obuvženy obuwia obuwie occhiali catalog occhiali ray occhiali sol occhiali_ occhiali-catalog occhiali-ray occhiali-sol occhialiray occhialisol oceanes-immo oceanesimmo ochudzanie oculosfeminino oczyszczalnia przydomow oczyszczalnia-przydomow odchudzanie odszkodowania odziez oem-software oemsoftware of deemed of herpes of internet of-deemed of-herpes of-internet ofargument offer out offer watch offer-out offer-watch offer.net offer.weebly offering free offering-free offerout offerta occhia offerta ray offerta-occhia offerta-ray offertaocchia offertaray offerte calvin offerte occhia offerte ray offerte-calvin offerte-occhia offerte-ray offertecalvin offerteocchia offerteray offerwatch office autopilot office-autopilot officeautopilot official giub official moncler official website official-giub official-moncler official-sale official-steeler official-style official-ugg official-website officiale moncler officiale-moncler officialemoncler officialgiub officialmailsite officialmoncler officialsale officialsteeler officialstyle officialteamshop officialugg officialwebsite offight ofhuman oficery ofnews oher hand oherhand ohttp oit| ojectx ok-cheap ok-sex okanyway okcanadagoose okcheap okpay oksex oksunglass okycupid old/dress old| once-in-a-lifetime one nike one-nike one:- one:) oneminutesite onenike ones size ones time ones-size ones-time onestime onestrap online bag online cash online casino online cassino online cheap online dat online dump online espa online free online gambl online gry online internet online journ online loan online longbow online m4a online mp3 online out online pharm online poker online pokie online reader online schuh online shoe online shop online usa online-bag online-blog online-business online-cash online-casino online-cassino online-cert online-cheap online-dat online-dump online-espa online-free online-gambl online-gry online-guide-of online-internet online-invest online-journ online-loan online-longbow online-m4a online-market online-med online-money online-mp3 online-out online-outlet online-pharm online-poker online-pokie online-reader online-sale online-schuh online-shoe online-shop online-store online-usa online-zapatilla online.asp online.cfm online.com online.de online.htm online.in online.jsp online.php online.web onlinebackup onlinebag onlineblog onlinebusiness onlinebuy onlinecash onlinecasino onlinecassino onlinecert onlinecheap onlinedat onlinede. onlineespa onlinefree onlinegambl onlineinternet onlineinvest onlinejp onlineloan onlinem4a onlinemed onlinemoney onlinemp3 onlineout onlineoutlet onlinepharm onlinepoker onlinepris onlinereader onlines.co onlines.web onlinesale onlineschuh onlineshoe onlineshop onlinestore onlinezapatilla onlpy only poker only pokie only-deal only-poker only-pokie onlyway onsale- onsale.asp onsale.cfm onsale.co onsale.htm onsale.jsp onsale.php onsales.co ontheir ooo bag ooo brand ooo watch ooo-bag ooo-brand ooo-watch ooobag ooobrand ooowatch openair opinion.in opinions.in oprawki ray oprawki-ray oprawkiray opt-in promotion opt-in-promotion optimization enhance optimization_ optimization-enhance optimize enhance optimize_ optimize-enhance optimizing_ option binaire option-binaire options binaire options-binaire options-trad optionstrad opyo0 oral-sex order forte order generic order parafon order_type order-forte order-generic order-parafon orderforte ordergeneric orderparafon ordersoma org/log org/rest organic hemp organic-hemp organizovana zlo organizovana-zlo organogold orgazma orghttp oriflame origami origami origami-origami origamilesson origami origamilesson-origami original_ orlistat orologi uomo orologi-uomo orologiuomo osobistosci osobistości other-brand otherbrand othertype othought otonanocoach otoplasty otoplenie/otoplenie otvety oulet online oulet-online ouletonline our link our-link ourlink out assim out-assim outelt outilclient outlet 1 outlet 2 outlet 3 outlet 4 outlet 2013 outlet 2014 outlet 2015 outlet bag outlet canad outlet cheap outlet coach outlet france outlet hand outlet hermes outlet hogan outlet italia outlet louis outlet moncler outlet oakley outlet offer outlet online outlet shoe outlet store outlet ugg outlet uk outlet usa outlet vancouver outlet woolrich outlet_ outlet-1 outlet-2 outlet-3 outlet-4 outlet-2013 outlet-2014 outlet-2015 outlet-austr outlet-canad outlet-cheap outlet-coach outlet-de outlet-france outlet-hermes outlet-hogan outlet-italia outlet-jap outlet-jp outlet-kopen outlet-louis outlet-mart outlet-moncler outlet-offer outlet-online outlet-sale outlet-shoe outlet-shop outlet-store outlet-ugg outlet-uk outlet-usa outlet-vancouver outlet-web outlet-woolrich outlet.bl outlet.cc outlet.click outlet.co outlet.eu outlet.mobi outlet.name outlet.net outlet.org outlet.uk outlet.us outlet.weebly outlet+ outlet1 outlet2 outlet3 outlet4 outletaustr outletbag outletcanad outletcheap outletcoach outletde outletforsale outletfrance outlethermes outletitalia outletjap outletjp outletkopen outletleouf outletlouis outletlove outletmart outletmoncler outletoffer outletonline outlets coach outlets-coach outlets-online outlets-sale outlets.co outlets.net outlets.org outlets.us outletsale outletscoach outletshoe outletshop outletsonline outletssale outletstore outletugg outletuk outletusa outletweb outletwoolrich outnumbered gainer outnumbered loser outnumbered-gainer outnumbered-loser outplacement compan outplacement-compan outplacementcompan outplacements outrank-competitor outsourcing compan outsourcing-compan outsourcingcompan outstanding blog outstanding page outstanding post outstanding site outstanding topic outstanding weblog outstanding website outstanding-blog outstanding-page outstanding-post outstanding-site outstanding-topic outstanding-weblog outstanding-website outstandingblog outstandingpage outstandingpost outstandingsite outstandingtopic outstandingweblog outstandingwebsite ovariancyst overall glance overall-glance own blog own blogroll own webpage own-blog own-blogroll own-webpage ownblog owned awn owned-awn ownersinsur owninterest oxycodone oxycontin oy oympia oρ oϲ oг oԁ oѕ oҟ oо oх oһ oь oս p http p.@ p90x package printing package-printing packaging printing packaging-printing packers jers packers-jers packersjers page scrape page_ page-scrape page.best page.tl page/pag page/page page/pg page/view pageed pageeed pages scrape pages_ pages-scrape pages/more pages/page pages/view pagescrape pagesscrape pagss pain-behind pain-relief. painbehind painless glad painless-glad painrelief. paintedfor pallet display pallet-display panda-shoe pandashoe pandora brace pandora charm pandora jewel pandora online pandora sale pandora_ pandora-brace pandora-charm pandora-jewel pandora-online pandora-sale pandoraau pandorabrace pandoracharm pandorajewel pandorauk panerai clock panerai watch panerai-clock panerai-watch paneraiclock paneraiwatch panier site panier-site paniersite pantalone hollis pantalone-hollis pantalonehollis panthers jers panthers merch panthers store panthers-jers panthers-merch panthers-store panthersmerch panthersstore panty pic panty play panty sex panty-pic panty-play panty-sex pantymania pantypic pantyplay par/index paradisiaque parafon forte parafon generic parafon info parafon muscle parafon_ parafon-forte parafon-generic parafon-info parafon-muscle parafonforte parafongeneric parafoninfo parafonmuscle paragraph writ paragraph-writ parajumpers parchet triplu parchet-triplu paretologic paris bors paris model paris securis paris sécuris paris-bors paris-for paris-model paris-royal paris-securis paris-sécuris parisbors parismodel parka france parka-france parkafrance parkas france parkas-france parkasfrance parkiety part mariage part-mariage particular article particular-article party poker party shirt party tshirt party xxx party_ party-poker party-shirt party-tshirt party-xxx partypoker partyshirt partytshirt partyxxx pas cher pas-cher pascher pascherfr pascheroakley pass generat pass prefix pass-generat pass-prefix passgenerat passprefix password generat password prefix password-generat password-prefix passwordgenerat passwordprefix passwort passwrot patagonia-zone patagoniazone patience maxi patience-maxi patiencemaxi patrao-online patraoonline patriots hat patriots jers patriots-hat patriots-jers paulsmith-cheap paulsmith-shop paulsmith1 paulsmith2 paulsmith201 paulsmithcheap paulsmithka paulsmithsa paulsmithshop paulsmithsu paxil pay_day pay-as-you-go payday loan payday-loan payday-on payday.co payday.in payday.pl payday.ro payday.ru payday.su payday.za paydayloan paydaynote paydayon payed off payed-off payment nigeria payment-nigeria paymentnigeria payments nigeria payments-nigeria paymentsnigeria paymobile payoneer paypal cash paypal money paypal-cash paypal-money paypalcash paypall paypalmoney payroll-calc paysafecard exchange paysafecard instant paysafecard-exchange paysafecard-instant pbrolme pc access pc health pc sex pc stuff pc windows pc-access pc-health pc-sex pc-stuff pc-windows pcaccess pchealth pcsex pcstuff pcwindows pdf/bv pdf/celine pdf/chanel pdf/prada pdf/rolex peer extra peer your peer-extra peer-your pefcret pen montblanc pen-montblanc penis adv penis blog penis enlarg penis forstor penis_ penis-adv penis-blog penis-enlarg penis-forstor penis.co penis.in penis.pl penis.ro penis.ru penis.su penis.za penisadv penisblog penisenlarg penmontblanc penned write penned-write penning this penning-this penny auction penny bid penny stock penny-auction penny-bid penny-stock pennyauction pennybid pennystock pens montblanc pens-montblanc pensmontblanc peopleand percocet perfect diet perfect vpn perfect writ perfect-diet perfect-vpn perfect-writ perfectdiet perfectvpn perfectwrit permission allow permission-allow permonth.co person provide person-provide personal pc personal-exper personal-injury personal-natur personal-pc personalexper personalinjury personalnatur personalpc personnalis petencies petite dress petite-dress petitedress petroleum kohlenwasserstoff petroleum-kohlenwasserstoff petroleumkohlenwasserstoff peuterey giub peuterey roma peuterey-giub peuterey-roma peutereygiub peutereyroma pezone mega pezone-mega pezonemega pezones mega pezones-mega pezonesmega pflegeversicherung pflegezusatzversicherung pg concern pg-concern pg/blog pg/forum pg/page pg/post pg/profil pg/view pgconcern pharm_ pharm. pharma canad pharma from pharma_ pharma-canad pharma-from pharma. pharmacies canad pharmacies from pharmacies-canad pharmacies-from pharmacy 24 pharmacy canad pharmacy from pharmacy online pharmacy_ pharmacy-24 pharmacy-at pharmacy-canad pharmacy-from pharmacy-online pharmacy24 pharmacyat pharmacyonline pharmaun pharmi pharmo pharms pharmz pheaemon phentermine phish casino phish-casino phishcasino phon-store phone advert phone free phone gratuit phone-advert phone-free phone-gratuit phone-jammer phone-lookup phone-number-lookup phone-store phoneadvert phoneforsale phonefree phonegratuit phonejammer phonelookup phonescanad phonesforsale phonestore phonstore photo/bv photo/celine photo/chanel photo/online photo/prada photo/rolex photoeditingdeal photos/bv photos/celine photos/chanel photos/online photos/prada photos/rolex php?article php?rowstart php?showuser php?tag php?title php5?title phpbb2 phpinfo phpoakley phttp phync phytoceramide pics-expensive pics, expensive picture blog picture-blog pictures blog pictures-blog pidarashechk pignee pigus kvepalai pigus-kvepalai pill cheap pill-cheap pillcheap pillonline pills cheap pills-cheap pills.co pillscheap pillsonline pillz pink large pink-large pinklarge pioggia gucci pioggia-gucci pioggiagucci pip hunter pip-hunter pipe/view piphunter pips daily pips hunter pips-daily pips-hunter pipshunter piroxicam piscine piumini moncler piumini woolrich piumini-moncler piumini-woolrich piuminimoncler piuminiwoolrich piumino moncler piumino-moncler piuminomoncler pi貌 pl/wiki plaesure plansare plarusee plastic ambalaje plastic caserole plastic casolete plastic-ambalaje plastic-caserole plastic-casolete plasticambalaje plasticcaserole plasticcasolete plated watch plated-watch plavix play-free play-online playerblock playfree playonline playvideo plazajp pleasant article pleasant blog pleasant designed pleasant good pleasant post pleasant-article pleasant-blog pleasant-designed pleasant-good pleasant-post please click please-click pleassant pleasure secret pleasure-secret pleasuresecret pleease pliage bag pliage cuir pliage shop pliage_ pliage-bag pliage-cuir pliage-shop pliagebag pliagecuir pliageshop plombier paris plombier-paris plombierparis plreause plugin_ plumbingservice plus dating plus-dating plus-my plus-size plusforsale plussize plytki plz assist plz help plz respond plz-assist plz-help plz-respond pmu poker pmu-poker pmupoker poco prezzo poco-prezzo podarochnoy korobk podarochnoy-korobk point generat point-generat point| pointgenerat points generat points-generat pointsgenerat poished poisk-nomera-tele pojap poke amulet poke cheat poke coin poke tcg poke-amulet poke-cheat poke-coin poke-tcg pokeamulet pokecheat pokecoin pokego-coin pokegocheat pokegocoin pokemon amulet pokemon bux pokemon cheap pokemon cheat pokemon coin pokemon suppl pokemon tcg pokemon-amulet pokemon-bux pokemon-cheap pokemon-cheat pokemon-coin pokemon-suppl pokemon-tcg pokemonamulet pokemonbux pokemoncheat pokemoncoin pokemongocheat pokemongocoin pokemontcg poker chip poker machine poker money poker online poker strateg poker without poker_ poker-chip poker-machine poker-money poker-online poker-strateg poker-without pokera pokerchip pokermachine pokermoney pokeronline pokerstrateg poketcg pokie machine pokie online pokie-machine pokie-online pokiemachine pokieonline pokies online pokies-online pokiesonline pokornému polnocno pólnocno północno polo hollis polo out polo ralph polo shoe polo_ polo-hollis polo-lacoste polo-ralph polo-shoe polohollis poloralph polorozed polos ralph polos-ralph poloshoe polosralph pool dating pool-dating popular brand popular-brand popularna popularną markę porn angel porn beer porn big porn comic porn dairy porn diary porn ebony porn eye porn free porn galler porn girl porn gratuit porn hot porn hub porn lesb porn live porn lohan porn mobile porn model porn movie porn photo porn pic porn porn porn post porn pour porn prev porn search porn serch porn sex porn stream porn tickl porn tube porn vergin porn vid porn virgin porn_ porn- porn-angel porn-beer porn-big porn-dairy porn-diary porn-ebony porn-eye porn-free porn-galler porn-girl porn-hot porn-hub porn-lesb porn-lohan porn-mobile porn-model porn-movie porn-photo porn-porn porn-post porn-pour porn-prev porn-search porn-tickl porn-vergin porn-virgin porn. porn.dairy porn.hot porn.model porn.movie porn.vergin porn.virgin porn@ pornangel pornbeer pornbig porncomic porndairy porndiary pornebony porneye porngaller porngirl porngratuit pornhot pornhub pornlesb pornlive pornlohan pornmobile pornmodel pornmovie porno comic porno girl porno gratuit porno lady porno live porno pic porno pour porno sex porno stream porno tube porno vid porno_ porno- porno-girl porno-pour porno. porno@ pornocomic pornogirl pornogratuit pornolady pornolive pornopic pornos_ pornos- pornos. pornos@ pornosex pornotube pornovid pornphoto pornpic pornporn pornpost pornsearch pornsex pornstream porntube pornvergin pornvid pornvirgin positively helpful positively-helpful possess post extreme post post post_ post-extreme post-post post-service post: post! post.best post.much post.pw post.really post.thank postcarf posting comment posting-comment posting! posts manually posts post posts_ posts-manually posts-post posts: posts! posts.asp posts.much posts.pw posts.really posts.thank posttestimonial potngsi powder bene powder review powder-bene powder-review powderbene power level power-level powerball powerbank powerful scrape powerful-scrape powerfulscrape powerlevel powfleul pozew. poznajseo poznan pozycjonowanie pozyczka pozyczki pp-class ppc affiliate ppc compan ppc program ppc-affiliate ppc-compan ppc-program ppv camp ppv click ppv cpa ppv traff ppv-camp ppv-click ppv-cpa ppv-traff ppvcamp ppvclick ppvcpa ppvtraff prada bag prada cell prada cheap prada clutch prada dany prada design prada dress prada girl prada hand prada new prada occhiali prada online prada out prada sac prada sport prada tshirt prada uomo prada vest prada_ prada-bag prada-cell prada-cheap prada-clutch prada-dany prada-design prada-dress prada-girl prada-hand prada-new prada-occhiali prada-online prada-out prada-sac prada-sport prada-tshirt prada-uomo prada-vest pradabag pradacell pradacheap pradaclutch pradadany pradadesign pradadress pradagirl pradahand pradanew pradaocchiali pradaonline pradaout pradasac pradasport pradauomo pradavest prawnik prazosin prednisone preference-0 preference-1 preference-2 preference-3 preference-4 preference-5 preference-6 preference-7 preference-8 preference-9 preferences-0 preferences-1 preferences-2 preferences-3 preferences-4 preferences-5 preferences-6 preferences-7 preferences-8 preferences-9 pregnancysymptom pregnantwere premarin premature ejac premature-ejac prematureejac premium cig premium dignity premium key premium out premium-account premium-cig premium-dignity premium-key premium-out premiumcig premiumdignity premiumkey premiumout prentice capital prentice-capital prenticecapital prepaid credit prepaid-credit prepaidcredit preparation wise preparation-wise preparationwise prescript.asp prescript.cfm prescript.htm prescript.jsp prescript.php prescription acne prescription-acne prescription.asp prescription.cfm prescription.htm prescription.jsp prescription.php prescriptionacne presentation however presentation subsequent presentation-however presentation-subsequent presently fascinate presently-fascinate preserveness pretty worth pretty-worth prettyworth previcox prezzi bors prezzi giub prezzi-bors prezzi-giub prezzibors prezzigiub price replica price-of-gold price-replica price-to-book pricereplica pricetobook priligy primary-change primeessay principal longchamp principal-longchamp principallongchamp privat ftp privat label privat-ftp privat-label privat-proxy private ftp private_ private-ftp private-label private-proxy privateftp privatelabel privateproxy privatftp privatlabel privatnyye proksi privatnyye-proksi privatproxy privilege card privilege-card privilegecard prix chaus prix ugg prix-chaus prix-ugg prixchaus prixugg pro key pro medical pro pip pro review pro-medical pro-pip pro-review proactol probes-aloka probesaloka problemowych procedures-for proceesing produce article produce-article producer excellent producer-excellent product product hermes product_ product-hermes product-sale product/product producthermes produit hermes produit_ produit-hermes produithermes produkc produkcja profesjonal professianal professional 2005 professional 2006 professional 2007 professional 2008 professional 2009 professional 2010 professional 2011 professional 2012 professional 2013 professional 2014 professional 2015 professional ugg professional-2005 professional-2006 professional-2007 professional-2008 professional-2009 professional-ugg professional2005 professional2006 professional2007 professional2008 professional2009 professional2010 professional2011 professional2012 professional2013 professional2014 professional2015 professionals.co professionalugg profil_ profil/profil profile_ profile? profile/? profile/blog profile/profil profiles/blog profiles/profil profils/profil profissionais profit pro profit review profit_ profit-margin profit-pro profit-review profit-seek profitpro profitreview profits- profitseek program ppc program-ppc prohormone proisxozhdenie proizvodstvo project-earn project-hemp projecthemp prokey prokeyshop prokuror chi prokuror či prokuror-chi prokuror-či prom-dress promo art promo artist promo bag promo shop promo store promo sys promo team promo-art promo-artist promo-bag promo-shop promo-store promo-sys promo-team promo+ promoart promobag promocja promocode promos code promos-code promoshop promostore promosys promote campaign promote-campaign promotecampaign promotion bag promotion shop promotion store promotion team promotion-bag promotion-shop promotion-store promotion-team promotional bag promotional shop promotional store promotional-bag promotional-shop promotional-store promotionalbag promotionalshop promotionalstore promotionbag promotionshop promotionstore propecia proper-ugg property pro property-pro propertypro properugg propip propranolol prostitutki protandim protein bene protein powder protein review protein-bene protein-diet protein-powder protein-review proteinbene proteindiet provewhether provigil proxénétisme prozac- prywatne przepisane przeprowadzki przysiegly psych-clinic psychclinic public fuck public sex public-fuck public-sex publica foto publica-foto publicafoto publicfuck publicsex publik acja publik foto publik seen publik-acja publik-foto publik-seen publikacja publikfoto pucci dress pucci-dress pucci-out puccidress pucciout puercash pufy. pullover-t-shirt pullover-tshirt pullovertshirt puma deutsch puma ferrari puma out puma paidat puma schuh puma sneak puma sverige puma-deutsch puma-drifter puma-ferrari puma-finland puma-lauf puma-nice puma-out puma-paidat puma-schuh puma-sneak puma-sverige pumadeutsch pumadrifter pumaferrari pumafinland pumalauf pumanice pumaout pumapaidat pumaschuh pumasneak pumasverige purchase generic purchase tiffany purchase-generic purchase-tiffany purchasegeneric pure-jobs purple longchamp purple-longchamp purplelongchamp purse cheap purse forum purse online purse out purse-cheap purse-forum purse-out purse-sale pursecheap purseout purses cheap purses online purses out purses-cheap purses-out purses-sale pursescheap pursesout pussy porn pussy wet pussy-porn pussy-wet pussyporn pussywet puzzle maker puzzle-maker puzzlemaker pu貌 pytanie pρ pо q http q.@ qampuz qhttp qqq qquality qry_ qsymia india qsymia_ qsymia-india qtrade quality article quality content quality post quality-article quality-content quality-post quanto cost quanto-cost quantocost quartz-seiko quartzseiko queen chiffon queen clutch queen out queen-chiffon queen-clutch queen-out queenchiffon queenclutch queenjp queenout quelques autre quelques outre quelques-autre quelques-outre querireda quick fast quick loans quick_ quick-fast quick-loan quick|fast quickfast quickloan quincy femme quincy-femme quincyfemme quinoa stomach quinoa-stomach quit-smok quite photo quite-photo qux qvc r http r?f?rence r.@ r.all r.for r.the r.two r4i-gold r4igold radikal.ru radiocarpea raiders hat raiders-hat raidershat raloxifene ralph-lauren ramipril rank increase rank-build rank-increase rankbuild rankincrease ranking: rapid-pay rapidpay rasalinga rastreadores rather enlightening rather-enlightening ravensfan ray_ban ray-ban-aviator ray-ban-fold ray-bans ray+ban rayban aviat rayban cheap rayban glass rayban groben rayban gunstig rayban lage rayban lune rayban out rayban pascher rayban polar rayban schwar rayban shop rayban sunglass rayban tokyo rayban uk rayban_ rayban-aviat rayban-cheap rayban-glass rayban-groben rayban-gunstig rayban-lage rayban-lune rayban-out rayban-pascher rayban-polar rayban-schwar rayban-shop rayban-sunglass rayban-tokyo rayban-uk raybanaviat raybanerd raybaneye raybanglass raybangroben raybangunstig raybanlage raybanlune raybanout raybanpascher raybanpolar raybanrb raybans lune raybans uk raybans_ raybans-lune raybans-uk raybanschwar raybanshop raybanslune raybansuk raybansunglass raybantokyo raybanuk razadyne read everthing read smaller read-everthing read-smaller readers-base readers' base readers’ base reading taste reading-taste readyto real xxx real-estate-web real-estate.web real-xxx realestate.co realestate.web realestate.wordpress really seldom reallywork.we realtor promo realtor-promo realxxx reasons-why rebecca jap rebecca jp rebecca uk rebecca-jap rebecca-jp rebecca-uk rebeccajap rebeccajp rebeccauk rebecka charger rebecka-charger reborn article reborn blog reborn page reborn post reborn site reborn weblog reborn website reborn-article reborn-blog reborn-page reborn-post reborn-site reborn-weblog reborn-website reccomend this reccomend-this receive carried receive-carried recent seo recent-seo recentseo reciclable recipe paleo recipe-paleo recipepaleo recommend internet recommend-internet recommended internet recommended-internet records.net recovery-now recoverynow red christ red ugg red-bottom-shoe red-christ red-ugg red+bottom+shoe redbottomshoe redchrist redirect_ redirect.asp redskinsjers redsoleshoe reductil redugg redwingjp reebok baseball reebok ital reebok scarpe reebok-baseball reebok-ital reebok-scarpe reebok-zig reebokital reebokscarpe reebokzig reeview referencement référencement refire cert refire-cert refluks reflux symptom reflux-symptom refluxsymptom reg_ regarding blog regarding thiss regarding-thiss regarfing register cash register earn register paid register-cash register-earn register-paid registration cash registration earn registration paid registration strateg registration-cash registration-earn registration-paid registration-strateg registrator registry-clean registry-fix registry-repair registry-tool registry+ registryclean registryfix registryrepair registrytool regualar reguliatory reirect rejersey.co rejersey.net rekla. reklamowe rekreacja relax private relax-private relaxation therefore relaxation-therefore reliable-rx reliablerx relief secret relief-secret reliefsecret religion jeans religion-jeans religionjeans rellay relogioreplica relogios replica relogios réplica relogios-replica relogios-réplica relogiosreplica remarkable article remarkable blog remarkable info remarkable page remarkable paragraph remarkable post remarkable practice remarkable site remarkable understand remarkable weblog remarkable website remarkable-article remarkable-blog remarkable-info remarkable-page remarkable-paragraph remarkable-post remarkable-practice remarkable-site remarkable-understand remarkable-weblog remarkable-website remarkablearticle remarkableblog remarkableinfo remarkablepage remarkableparagraph remarkablepost remarkablepractice remarkablesite remarkableunderstand remarkableweblog remarkablewebsite remember must remember-must remont avtomat remont vorot remont-avtomat remont-vorot rent_in rent-car rentinsur reockn repeated galdi repeated-galdi replica bag replica birk replica cartier replica chanel replica china replica chinese replica femme replica hand replica herve replica homme replica ip replica jack replica jers replica kors replica leger replica louis replica michael replica nba replica oakley replica ray replica relogio réplica relogio replica rolex replica service replica top replica ugg replica watch replica_ replica-bag replica-brand replica-cartier replica-chanel replica-china replica-chinese replica-de- réplica-de- replica-design replica-femme replica-gucci replica-hand replica-herve replica-homme replica-ip replica-jack replica-jers replica-kors replica-leger replica-louis replica-michael replica-nba replica-oakley replica-prada replica-ray replica-relogio réplica-relogio replica-rolex replica-service replica-store replica-top replica-ugg replica-world replica< replica7 replica8 replicabag replicabrand replicacartier replicachanel replicachina replicachinese replicade replicadesign replicafemme replicagucci replicahand replicaherve replicahomme replicaip replicajack replicajers replicakors replicaleger replicalouis replicamichael replicanba replicaoakley replicaprad replicaray replicarelogio replicarolex replicas relogio réplicas relogio replicas_ replicas-de- réplicas-de- replicas-relogio réplicas-relogio replicasde replicaservice replicasrelogio replicastore replicatop replicaugg replicawatch replicaworld replique chanel replique femme replique homme replique-chanel replique-femme replique-homme repliquechanel repliquefemme repliquehomme reports.asp reports.cfm reports.htm reports.jsp reports.php rescator reseaux sociaux réseaux sociaux reseaux-sociaux réseaux-sociaux resist comment resist-comment resources/styles restoremen restoril resultat pmu résultat pmu resultat-pmu résultat-pmu results.htm retail-store retailstore retin-a retro jord retro-jord retrojord reverse lookup reverse-lookup reverse-phone reverselookup reversephone revia med revia-med review best review-best review-on review-source review.asp review.bl review.cfm review.htm review.in review.jsp review.php reviewbest reviewon reviews best reviews-best reviews-on reviews.asp reviews.bl reviews.cfm reviews.htm reviews.in reviews.jsp reviews.net reviews.org reviews.php reviewsbest reviewson reviewsource reviewstv reviewthe reviewx reviot revival beauty revival-beauty revivalbeauty revive beauty revive-beauty revivebeauty revolutionjog revolutionstroll rheumatoidarthritis rhttp rice-cooker ricecooker rich woolrich rich-woolrich richwoolrich riga stag riga-stag riga.stag rigastag right blog right-blog rightblog rilopkais.in rin.in riot points riot-points riotpoints ripoffreport rise-hire risehire rizatriptan robaxin robe-de-mariee robe-du-mariage robedemariee robedumariage robertby roger vivier roger-vivier rogervivier rok.ru rok.su rokettube roleplay rolex prix rolex_ rolex-watch rolexwatch roofingcontractor ropa belstaff ropa interior ropa-belstaff ropa-interior ropabelstaff ropainterior rose-shoe roseshoe rosettastoneeasy roshe run roshe-run rosherun rosuvastatin rotating-hot-iron rotatinghotiron rouge moncler rouge-moncler rougemoncler roxy ugg roxy-ugg roxyugg royal-club royalclub róże rozszerzona rpg online rpg-online rpgonline rss.asp rss.cfm rss.htm rss.jsp rss.php ru-blacklist rublacklist rug fur rug-fur rugfur rugs fur rugs-fur rugsfur ruhttp run chaus run cpa run obuv run ppv run shoe run sneak run-chaus run-cpa run-obuv run-ppv run-shoe run-sneak runchaus runcpa runescape gold runescape million runescape-gold runescape-million runescapegold running obuv running sneak running-obuv running-sneak runningobuv runningsneak runobuv runppv runshoe runsneak runway dress runway sita runway-dress runway-sita runwaydress runwaysita russian mis russian mr russian ms russian mulberry russian-mis russian-mr russian-ms russian-mulberry russianmis russianmr russianms russianmulberry rusztowania rx-pharm rxpharm rе rі rу ɍa ɍe ɍi ɍn ɍo ɍu ɍy s http s.@ s.all s.for s.the s.two s.webeden sabo charm sabo out sabo ring sabo sale sabo shop sabo uk sabo-charm sabo-out sabo-ring sabo-sale sabo-shop sabo-uk sabo-us sabocharm saboout saboring sabosale saboshop sabouk sac celine sac chanel sac chloe sac gucci sac guess sac hermes sac lancel sac longchamp sac louis sac reutilis sac réutilis sac sold sac trail sac vanessa sac vuitton sac-a-main sac-celine sac-chanel sac-chloe sac-gucci sac-guess sac-hermes sac-lancel sac-longchamp sac-louis sac-reutilis sac-réutilisable sac-sold sac-trail sac-vanessa sac-vuitton sacceline sacchanel sacchloe sacgucci sacguess sachermes saclancel saclongchamp saclouis sacreutilis sacréutilisable sacs celine sacs chloe sacs fr sacs hermes sacs lancel sacs longchamp sacs louis sacs trail sacs-celine sacs-chloe sacs-fr sacs-hermes sacs-lancel sacs-longchamp sacs-louis sacs-trail sacs-vanessa sacs-vuitton sacsceline sacschloe sacsfr sacsguess sacshermes sacslancel sacslongchamp sacslouis sacsold sacstrail sacsvanessa sacsvuitton sactrail sacvanessa sacvuitton sadehap saidnew saintoffice saints jers saints-jers saintsjers salaire instant salaire-instant salaireinstant saldi online saldi-footwear saldi-online saldifootwear saldionline sale bestsell sale cosmetic sale louis sale lulu sale miami sale nederland sale oakley sale online sale template sale-4u sale-bestsell sale-cheap sale-cosmetic sale-factor sale-for-sale sale-for-whole sale-jap sale-jp sale-longchamp sale-louis sale-lulu sale-miami sale-nederland sale-oakley sale-online sale-template sale-tokyo sale-train sale.co. sale.weebly sale+ sale= sale4u salecanad salecheap salecosmetic salefactor saleforsale saleforwhole salejp salelouis salelulu salemiami saleoakley saleonline saleout sales-class sales-factor sales-inspir sales-train sales.co. sales+ salesclass salesfactor saleshop salesshop salestrain saletemplate saletokyo saletrain saleu.co salomon athletic salomon canada salomon run salomon_ salomon-athletic salomon-canada salomon-fr salomon-run salomon-speedcross salomon.asp salomon.cfm salomon.htm salomon.jsp salomon.php salomonathletic salomoncanada salomonfr salomonrun salomonspeedcross salvatore outlet salvatore-outlet salvatoreoutlet salvia. sameday-loan samedayloan sample@ samurai siege samurai-siege sandaljp sandalsjp sanders jers sanders-jers sandersjers sandypasch santehnik krug santehnik-krug satchelbag satcheldbag sauvegarde extern sauvegarde-extern save-you-time sayt vizitk sayt-vizitk sɑ sbobet scam store scam-store scam.asp scam.cfm scam.htm scam.in scam.jsp scam.net scam.org scam.php scar repair scar-repair scarpe air scarpe hogan scarpe italia scarpe mbt scarpe nike scarpe prada scarpe roger scarpe supra scarpe_ scarpe-air scarpe-basket scarpe-hogan scarpe-italia scarpe-mbt scarpe-nike scarpe-prada scarpe-roger scarpe-supra scarpebasket scarpehogan scarpeitalia scarpenike scarpeprada scarperoger scarrepair schlgsseldienste schlüsseldienste schoene seite schoene-seite schoenen dame schoenen dsqu schoenen seite schoenen-dame schoenen-dsqu schoenen-seite schoenendame schoenendsqu schoenenseite schoeneseite school.a schuh gunstig schuh günstig schuh puma schuh sky schuh ysl schuh_ schuh-gunstig schuh-günstig schuh-puma schuh-sky schuh-ysl schuhe gunstig schuhe günstig schuhe puma schuhe sky schuhe ysl schuhe_ schuhe-gunstig schuhe-günstig schuhe-puma schuhe-sky schuhe-ysl schuhegunstig schuhegünstig schuhepuma schuhesky schuheysl schuhgunstig schuhgünstig schuhpuma schuhsky schuhysl schweiz online schweiz-online schweizonline sciatica pain scisser scontati scoriescod scott wing scott-wing scottwing scrapebox scraper free scraper-free scraperfree sd3546a seaddons seahawks jers seahawks-jers seahawksjers search gogle search optim search porn search-engine-opt search-gogle search-optim search-porn searchengine-opt searchengine. searchengineopt searchoptim searchporn seawaypab secondgrade secret advant secret advert secret beautiful secret generous secret review secret-advant secret-advert secret-beautiful secret-generous secret-review secret.co secretadvant secretadvert secretbeautiful secretgenerous secretreview secured result secured-result security-for seek man seek men seek wom seek-man seek-men seek-wom seeking man seeking men seeking wom seeking-man seeking-men seeking-wom seekingman seekingmen seekingwom seekman seekmen seekwom seeming vexation seeming-vexation seg-board segboard segway-fun segwayfun seikomise seks.ro seks.ru seks.su seksual self google self-google selfgoogle sell dump sell iphone4 sell iphone5 sell iphone6 sell lancel sell-dump sell-iphone4 sell-iphone5 sell-iphone6 sell-lancel sell-now selling iphone4 selling iphone5 selling iphone6 selling-iphone4 selling-iphone5 selling-iphone6 selllancel sellnow send earn send-earn sendflowers sensual massage sensual-massage sentence word sentence-word sentient-health sentienthealth senuke vps senuke-vps senukevps seo add seo barn seo comp seo gain seo gig seo host seo luton seo pick seo plug seo rank seo soluton seo source seo tool seo vps seo widget seo wise seo with seo_ seo- seo-add seo-barn seo-comp seo-gain seo-host seo-luton seo-pick seo-plug seo-rank seo-soluton seo-source seo-tool seo-vps seo-widget seo-wise seo-with seo, seo. seoadd seoagenc seobarn seocomp seogain seohost seoluton seopick seoplug seorank seosoluton seosource seotool seovps seowidget seowise seowith sepid-shimi sepidshimi serch engin serch-engin serial numberul serial-numberul series erotic series-erotic serieserotic seriously entice seriously-entice serravalle out serravalle-out serravalleout serrurier marseille serrurier paris serrurier-marseille serrurier-paris sertraline servantappear server 2005 server 2006 server 2007 server 2008 server 2009 server 2010 server 2011 server 2012 server 2013 server 2014 server 2015 server-2005 server-2006 server-2007 server-2008 server-2009 server-2010 server-2011 server-2012 server-2013 server-2014 server-2015 server2005 server2006 server2007 server2008 server2009 server2010 server2011 server2012 server2013 server2014 server2015 servicedoc services/services servicii profes servicii-profes serwis sessuale sessuali set/bv set/celine set/chanel set/rolex settings1 settings2 settings3 settlement cash settlement-cash several opportune several-opportune sex advice sex blog sex cam sex club sex dat sex game sex gratis sex hook sex hub sex movie sex mp sex pc sex porn sex scan sex search sex sex sex shop sex tape sex teen sex toy sex tube sex video sex virgin sex web sex xxx sex-advice sex-blog sex-cam sex-club sex-dat sex-game sex-gratis sex-hook sex-hub sex-movie sex-mp sex-pc sex-porn sex-scan sex-search sex-sex sex-shop sex-tape sex-teen sex-toy sex-tube sex-video sex-virgin sex-web sex-xxx sex.asp sex.cfm sex.htm sex.jsp sex.php sex.porn sexadvice sexblog sexcam sexclub sexdat sexelist sexero sexgame sexgratis sexhook sexhub sexmovie sexmp sexo mon sexo-mon sexomon sexpc sexporn sexscan sexsearch sexsex sexshop sextape sexteen sextoy sextube sexual fantas sexual moment sexual-fantas sexual-moment sexualfantas sexualmoment sexvideo sexvirgin sexweb sexxx sexy fantas sexy girl sexy moment sexy sex sexy teen sexy virgin sexy web sexy woman sexy women sexy-fantas sexy-girl sexy-moment sexy-sex sexy-teen sexy-virgin sexy-web sexy-woman sexy-women sexy.asp sexy.cfm sexy.co sexy.girl sexy.htm sexy.jsp sexy.php sexyfantas sexygirl sexymoment sexysex sexyteen sexyvirgin sexyweb sexywoman sexywomen seznamce sgames.co shanghai date shanghai escort shanghai massage shanghai-date shanghai-escort shanghai-massage shanghaidate shanghaiescort shanghaimassage shanrig share site share ssite share-site share-ssite shared site shared ssite shared-site shared-ssite sharedsite sharedssite shares site shares-site sharesite sharessite sharing site sharing-site sharingsite sharply-wrong shate thou shate-thou shed fat shed pound shed-fat shed-pound shedfat shedpound shemale shift dress shift-dress shiftdress shirt cease shirt cheap shirt custom shirt embroid shirt online shirt print shirt-cease shirt-cheap shirt-custom shirt-embroid shirt-online shirt-print shirtandshirt shirtandtshirt shirtcease shirtcheap shirtcustom shirtembroid shirtonline shirtprint shirts cheap shirts custom shirts embroid shirts online shirts print shirts-cheap shirts-custom shirts-embroid shirts-online shirts-print shirtsandshirt shirtsandtshirt shirtscheap shirtscustom shirtsembroid shirtsonline shirtsprint shoe america shoe announce shoe cloth shoe dior shoe flat shoe jap shoe jp shoe man shoe mbt shoe men shoe mizuno shoe online shoe out shoe promo shoe sale shoe sol shoe uk shoe woman shoe women shoe_ shoe-america shoe-cloth shoe-dior shoe-flat shoe-jap shoe-jp shoe-man shoe-mbt shoe-men shoe-mizuno shoe-online shoe-out shoe-promo shoe-sale shoe-sol shoe-uk shoe-woman shoe-women shoe.asp shoe.cfm shoe.htm shoe.jsp shoe.mobi shoe.name shoe.php shoe+ shoeamerica shoecloth shoedior shoeflat shoejap shoejp shoembt shoemizuno shoeonline shoeout shoepromo shoes 2013 shoes 2014 shoes 2015 shoes america shoes announce shoes dior shoes jap shoes jp shoes man shoes mbt shoes men shoes online shoes out shoes uk shoes woman shoes women shoes_ shoes-2013 shoes-2014 shoes-2015 shoes-america shoes-cheap shoes-cloth shoes-dior shoes-jap shoes-jp shoes-man shoes-mbt shoes-men shoes-on-sale shoes-online shoes-out shoes-uk shoes-woman shoes-women shoes.asp shoes.mobi shoes.name shoes+ shoes2013 shoes2014 shoes2015 shoesale shoesamerica shoescloth shoesjap shoesjp shoeskan shoesmart shoesmbt shoesol shoesonline shoesonsale shoesout shoessale shoestore shoesuk shoesus shoeuk shoot-tequila shop boot shop bought shop coupon shop erotic shop jap shop jp shop makeup shop online shop thai shop_ shop-afl shop-boot shop-bought shop-coupon shop-erotic shop-jap shop-jp shop-makeup shop-nfl shop-now shop-online shop-pin shop-shoe shop-thai shop-to-you shop.asia shopboot shopcoupon shopent.ru shopent.su shoperotic shopg2bag shopjap shopjp shopmakeup shopnfl shopnow shopof shoponline shoppe_ shopper_ shoppers_ shoppes_ shopping getaway shopping harmony shopping online shopping site shopping ugg shopping_ shopping-getaway shopping-harmony shopping-online shopping-site shopping-ugg shopping+ shopping24 shoppingcenter.co shoppingharmony shoppingonline shoppingsite shoppingugg shops_ shopshoe shopthai shoptoyou shopuk shopus.co short ugg short-ugg shorttermloan shortugg shoulder tote shoulder-tote shouldertote show_ show-news show-topic shownews showroom gucci showroom-gucci showroomgucci showtopic shox turbo shox-turbo shoxturbo shred hd shred-hd shredhd shrwed shttp sɦ si.lv.e.r.w.are si've si’ve sia ottimo sia-ottimo sibutramine sieg heil sieg-heil siege hack siege-hack siegheil sigarette sign zodiac sign-zodiac signa zodiac signa-zodiac signazodiac signed-jers signifiant significant infos significant-infos significantly post significantly-post signin widget signin_password signin-widget signinwidget signo zodiac signo-zodiac signout widget signout-widget signoutwidget signozodiac signzodiac silagra silberbarren sildenafil silver-and-gold silver-gold silver-ingot silver-jewel silver-suite silverandgold silvergold silveringot silverjewel silversuite sim-only simonly simplexml you simplexml-you simply extremely simply shared simply-extremely simply-shared sincere understand sincere-understand sinequan singapore.asp singapore.cfm singapore.htm singapore.jsp singapore.php sirop mangustin sirop-mangustin sistershit site asian site backup site link site offer site official site officiel site owner site platform site position site post site provid site theme site traff site ufficial site-asian site-backup site-google site-link site-offer site-official site-officiel site-owner site-platform site-position site-post site-provid site-theme site-traff site-ufficial site:- site:) site! site.co site.in site24 sitecode sitelink sitemap0 sitemap1 sitemap2 sitemap3 sitemap4 sitemap5 sitemap6 sitemap7 sitemap8 sitemap9 siteofficial siteofficiel sites24 siteufficial sito official sito officiel sito ufficial sito ugg sito-official sito-officiel sito-ufficial sito-ugg sitoofficial sitoofficiel sitoufficial sitougg situs poker situs-poker siutpd size-genetic sizegenetic sk8 hi sk8-hi sk8hi skapa grupp skapa-grupp skapagrupp skelaxin skhemy skidki bilet skidki na skidki otel skidki tur skidki-bilet skidki-na skidki-otel skidki-tur skilled blog skilled-blog skilledblog skin pigment skin_pigment skin-care-review skin-pigment skincare-review skincare-work skincarereview skincarework skinnys review skinnys-review skinnysreview skins bet skins-bet skip-trac skjonnhetsprodukter skjønnhetsprodukter sklep sklepy skor online skor timber skor-online skor-rea skor-timber skoronline skorrea skortimber skup-aut skup,aut sky pharmacy sky-pharmacy skypharmacy sledge baseball sledge bat sledge-baseball sledge-bat slimpill slimup slipper khaki slipper-khaki slipperkhaki slippers khaki slippers-khaki slipperskhaki slongchamp slot machine slot-machine slotmachine slugi elektrek slugi otdeloch slugi plotnik slugi santehnik slugi stekol slugi-elektrek slugi-otdeloch slugi-plotnik slugi-santehnik slugi-stekol slugy elektrek slugy otdeloch slugy plotnik slugy santehnik slugy stekol slugy-elektrek slugy-otdeloch slugy-plotnik slugy-santehnik slugy-stekol slut porn slut-porn slutporn sluts porn sluts-porn slutsporn slvrenew smallbusinessplan smaller article smaller content smaller post smaller-article smaller-content smaller-post smart hoverboard smart-balance smart-drug smart-hoverboard smartbalance smartdrug smarthoverboard smeoone smith sold smith-sold smithsold smokingnews sms grupp sms-grupp smsgrupp snapback-cap snapbacks snatch your sneaker trade sneaker-trade sneaker.asp sneaker+ sneakerchef sneakerjap sneakerjp sneakers sneakers retail sneakers sale sneakers trade sneakers-retail sneakers-sale sneakers-trade sneakers.asp sneakers24 sneakersretail sneakerssale sneakerstrade sneakertrade snism snore mouth snore stop snore-mouth snore-stop snoremouth snorestop snoring expert snoring mouth snoring-expert snoring-mouth snoringexpert snoringmouth social butler social marketing social-bookmark social-butler social-marketing social-media-manage socialbookmark socialbutler socialengine socialite. socialmarketing societys soeasy sofosbuvir soft_ soft-secret softsecret software house software secret software_ software-house software-secret software.in software.pl software.ro software.ru software.su software.za softwarehouse softwaresecret sohbet sold-out solde botte solde canad solde lancel solde loubou solde ralph solde_ solde-botte solde-canad solde-lancel solde-loubou solde-ralph solde.co solde.in solde.pl solde.ro solde.ru solde.su solde.za solde< soldebotte soldecanad soldelancel soldeloubou solderalph soldes botte soldes canad soldes lancel soldes loubou soldes ralph soldes_ soldes-botte soldes-canad soldes-lancel soldes-loubou soldes-ralph soldes.co soldes.in soldes.pl soldes.ro soldes.ru soldes.su soldes.za soldes< soldesbotte soldescanad soldeslancel soldesloubou soldesralph soleil chanel soleil ray soleil-chanel soleil-ray soleilchanel soleilray solução solutioninc solutionsinc some functionalitie some-functionalitie somedias son copain son-copain soncopain songs reaches songs thousand songs-reaches songs-thousand sonicsearch sooemne soon:- soon:) sovaldi sozedde.co sozedde.in sozedde.pl sozedde.ro sozedde.ru sozedde.za sp1 key sp1-key spaccio gucci spaccio woolrich spaccio-gucci spaccio-woolrich spacciogucci spacciowoolrich spade diaper spade out spade-diaper spade-out spadediaper spadeout spain jers spain-jers spam link spam respon spam-link spam-respon spamlink spammer link spammer spam spammer-link spammer-spam spammerlink spamrespon sparkle ugg sparkle-ugg sparkleugg special-offer specialty-transfer specialtytransfer specific gift specific-gift spedified speed-loan speedloan speedy-product spilivanie derev'yev spilivanie derev’yev spilivanie derevev spilivanie-derevev spip.asp spip.cfm spip.htm spip.jsp spip.php splendid depart splendid tiffany splendid-depart splendid-tiffany splendiddepart splendidtiffany splitting announce splitting-announce sponsoring secret sponsoring-secret sponsoringsecret sport tronch sport-tronch sportbikepart sportbook sportivo_ sportsbet sportsbook sportsfanshop sportsfanstore sportsgear sportsjers sporttronch spot poker spot-poker spotpoker spravochnik sprawdzone narzedzia sprawdzone narzędzia sprawdzone-narzedzia sprawdzone-narzędzia sprawdzonenarzedzia sprawdzonenarzędzia sprinkler tune sprinkler-tune sprinklertune sprzataniu powier sprzataniu-powier sprzedaz spy software spy-software spyder jack spyder ski spyder-jack spyder-ski spyderjack spyderski spysoftware spyware squidoo.co ssory ssylka sta'sean sta’sean stag party stag weekend stag-party stag-weekend stag.weekend stagparty stagweekend standing website standing-website standingof star sunglass star-item star-sunglass starsunglass state-of-the-art statement state statement-state statuscode stazhirovka steelersfan stendra stevewynnloan stewart furniture stewart patio stewart-furniture stewart-patio stiefel damen stiefel schweiz stiefel-damen stiefel-schweiz stiefeldamen stiefelschweiz stig hollis stig online stig-hollis stig-online stighollis stigonline stimate stimulacion sex stimulacion-sex stimulacionsex stimulation sex stimulation-sex stimulationsex stivale timber stivale-timber stivaletimber stivali pioggia stivali timber stivali-pioggia stivali-timber stivalipioggia stivalitimber stobuys stock screener stock-screener stock-ticker stockscreener stockticker stodio beat stodio-beat stodiobeat stodios beat stodios-beat stodiosbeat stone jap stone jp stone-jap stone-jp stonejap stonejp stop smok stop snor stop-addict stop-smok stop-snor stopsmok stopsnor stor punkt stor-punkt store austr store caldle store gucci store online store out store penis store purchased store shopping store wholes store-caldle store-gucci store-online store-out store-penis store-purchased store-service store-shopping store-wholes store.bl store.htm store.org storegucci storejp storeonline storeout storepenis stores.bl stories funny stories-funny storiesfunny storm hurricane storm-hurricane storre penis storre-penis storrepenis story funny story-funny storyfunny stosunek stoxymom straightface strapless tiered strapless-tiered straplesstiered strategies-for strategii binarny strategii-binarny strategy-for stratificat. strattera stream2watch street-saw streetsaw strength harmony strength-harmony strick jack strick-jack strickjack stride jack stride-jack stridejack stromectol strona blu strona-autora strona-blu strona.blu stronaautora strong populat strong very strong-populat strong-very studio beat studio-beat studiobeat studios beat studios-beat studiosbeat studycon studylook stumbledupon stumpmaster stunningq style mbt style-mbt style:- style:) styledkitchen stylembt styleshq stylevip stylo montblanc stylo-montblanc stylomontblanc stylowe subject_ subject= submissive porn submissive-porn submissive.porn submissiveporn submit web submit-web submitweb subscribe link subscribe_ subscribe-link subscribelink subscription link subscription_ subscription-link subscriptionlink subsequent publish subsequent-publish substance deal substance-deal substancedeal succeed online succeed-online succeedonline success article success blog success online success page success post success site success website success you success-article success-blog success-online success-page success-post success-site success-website success-you successarticle successblog successful article successful blog successful page successful post successful site successful website successful-article successful-blog successful-page successful-post successful-site successful-website successfularticle successfulblog successfulpage successfulpost successfulsite successfulwebsite successonline successpage successpost successsite successyou sucesso.co sucette such available such-available suchavailable suggested article suggested blog suggested page suggested post suggested site suggested website suggested-article suggested-blog suggested-page suggested-post suggested-site suggested-website suggestedarticle suggestedblog suggestedpage suggestedpost suggestedsite suggestedwebsite sumatriptan summer coach summer-coach sunchannel sunglass cheap sunglass out sunglass-cheap sunglass-out sunglassau sunglasscheap sunglasses cheap sunglasses out sunglasses ray sunglasses-cheap sunglasses-out sunglasses-ray sunglasses.us sunglassesau sunglassescheap sunglassesok sunglassesuk sunglassok sunglassuk sunishne sunrize sunsetthe super compan super real super-compan super-real superarticle superb blog superb data superb-blog superb-data supercompan superdry outlet superdry-outlet superdryoutlet superheroz superior compan superior resource superior-compan superior-resource superiorcompan superstar class superstar status superstar-class superstar-status superstarclass supplement energy supplement-energy supplementenergy supplements energy supplements-energy supplementsenergy suppliment supply_ supra boutique supra online supra schuh supra shoe supra-boutique supra-online supra-schuh supra-shoe supraonline suprashoe suprax supreme-essay supremeessay surf online surf to surf-online surf-to surfing online surfing-online surprisezone suvwithbest suwa³ki svente enligne svente-enligne sventeenligne sverige online sverige-online sverigeonline swarovski swarovski_ swarovskijap swarovskijp sweet blog sweet cum sweet page sweet site sweet-blog sweet-cum sweet-page sweet-site sweetblog sweetpage sweetsite swegway swiss replica swiss-replica swissreplica sword trade sword-trade swords trade swords-trade swordstrade swordtrade syaneru symptom med symptom-med symptommed symptoms med symptoms-med symptomsmed sysadmin system pro system review system web system yoga system_ system-pro system-review system-web system-yoga systemdb systempro systemreview systemweb systemyoga sysuser szambo sϲ sі sо sу sһ sօ t http t_/ t.@ t.all t.e.mptest t.em.ptest t.emp.test t.empt.est t.empte.st t.emptes.t t.emptest t.for t.the t.two t.w.i.t t.w.it t.wi.t t.wit. t.witt. tabak fumar tabak tanzh tabak-fumar tabak-tanzh tabaki tabletki odchudz tabletki-odchudz tabletz taboo matter taboo topic taboo-matter taboo-topic tadalafil tadapox tag/event tags: tai game tai ionline tai online tai-game tai-ionline tai-online taigame taiionline taille cost taille-cost taillecost taionline taken gravely taken-gravely takenwith takin note takin-note takje care takje-care talent manage talent recruit talent-manage talent-recruit talentmanage talentrecruit talknig tamiflu tamoxifen tamsulosin tanger out tanger-out tapicerowane targeted traff targeted visit targeted-traff targeted-visit targetedtraff targetedvisit targetted traff targetted visit targetted-traff targetted-visit targettedtraff targettedvisit tarif-malin tarifmalin tariki shoe tariki-shoe tarikishoe tarot divin tarot-divin tasche longchamp tasche-longchamp taschelongchamp taschen longchamp taschen-longchamp taschenlongchamp tastegood.co tattoo cheap tattoo sneak tattoo tip tattoo_ tattoo-cheap tattoo-sneak tattoo-tip tattoocheap tattoos cheap tattoos sneak tattoos top tattoos_ tattoos-cheap tattoos-sneak tattoos-tip tattooscheap tattoosneak tattoostip tattootip tax-book tax-debt taxbook taxdebt taylormade_ tɑ te.m.ptest te.mp.test te.mpt.est te.mpte.st te.mptes.t te.mptest teach-you-every team shirt team tshirt team-online team-shirt team-tshirt teamonline teamproshop teams-online teamshirt teamsonline teamtshirt teamwork coach teamwork-coach teamworkcoach teen cam teen female teen porn teen sex teen virgin teen webcam teen-cam teen-female teen-porn teen-sex teen-virgin teen-webcam teen.sex teenage female teenage-female teenaged female teenaged-female teencam teeniepant teenporn teens cam teens porn teens webcam teens-cam teens-porn teens-webcam teenscam teensex teensporn teenswebcam teenvirgin teenwebcam tees hollis tees-hollis teeshollis tegs: tele gratuit tele-gratuit telegratuit telepon- tem.p.test tem.pt.est tem.pte.st tem.ptes.t tem.ptest temp-test temp.t.est temp.te.st temp.tes.t temp.test template.co templates.co templerun- templerun1 tempt.e.st tempt.es.t tempt.est tempte.s.t tempte.st temptes.t temptest tenormin term dinner term loan term-dinner term-loan terminaly termopane test post test-post test.ca test.in test.tumblr test1. testosterone-boost testpost testrun testuser teta mega teta-mega tetamega tetas mega tetas-mega tetasmega tetona mega tetona-mega tetonamega tetonas mega tetonas-mega tetonasmega tetracycline texans-jers texansjers textile urbain textile-urbain tgf@ thailand m88 thailand-m88 thailandm88 thailove thanks designed thanks-designed thanks.i that isnt that-isnt thatattempt thatover thatprofit thatrapid thatthe the notify the pokie the_best the-benefits-of the-best-treatment the-flashboard the-hoverboard the-hypothyroid the-latest- the-notify the-pokie theattempt theaverage thebest thedevelop thedirect theeasy theflashboard theguest thehoverboard thehypothyroid theme generat theme sale theme-basic theme-generat theme-sale themebasic themegenerat themes sale themes-for-windows themes-sale themesforwindows there admin there-admin thereadmin therealest thes good these however these-however thespacious thetopdog thetwo theuniversity theyll theywe thhis thing way thing-way things-consume things-know things-we-love thingway this gucci this publish this weblog this webpage this-gucci this-publish this-weblog this-webpage thisgucci thispublish thjng thkuafnl thm.asp thm.cfm thm.htm thm.jsp thm.php thninkig thnkgini thnnik thnx u thnx-u thomas sabot thomas-fat thomas-sabot thomasfat thomassabosale thomassabouk thomassabous thoughtful shop thoughtful-shop thoughtfulshop thoughtl thouhgt thportfol thrity throwback cheap throwback-cheap throwbackcheap thttp thumb.asp thumb.cfm thumb.gif thumb.htm thumb.jpeg thumb.jpg thumb.jsp thumb.php thumb.png thumbs.asp thumbs.cfm thumbs.gif thumbs.htm thumbs.jpeg thumbs.jpg thumbs.jsp thumbs.php thumbs.png thus where thus-where thyromine tɦ tienda barata tienda barato tienda futbol tienda-barata tienda-barato tienda-futbol tiendabarata tiendabarato tiendafutbol tiendas hollis tiendas-de tiendas-hollis tiendasde tiendashollis tier business tier-business tiffany france tiffany gemstone tiffany jewel tiffany ring tiffany-france tiffany-gemstone tiffany-jewel tiffany-out tiffany-ring tiffanygemstone tiffanyjewel tiffanyout tiffanyring tiffanys & tiffanysale tiffiny tihnikng tiki-index till today till-today timberland bambi timberland boot timberland catalo timberland cheap timberland earth timberland giub timberland herr timberland khaki timberland laarzen timberland men timberland out timberland pas timberland saldi timberland schoene timberland shoe timberland slipper timberland swede timberland uom timberland villa timberland women timberland-bambi timberland-boot timberland-catalo timberland-cheap timberland-earth timberland-giub timberland-herr timberland-khaki timberland-laarzen timberland-men timberland-out timberland-pas timberland-saldi timberland-schoene timberland-slipper timberland-swede timberland-uom timberland-villa timberland-women timberland1 timberland2 timberlandbambi timberlandboot timberlandcatalo timberlandcheap timberlandearth timberlandgiub timberlandherr timberlandinneder timberlandkhaki timberlandlaarzen timberlandmen timberlandout timberlandpas timberlandsaldi timberlandschoene timberlandslipper timberlandswede timberlanduom timberlandvilla timberlandwomen time-synchronisation.co times(with timesamsonss timeshare tingenieur tinnitus tin tinnitus-tin tinnitustin tinytowtimmy.co tipblog tips-for- tips-meny tips-to-start tips.info tipsblog tipsof tire-disc tire-wholes tiredisc tires-disc tires-wholes tiresdisc tireswholes tirewholes tit-pad titans merch titans-merch titansmerch tits pic tits-pic titspic tittie pic tittie-pic tittiepic titty pic titty-pic tittypic tizanidine tjrs eu tjrs-eu tms-shoe tmsshoe tnf-sale tnfsale to commenting to daylight to gget to operates to truly to-commenting to-daylight to-gget to-operates to-truly toasterovensnow today/article todescribe tods jap tods jp tods-jap tods-jp todsjap todsjp toes pain toes-pain toeshoe.co toeshoes.co tofranil toile vanessa toile-vanessa toilevanessa tojp.co token generat token hack token-generat token-hack tokengenerat tokenhack tokyo-lv told u? tomber enceinte tomber-enceinte tomberenceinte toms price toms shoe toms women toms-cheap toms-damen toms-price toms-shoe toms-women tomscheap tomsdamen tomsformen tomsforsale tomsforwomen tomsout tomsshoe tomsshoes tomswomen tones way tones-way tongue retain tongue-retain tongueretain too thun too-thun top article top blog top bulgari top bvlgari top new top stop top tier top viral top_bank top-article top-blog top-bulgari top-bvlgari top-forum top-grade top-list top-muscle top-new top-rank top-search top-site top-stop top-tai top-tier top-viral topamax toparticle topblog topbulgari topbvlgari topcontractor topforum topic.really topic.thank topics.really topics.thank topiramate toplist topman123 topmuscle topnew topoic toprank topsearch topseo topsite topstop toptai topvideocon topviral tor puter tor-puter torgovye strategii torgovye-strategii torrent-movie torrentmovie torrents torsemide toryburch1 toryburch2 toryburchflat toryburchten tospeed tosurveu totaldns.in totalizador totally free totally-free tote hand tote-hand totehand town| trace-service tracing-service track-back track-phone-location trackback-url tracking-a-phone trade_your trading-method traffic_ trafficvance trafficz trailer sale trailer-sale trailersale training-online training-pro trainingonline trainingpro traitement-des tramadol transform-vhs trashma1l trashmai1 tratamente corpor tratamente faci tratamente-corpor tratamente-faci travel.pl traveltrip trazodone treatment.in trempe sous trempe-sous trend shop trend-shop trends shop trends-shop trendshop trendsshop trening tresore tretinoin trickphoto trimethoprim triviatrivia trollapp true search truly peaked truly suppose truly thk truly very truly-peaked truly-suppose truly-thk truly-very truthabout try-these tube-zzz tubee.tv tubezzz tucholskie tumi ducati tumi tumi tumi-ducati tumi-tumi tumi1 tumi2 tumiducati tumitumi tummy tuck tummy-tuck tummytuck turbo-vac turbosprezar turystyka tutorial.asp tutorial.cfm tutorial.htm tutorial.jsp tutorial.php tutoringand tvturn tw.i.t tw.itt. twenty sunglass twenty-sunglass twentysunglass twerk fitness twerk video twerk-fitness twerk-video twerk.fitness twerking video twerking-video twerkingvideo twerkvideo twit.t. twitter-hack tworzenie tʏ tе tо tу tү tօ u http u.@ u4nba uauaua ubezpieczenia ubirkin uch:- uch:) ucoz.co ucoz.ru uefa-fifa uefafifa ufficiale moncler ufficiale-moncler ufficialemoncler ufficiales ugboos ugbos ugg 3 ugg ad ugg animal ugg austr ugg bailey ugg bamsestovler ugg bamsestøvler ugg barata ugg barato ugg bebe ugg black ugg boot ugg bota ugg botte ugg brown ugg brux ugg cheap ugg class ugg dakota ugg disc ugg elsey ugg enfant ugg espa ugg femme ugg fox ugg france ugg glove ugg gold ugg gunstig ugg günstig ugg hobo ugg hot ugg italia ugg ken ugg mocha ugg noir ugg out ugg paris ugg pas ugg pascher ugg sale ugg shear ugg shoe ugg short ugg silver ugg slipper ugg sold ugg stovler ugg støvler ugg style ugg three ugg turn ugg wom ugg-3 ugg-ad ugg-animal ugg-bailey ugg-bamsestovler ugg-bamsestøvler ugg-barata ugg-barato ugg-bebe ugg-best ugg-black ugg-boot ugg-bota ugg-botte ugg-brown ugg-brux ugg-cheap ugg-class ugg-comfort ugg-disc ugg-elsey ugg-enfant ugg-espa ugg-femme ugg-for ugg-fox ugg-france ugg-glove ugg-gold ugg-gunstig ugg-günstig ugg-hobo ugg-hot ugg-italia ugg-ken ugg-mocha ugg-noir ugg-official ugg-on ugg-out ugg-paris ugg-pas ugg-pascher ugg-sale ugg-shear ugg-shoe ugg-short ugg-silver ugg-site ugg-slipper ugg-sold ugg-stovler ugg-støvler ugg-style ugg-three ugg-turn ugg-uk ugg-usa ugg-wom ugg.asp ugg.cfm ugg.htm ugg.jsp ugg.php ugg+ ugg= ugg3 uggad ugganimal uggatcanad uggbailey uggbamsestovler uggbamsestøvler uggbarata uggbarato uggbebe uggbest uggblack uggboo uggboot uggbos uggbota uggbotte uggbrown uggbrux uggcheap uggclass uggclog uggcomfort uggdakota uggdisc uggelsey uggenfant uggespa uggfemme uggfor uggfox uggfrance uggglove ugggold ugggunstig ugggünstig ugghobo ugghot uggitalia uggken ugglove uggmocha uggnoir uggofficial uggonline uggout uggparis uggpas uggpascher uggs austr uggs bailey uggs bebe uggs black uggs boot uggs brux uggs class uggs elsey uggs femme uggs gold uggs hot uggs kopen uggs mocha uggs neder uggs noir uggs out uggs paris uggs pas uggs pascher uggs sale uggs shoe uggs silver uggs sold uggs ugly uggs uitverkoop uggs wom uggs-austr uggs-bailey uggs-bebe uggs-black uggs-boot uggs-brux uggs-class uggs-elsey uggs-femme uggs-for uggs-gold uggs-hot uggs-kopen uggs-mocha uggs-neder uggs-noir uggs-on uggs-out uggs-paris uggs-pas uggs-pascher uggs-sale uggs-shoe uggs-silver uggs-site uggs-sold uggs-ugly uggs-uitverkoop uggs-uk uggs-usa uggs-wom uggs.co uggsale uggsaustr uggsbailey uggsbay uggsbebe uggsblack uggsboot uggsbrux uggscheap uggsclass uggselsey uggsfemme uggsfor uggsgold uggshear uggshoe uggshort uggshot uggsilver uggsite uggskopen uggslipper uggslove uggsmocha uggsneder uggsnoir uggsold uggsonline uggsout uggsparis uggspas uggspascher uggss uggssilver uggstovler uggstøvler uggstyle uggsugly uggsuitverkoop uggsusa uggsustra uggswom uggthree uggturn uggusa uggustra uggwom uhren out uhttp uk out uk sale uk-cig uk-hand uk-loan uk-mall uk-mart uk-out uk-sale uk.co. uk.webeden uk/mulberry uk/prada ukcig ukclsale ukgardenhouses ukhand ukloan ukmall ukmart ukonline ukout uksale uksonline ukugg ukvip ulkotours ultimate microsoft ultimate sp1 ultimate stag ultimate-microsoft ultimate-sp1 ultimate-stag ultimate-tab ultimatemicrosoft ultimatestag ultimatetab ultra hoverboard ultra-hoverboard ultrahoverboard ultram umschuldung undeniably believe undeniably-believe under pant under-pant understood of understood-of underthe une moncler une-moncler unemoncler unique article unique broker unique interact unique-article unique-broker unique-interact uniquebroker uniqueinteract unitedidesign universal key universal-key universalkey unknown.co unknown.in unknown.pl unknown.ro unknown.ru unknown.su unknown.za unknown@ unlim pay unlim-pay unlimited pay unlimited-pay unlimitedpay unlimpay unlock iphone unlock-iphone unlock-mobile unlock-phone unlockiphone unlockmobile unlockphone unono chaus unono-chaus unonochaus unwanted-hair unwantedhair uomo adidas uomo cartier uomo dsqu uomo gucci uomo man uomo moncler uomo scarpe uomo timber uomo-adidas uomo-cartier uomo-dsqu uomo-gucci uomo-man uomo-moncler uomo-scarpe uomo-short uomo-timber uomoadidas uomocartier uomogucci uomoman uomomoncler uomoscarpe uomotimber up date up till up-till up-to-date up=date upadte update movie update-movie upgrade key upgrade-key upgradekey upload_ upload-file uploaded_ uploaded-file uploadedfile uploadfile uploading_ uploading-file uploadingfile uploads_ upseseglype urbain football urbain-football url fx url-fx url-query url-status urlacher jers urlacher-jers urlacherjers urlfx urlquery urls fx urls-fx urls2 urlsfx urlstatus uroda us informed us-informed us.please us/profil usa-best usabest usefuyl user_ user: user/profil user/view userinfo username! users_ usinformed using simplexml using-simplexml ustanovka kanali ustanovka-kanali usual discuss usual-discuss usualdiscuss usually relative usually-relative usuario_ usuario: ut.ag/ utile stuf utile-stuf utilized use utilized-use uugg uuse uustore uy/image uг uѕ v http v.@ va ballgown va garanteaza va-ballgown va-garanteaza vaballgown vagina live vagina online vagina-live vagina-online vagira vaigra valise louis valise-louis valiselouis valium valka derev'yev valka derev’yev valka derevev valka-derevev valtrex valuable opt-in valuable-opt-in valuble value blog value comment value page value post value site value web value-blog value-comment value-page value-post value-site value-web valueble vape cloud vape pen vape stick vape_ vape-cloud vape-pen vape-stick vapecloud vapepen vapestick vapor cloud vapor ix vapor pen vapor stick vapor x vapor-cloud vapor-ix vapor-pen vapor-stick vapor-x vaporcloud vaporix vaporizer pen vaporizer-pen vaporpen vaporstick vaporx vapour ix vapour pen vapour stick vapour x vapour-cloud vapour-ix vapour-pen vapour-stick vapour-x vapourcloud vapourix vapourizer pen vapourizer-pen vapourstick vapourx varabella vardenafil variant1 variant2 variant3 vbyj vecaro lifestyle vecaro_ vecaro-lifestyle vecarolifestyle vegas hotel vegas show vegas social vegas-hotel vegas-show vegas-social vendita vendorlock veneta bors veneta out veneta port veneta prezzi veneta-bors veneta-out veneta-port veneta-prezzi venetabors venetaout venetaport venetaprezzi vengasbong venlafaxine ventolin inhale ventures impart ventures note ventures pleas ventures profit ventures shape ventures-impart ventures-note ventures-pleas ventures-profit ventures-shape venus factor venus-factor venusfactor vergin porn vergin-porn vergin.porn verginporn verkoop timber verkoop-timber verkooptimber verres ray verres-ray versace belg versace jap versace jp versace uk versace-belg versace-jap versace-jp versace-uk versacebelg versacejap versacejp versaceuk versicherungsmakler verspiegelte ray very disconcertingly very imparted very lower very oone very valid very-disconcertingly very-imparted very-lower very-oone very-valid very} veryvalid vest belg vest moncler vest-belg vest-moncler vestbelg veste belg veste moncler veste-belg veste-moncler vestebelg vestemoncler vestes belg vestes moncler vestes-belg vestes-moncler vestesbelg vestesmoncler vestidos vestmoncler veston class veston-class vestonclass vests belg vests moncler vests-belg vests-moncler vestsbelg vestsmoncler vetement femme vetement homme vetement-femme vetement-homme vetementfemme vetementhomme veterinarnaya vetrinary vhs-to-digital vhttp via-internet viagara viagera viagra vibro love vibro-love vibro.love vibrolove vicodin videncia charme videncia gratis videncia vidente videncia-charme videncia-gratis videncia-vidente videnciacharme videnciagratis videnciavidente video asphyx video erotic video magnif video poker video pokie video pop video porn vidéo porn video sex video sport video tube video xblog video xxx video-asphyx video-erotic video-magnif video-poker video-pokie video-pop video-porn vidéo-porn video-sex video-sport video-tube video-xblog video-xxx video.asp video.cfm video.htm video.jsp video.php videochat videogamedesign videomagnif videopoker videopokie videopop videoporn vidéoporn videos porn vidéos porn videos xblog videos-porn vidéos-porn videos-xblog videos.asp videos.cfm videos.htm videos.jsp videos.php videosex videosporn vidéosporn videosport videosxblog videotube videoxblog videoxxx vidios viencare vietnam-visa vietnamvisa view_ view-article view-blog view-entry view/page view/pg view/story viewarticle viewblog viewentry viewlink viewpag viewtopic vigara vigrx vimax vinder burberry vinder jakker vinder-burberry vinder-jakker vinderburberry vinderjakker vinho bras vinho braz vinho-bras vinho-braz vinhobras vinhobraz vintage erotic vintage-erotic vintageerotic vinter burberry vinter jakker vinter-burberry vinter-jakker vinterburberry vinterjakker vintovoy nas vintovoy-nas vintovyye nas vintovyye-nas vip девочк vip-девочк vipgirl viral adremus viral eas viral hit viral play viral storie viral story viral video viral_ viral-adremus viral-eas viral-hit viral-play viral-storie viral-story viral-video viral.club viral.online viraladremus viraleas viralhit viralplay viralstorie viralstory viralvideo viramune virgin porn virgin-porn virgin.porn virginporn virtual sex virtual-sex virtualsex virus answer virus hoax virus infect virus remov virus secur virus_ virus-answer virus-hoax virus-infect virus-remov virus-secur virusanswer virushoax virusinfect virusremov virussecur visijt visit homepage visit my visit web visit-homepage visit-my visit-web visitant visitor/day visitors/day vitalikor vitamin-for vitaminfor vitamins-for vitaminsfor vitamix vitrine vivekkunwar vivienne wood vivienne-wood viviennewood vivier ballerine vivier ital vivier pompe vivier-ballerine vivier-ital vivier-pompe vivierballerine vivierital vivierpompe vivotab vk.cc vkgnfx vod_ vogue toms vogue-toms voguetoms voltaren_ voltaren. von ysl von-ysl vonysl votre site votre-site votresite voyance amour voyance couple voyance gratuit voyance-amour voyance-couple voyance-gratuit voyance< voyanceamour voyancecouple voyancegratuit voynich code voynich-code vtx-fair vtxfair vuitton austr vuitton bag vuitton belt vuitton black vuitton bors vuitton brace vuitton cig vuitton damier vuitton deutsch vuitton espa vuitton factor vuitton fanny vuitton fashion vuitton france vuitton fx vuitton glass vuitton hand vuitton hard vuitton men vuitton online vuitton out vuitton pas vuitton purse vuitton replica vuitton sac vuitton shoe vuitton sold vuitton speed vuitton taschen vuitton wallet vuitton wholes vuitton women vuitton-bag vuitton-black vuitton-bors vuitton-brace vuitton-cig vuitton-damier vuitton-espa vuitton-fanny vuitton-fashion vuitton-glass vuitton-men vuitton-pas vuitton-purse vuitton-replica vuitton-shoe vuitton-sold vuitton-speed vuitton-uk vuitton-usa vuitton-wallet vuitton-wholes vuitton-women vuitton+ vuitton< vuittonbag vuittonbors vuittoncig vuittondamier vuittonespa vuittonfanny vuittonfashion vuittonglass vuittononline vuittonpas vuittonpurse vuittonreplica vuittonsac vuittonsold vuittonspeed vuittonuk vuittonusa vuittonwallet vuittonwholes vv free vv-free vvfree vvv vyrubka derev'yev vyrubka derev’yev vyrubka derevev vyrubka-derevev vzlomat vе w http w.@ w.il.lkom.men wakka= wallet out wallet-out walletout wallinside wallpaper download wallpaper free wallpaper-download wallpaper-free wallpapers download wallpapers free wallpapers-download wallpapers-free walmartbuy walmartmail wang bag wang-bag wangbag wanna say wanna state wanna-say wanna-state want bitcoin want-bitcoin wantbitcoin wantsand warcraftguide wardrobe application wardrobe-application wardrobeapplication ware-crack warecrack warehouse.in warehouse.net warehouse.org warehouse.pl warehouse.ro warehouse.ru warehouse.su warehouse.us warehouse.za warepics warez warmjp warriors jersey warriors-jersey warriorsjersey warszawa wasbusiness watch video watch-quartz watch-replica watch-seiko watch-video watches-quartz watchesquartz watchhoshi watchquartz watchreplica watchseiko water-weight waterprrof way-to-enjoy wayfarer barata wayfarer barato wayfarer glass wayfarer-baratas wayfarer-barato wayfarer-glass wayfarerbarata wayfarerbarato wayfarerglass wdmypass we-are-your wealth affiliate wealth-affiliate wealthaffiliate wealthy affiliate wealthy-affiliate wealthyaffiliate weave-hair weavehair web anal web blog web content web ddress web explore web farm web optim web owner web people web pharm web promo web sie web site's web site’s web sitte web therefore web viewer web website web_log web-anal web-blog web-content web-explore web-farm web-host web-log web-optim web-owner web-people web-pharm web-promo web-sie web-site web-sitte web-solution web-therefore web-traff web-viewer web-website web.best web/icon web/index webanal webblog webcambabe webcamsex webcontent webdesign.co webexplore webfarm weblink weblog! weblog} weblogg weblogto webmaster webnode.cn webnode.co webnode.cz webnode.fr webnode.hu webnode.it webnode.nl webog post webog-post weboptim webowner webpeople webpharm webpromo websex webshop/image websiite websit was websit-was website everyday website keep website look website online website post website style website traff website usa website visit website we website-everyday website-keep website-look website-online website-post website-style website-usa website-visit website-we website! website.keep website} website1 websitelook websiteonline websitepost websiterecord websites design websites online websites visit websites-design websites-online websites-visit websitesdesign websitesonline websitestyle websitesvisit websitetraff websiteusa websitevisit websitte websolution webste webtherefore webviewconsult webviewer wedding jewel wedding-jewel weddingdress wedtgh weeb week.in weekly income weekly profit weekly-income weekly-profit weekly.in weeklyincome weeklyprofit weelhtiar weight gain weight loss weight natur weight work weight-gain weight-loss weight-natur weight-work weightgain weightloss weightnatur weightwork welcometonginx well written! wellbutrin wentoutside weptwith werbegeschenke weredouble werent weste jacken weste-jacken westejacken westwood boot westwood necklace westwood online westwood shop westwood store westwood-boot westwood-necklace westwood-online westwood-shop westwood-store westwoodboot westwoodnecklace westwoodonline westwoodshop westwoodstore wet pant wet pussy wet-pant wet-pussy weteihal wetpant wetpussy wetting pant wetting-pant wettingpant weusecoin wewewe what blog what-blog what-we-know what's up, what’s up, whatblog whatll whats blog whats up, whats-blog whatsapp whatsblog wheel.es wheeles wheels.co wheels.net wheels.org whenshe where buy where get where sell where-buy where-can-i where-get where-sell where-to-buy where-to-get where-to-sell where+can where+to wherebuy wherecani whereget wheresell wheretobuy wheretoget which-is-depress which-will-save whichpag white hermes white nfl white-hermes white-nfl whitehermes whitenfl whoa this whoa-this whoah this whoah-this whois tool whois-tool whoistool wholesale acrylic wholesale bag wholesale cheap wholesale china wholesale chinese wholesale coach wholesale copy wholesale free wholesale hand wholesale iphone wholesale jers wholesale jersey wholesale jord wholesale led wholesale nba wholesale north wholesale polo wholesale ralph wholesale soccer wholesale-acrylic wholesale-bag wholesale-cheap wholesale-china wholesale-chinese wholesale-coach wholesale-copy wholesale-free wholesale-hand wholesale-iphone wholesale-jers wholesale-jersey wholesale-jord wholesale-led wholesale-mac wholesale-nba wholesale-north wholesale-polo wholesale-ralph wholesale-soccer wholesalebag wholesalecheap wholesalechina wholesalechinese wholesalecoach wholesalefree wholesalehand wholesalejers wholesalejersey wholesalejord wholesaleled wholesalemac wholesalenba wholesalenorth wholesalepolo wholesaler-mac wholesalermac wholesalesoccer whttp why-would-you wɦ widespread prescription widespread-prescription widget_ widyaw wife-tube wifetube wifewith wifi jammer wifi-jammer wifijammer wigsrus wihesd wiith wiki_ wiki/% wiki/index wiki/u% wiki/user wikka.asp wikka.cfm wikka.htm wikka.jsp wikka.php wild-star will hemp will-hemp will-save-you wille bijoux wille sold wille-bijoux wille-sold willebijoux willesold willhemp wilson jers wilson-jers wilsonjers win7.co win7pro win8.co win8pro window-file windowfile windows anytime windows_ windows-7-key windows-7-theme windows-8-key windows-8-theme windows-anytime windows-app windows-file windows7key windows7theme windows8key windows8theme windowsanytime windowsfile windowsphone.co winesickle wingsshop winter jassen winter-jassen winterjassen wise quote wise-quote witgh with pics with-pics withdrawal! withher withhim without invest without prescript without-invest without-prescript withthose woah this woah-this woderful woman bikini woman-bikini woman+ womanbikini women bikini women-bikini women.ru/pict women+ womenbikini won blog won webpage won-blog won-webpage wonblog wondeful wonderful article wonderful blog wonderful post wonderful publish wonderful site wonderful website wonderful-article wonderful-blog wonderful-post wonderful-publish wonderful-site wonderful-website woodgundy.us woolrich amster woolrich arctic woolrich bliz woolrich bolo woolrich cloth woolrich coat woolrich coupon woolrich donn woolrich europe woolrich field woolrich giub woolrich jack woolrich john woolrich out woolrich parka woolrich piumini woolrich scarf woolrich scarve woolrich site woolrich sito woolrich store woolrich uomo woolrich vest woolrich wool woolrich-amster woolrich-arctic woolrich-bliz woolrich-bolo woolrich-cloth woolrich-coat woolrich-coupon woolrich-donn woolrich-europe woolrich-field woolrich-giub woolrich-jack woolrich-john woolrich-out woolrich-parka woolrich-piumini woolrich-scarf woolrich-scarve woolrich-site woolrich-sito woolrich-store woolrich-uomo woolrich-vest woolrich-wool woolrich.arkis woolrichamster woolricharctic woolrichbliz woolrichbolo woolrichcloth woolrichcoat woolrichcoupon woolrichdonn woolriche site woolriche sito woolriche-site woolriche-sito woolrichesite woolrichesito woolricheurope woolrichfield woolrichgiub woolrichjack woolrichjohn woolrichout woolrichparka woolrichpiumini woolrichscarf woolrichscarve woolrichsite woolrichsito woolrichstore woolrichuomo woolrichvest woolrichwool woowoo-house woowoo-sex woowoo-site woowoohouse woowoosex woowoosite wordpress web wordpress-web wordpressweb woriing work-review workout-sale workoutsale workreview world fuck world porn world sex world xxx world-capital-advis world-fuck world-porn world-sex world-venture world-xxx worldfuck worldporn worlds most worlds-most worlds.bl worldsex worldugg worldventure worldwideugg worldxxx worth bookmarking worth comment worth-bookmarking worth-comment worthbookmarking wow-gold wowo-house wowo-sex wowo-site wowohouse wowosex wowosite wp robot wp-pad wp-phone wp-robot wpadmin wpcontent wpimage wpinclude wplist wprobot wpsite wpthemegen wrinting wristpain write-essay write-paper writeessay writepaper writing manually writing taste writing-essay writing-manually writing-paper writing-service writing-taste writingessay writingpaper writingservice wto brand wto-brand wtobrand wweb page wweb-page wwebpage wwellness www_ www.1 www.247 www.2012 www.2013 www.2014 www.2015 www.adults www.article www.barata www.barato www.best- www.consult www.deal- www.deels www.e-market www.efinanc www.erotic www.escort www.fake www.filmi www.filmy www.forum www.fot- www.fot. www.fota- www.fota. www.foteczka www.fotka www.fotki www.foto- www.foto. www.fotoalbum www.fotograf www.fotomania www.freerun www.fullsize www.gambl www.goosepa www.handbag www.howmuch www.instyler www.isabel- www.lastminute www.lolita www.lv www.nitrocellulose www.numberone www.oakleyin www.offerta www.offerte www.onsale www.penis www.porn www.promo www.r4i www.sadje www.scarpe www.schuh www.seo www.sexy www.shoe- www.shoes- www.torrent www.unlock www.videos www.wtf www.www. www.xx www.ysl www.zapata www.zapato www< wwwpctool wypoczynku wyposazenie wywóz wо wһ wօ x adidas x http x portant x rumer x stream x website x-adidas x-bisex x-galler x-http x-portant x-rumer x-stream x-website x.@ xadidas xalatan xanax xbisex xelerated xeloda xenical xgaller xhttp xl paket xl-paket xlpaket xn-- xopenex xportant xrumer xstream xsvseqvkz xtra size xtra-clean xtra-size xtrasize xwebsite xx.co xx.in xx.pl xx.ro xx.ru xx.su xx.za xxx best xxx free xxx fuck xxx group xxx mobile xxx party xxx pic xxx sex xxx tube xxx video xxx_ xxx-best xxx-free xxx-fuck xxx-group xxx-mobile xxx-party xxx-pic xxx-sex xxx-tube xxx-video xxx. xxxbest xxxfree xxxfuck xxxgroup xxxmobile xxxparty xxxpic xxxsex xxxtube xxxvideo xyngular xzzy.co xzzy.in xzzy.pl xzzy.ro xzzy.ru xzzy.su xzzy.za y culo y http y pezone y teta y tetona y-culo y-pezone y-teta y-tetona y.@ y.o.ur y.ou.r yahoo spy yahoo-spy yahoospy yanswer yarosl yearold yeezy boost yeezy-boost yeezy1 yeezy2 yeezy3 yeezyboost yhttp ynimb yo-dick yo-penis yo.u.r yodick yoga out yoga-out yogaout yoigucci yoour yopenis york sportiv york-sportiv yorksportiv you article you gucci you ssay you ugg you viral you write-up you writeup you-article you-gucci you-ssay you-ugg you-viral you-write-up you-writeup you,i you:- you:) youd yougucci youll young porn young-porn youngporn your blog your budget your dick your penis your porn your sijte your submit your success your traff your ugg your weblog your_ your-account your-blog your-budget your-credit your-dick your-ex your-free your-loan your-penis your-personal your-porn your-profile your-sijte your-site your-skin your-success your-ugg your-weblog your-website your-windows your-your your.mail@ youraccount yourblog yourbudget yourcredit yourdick yourdui yourfree yourloan yourls yourmail@ yourpenis yourpersonal yourporn yourprofile yoursijte yoursite yoursuccess yourugg yourweblog yourwebpage yourwebsite youth jers youth-jers youthjers youtube down youtube sens youtube vi youtube_ youtube-down youtube-sens youtube-vi youtubedown youtubesens youtubevi youu youugg youur youve youviral youyou yoyo diet yoyo-diet yoyodiet ysl bag ysl chaus ysl damen ysl femme ysl schuh ysl-bag ysl-chaus ysl-damen ysl-femme ysl-schuh yslbag yslchaus ysldamen yslfemme yslschuh yu move yu-move yumeeyum yutyca yuuguu yyou yyy ÿþ yе yо yօ z http z.@ z.in zabaw zagorodnyy dom zagorodnyy-dom zahnversicherung zahnzusatzversicherung zaidimai zaindeksowany zakazane zaklady zanaflex zanotti online zanotti sale zanotti shoe zanotti sneak zanotti-online zanotti-sale zanotti-shoe zanotti-sneak zanottionline zanottisale zanottishoe zanottisneak zantac zapata dc zapata mbt zapata_ zapata-dc zapata-mbt zapatadc zapatadembt zapatambt zapatas asic zapatas dc zapatas jord zapatas mbt zapatas_ zapatas-asic zapatas-dc zapatas-jord zapatas-mbt zapatasasic zapatasdc zapatasdembt zapatasjord zapatasmbt zapatilla dc zapatilla mbt zapatilla_ zapatilla-dc zapatilla-mbt zapatilladc zapatilladembt zapatillambt zapatillas asic zapatillas dc zapatillas jord zapatillas mbt zapatillas_ zapatillas-asic zapatillas-dc zapatillas-jord zapatillas-mbt zapatillasasic zapatillasdc zapatillasdembt zapatillasjord zapatillasmbt zapato dc zapato_ zapato-dc zapato-de-new zapatodc zapatodenew zapatos asic zapatos dc zapatos jord zapatos_ zapatos-asic zapatos-dc zapatos-jord zapatosasic zapatosdc zapatosjord zapewniaja bezpiecz zapewniaja-bezpiecz zapraszam zapravka kartrid zapravka-kartrid zarabotk zawieszki zboard.asp zboard.cfm zboard.htm zboard.jsp zboard.php zcode zdrowie zealous of zealous-of zed-bull zedbull zenonia zeny nike ženy nike zeny obuv ženy obuv zeny run ženy run zeny-nike ženy-nike zeny-obuv ženy-obuv zeny-run ženy-run zenynike ženynike zenyobuv ženyobuv zenyrun ženyrun zenys nike ženys nike zenys obuv ženys obuv zenys run ženys run zenys-nike ženys-nike zenys-obuv ženys-obuv zenys-run ženys-run zenysnike ženysnike zenysobuv ženysobuv zenysrun ženysrun zero calorie zero-calorie zerocalorie zfymail zhavy žhavý zhttp zielona kawa zielona-kawa zielonakawa zikinell zimmerman fund zimmerman hedge zimmerman-fund zimmerman-hedge zimmermanfund zimmermanhedge zimovane zinger baseball zinger bat zinger-baseball zinger-bat zip-password zippo feuerzeuge zippo zubehor zippo zubehör zippo-feuerzeuge zippo-zubehor zippo-zubehör zippofeuerzeuge zippozubehor zippozubehör zithromax zlinks złote zmedicin zoe boot zoe-boot zoidstore zoloft zolpidem zonejp zopiclone zovirax zsurgical zumbah zvideo zwrot podatku zwrot-podatku zx's zx’s zxog zyban zyprexa zyrtec zyvox ϳa ϳi ϳo ϳu νa νe νi νo οf οn οs ρa ρe ρi ρo ρp ϲa ϲo ͼ: ύa ύe ύi ύo ωa ωh ωi ωo ωі аl аr аs аt абонентская авиабилет автотерморегулятор агенств адгуард админу активной продаж активной-продаж алкогольные альтернатива андроид анилиновые краси анилиновые-краси аренда бесшо аренда видос аренда пенна аренда пенно аренда свето аренда-бесшо аренда-видос аренда-пенна аренда-пенно аренда-свето аренды авто аренды-авто базу данны базу-данны байкал озеро байкал отдых банков банкрот без бензогенератор бесплатная демо бесплатная диаг бесплатная-демо бесплатная-диаг бесплатной демо бесплатной диаг бесплатной-демо бесплатной-диаг бинарных опцион бинарных-опцион блог брелки валка деревьев валка-деревьев вашего бизнеса вашего-бизнеса вђ веб вещевой кардинг вещевой-кардинг взламывать одно взламывать паро взламывать-одно взламывать-паро взломать одно взломать паро взломать-одно взломать-паро виагра видео чат видео-чат визиток сайт винтовой нас винтовой-нас вклады сбербанк вклады-сбербанк вырубка деревьев вырубка-деревьев г© гa гe гo гy гей питер гей-питер глубокое глот глубокое-глот говорим гово говорим-гово говорить гово говорить-гово говоришь гово говоришь-гово говорю гово говорю-гово головные устройс головные-устройс грузоперевозки двери гарди двери сдвиж двери форп двери-гарди двери-сдвиж двери-форп девушка сопровождение девушка-сопровождение девушки сопровождение девушки-сопровождение декоративно прикладно декоративно-прикладно денег детские дискотек детские-дискотек дешево джекпот лотерея джекпот-лотерея дизайн дизайн дизайн-дизайн дизайн, дизайн домены доставка доход драйвера ԁa еa еb еc еd еr еs еt еv еw жк центральны жк-центральны за таблетку за-таблетку заболевани загородный дом загородный-дом заказ заправка картридж заправка-картридж заработ зарегистрировать здоровье сайт здоровье-сайт змусила на змусила-на знакомства значительный сайт значительный-сайт ѕs игровые изготовлен инете инстаграм интересн интернет іl іn іs іt кабриолет казино качест качественный квартир кино позитив кино-позитив кинопозитив китай клипса антихрап клипса-антихрап клубах комисси коммерческих трудност коммерческих-трудност компании компьютер консультаци контрабандного това контрабандного-това красивая девушка красивая-девушка кредит куп диплом куп свидетел куп удост куп-диплом куп-свидетел куп-удост купить купить диплом купить свидетел купить элитн купить-диплом купить-свидетел купить-элитн курс евро курс-евро курсов кассиров курсов-кассиров куртка мужская куртка-мужская ҟa ҟe ҟi ҟo ҟy ԛu лайткоин легальные ликвида лицензия ломают машин ломают-машин ломаютмашин любит глубокое любит-глубокое магаз маркет массажистка материал мебель медиа палитра месяц назад месяц-назад метал меховые миллион мир визиток многое другое многое-другое молочной монтаж ауп монтаж канали монтаж обслуж монтаж элект монтаж-ауп монтаж-канали монтаж-обслуж монтаж-элект москве устройс москве-устройс мужские пальто мужские-пальто найти человека накрутка подписчик накрутка-подписчик направления роста направления-роста начинающих алматы начинающих-алматы недорого низкие цен новинки новых автомоби оa оc оd оe оh оo оs оt оv обрезка деревьев обрезка-деревьев обучение объявлени одежда ожерелье онлайн оптом от ожирения от-ожирения отделка балкона отделка-балкона отзывы отличные официальных оформить груз оформить сайт оформить-груз оформить-сайт партнерская партнерских програм партнерских-програм пенная вечери пенная шоу пенная-вечери пенная-шоу пенное вечери пенное шоу пенное-вечери пенное-шоу перманентный макияж перманентный-макияж персональный плата платки батик платки купить платки-батик платки-купить платок батик платок купить платок-батик платок-купить под ключ под-ключ подарки подарков подарочной коробк подарочной-коробк подешевле пожаловать поиск-номера-теле поисковик поисск відео поисск-відео покер порно портал посетител похудению предла прелестный сайт прелестный-сайт преобрести прибыль приватные прокси приватные-прокси приветствует сайт приветствует-сайт привлекательный приема платежей приема-платежей приобрести приобрести недви приобрести-недви продаж продвижение аккау продвижение-аккау производство промо проститутки професси прошивка психоло путешествую р¶р рµр разработать игру разработать-игру рассылки сайт рассылки-сайт рднк реальный кардинг реальный-кардинг регистратор реклама рекламное рекламу ремонт автомат ремонт ворот ремонт-автомат ремонт-ворот ресниц ресурс рєр руб вста руб гарп руб коль рублей рулетка джекпот рулетка для рулетка-джекпот рулетка-для рф сайт визитк сайт рассылки сайт-визитк сайт-рассылки сайте сайтов самая крупная самая-крупная самый превосход самый-превосход свой сайт свой-сайт секс селитра симпатичная симптом сироп мангустина сироп-мангустина скандал жк скандал-жк скачать скидки билеты скидки на скидки отели скидки туры скидки-билеты скидки-на скидки-отели скидки-туры слот создать сайта создать-сайта сократите риск сократите-риск сопровождение девушка сопровождение девушки сопровождение-девушка сопровождение-девушки спам спиливание деревьев спиливание-деревьев спорт на спорт открытом спорт-на спорт-открытом справкой справочник сруба ссылка статей статья стили деко стили проспект стили-деко стили-проспект стилист проспект стилист-проспект стоимость страпон стратегия продвиж стратегия-продвиж супер схемы съемка табак танж табак-танж такси татуаж телефона узнать телефона-узнать торговля бинар торговля-бинар трудоустройства уa уe уo удивительные ура наконецто ура-наконецто услуг установка забор установка канали установка-забор установка-канали утепление фабрика меха фильмов финансовую фирму форум фото футбол һa һe һi һo һu һy целевые клиентские целевые-клиентские цена через поисск через-поисск шапка шёлковый палант шёлковый платок шёлковый-палант шёлковый-платок шкуры животных шкуры-животных щзп эвакуатор элитную мебель элитную-мебель элитныеноутбуки эротика юридической язык алматы язык-алматы языка алматы языка-алматы яндekc ԝa ԝe ԝi ԝo աa աɑ աe աi աn աo աu ոa ոe ոi ոn ոo սa օd օf օm օn օp օr օs օt օw օx օy ויקיפדיה ߋ ขายเสื้อผ้าเด็ก คาสิโน ต่างหู 루이비통 무료쿠폰 성인자 야동 트위터 アークテリクス アイホン アウトレット アディゼロ アディダス アバクロ アメリカン エキスプレス アメリカン·エキスプレス アメリカンーエキスプレス アルマーニ アンドロイド いい イヴサンローラン いホスティング ヴィクトリアシークレット ヴィトン ヴェネタ ウェブサイト ヴェルサーチ エアジョーダン エスパドリーユ エルメス オークリー おざ オロビアンコ お客 お店を お金を ガガ カジュアル カナダグース カルティエ カルティエは ギフト グッチ クラシック クロエ クロムハーツ ケース ケイトスペード コピ ゴヤール コルクウェッジ コンバース ご了承 ご返金 さしあげ サッカー サマンサタバサ サングラス サンダル ジェレミ スコット ジェレミ·スコット ジェレミースコット ジャケッ シューズ ショッ スニーカー スポード スワロフスキー セールス セイコー セクシー セリーヌ ダウン ダコタ ダナー ダミエ チャンルー ティエンポ ティファニ ティンバ デュベティカ トゥミ トリーバーチ トレッキング ナイキ ニューバランス ネックレス ノースフェイス のブックマーク の値段 バーバリー バスケットボール パタゴニア ハッカー バッグ パネライ バンクオブアメリカ バンズ ビトン ブーツ ファッショ フェラガモ フェンディ ブが広がります プライバシ プラダ ブランド ブルガリ ブレスレット プレゼント ブログ プロダ プロモ ベルトメンズ ポーター ホグロフス ボッテガ ホット ホリスター マークジェイコブス ミュウ ムービー用 メガネ モンクレール モンスタービーツ モンブラン ユーチューブ ラウンジ ラゲージ ルイヴィトン ルブタン レシート レスポートサック レプリカ ロレックス ワンランク上の 万年筆 上質 买烟 人気 仕入れ 他の製品 价格 保存 保証 倒 全球华人 公式 冷静化处理 割引 务部 化粧 医師 华人利益 博主 博彩 危機 即時比分 发泄物 取得 受注 口コミ 吉田カバン 商品 块钱 外贸 好評 婚庆礼仪 安い 小銭入れ 履 巨大 店の 店舗 性 成功的医生 手机 把药 摔死女 攜心山 最安値 服务 札入れ 样死 標準 正宗肥仔 正規店 死吗 毛刈 永久的耻辱 深圳新闻 漫画を 激安 無料 特価 状態 猫v 現金網 用品 発売 百家乐 直営店 眉n 眉r 看似深 真のメリット 秋冬 節約 米小游戏 素材 綺麗め 総合 考, 脳症 脿 腕時計 興奮 花火 芸能人 茅 荷物 落枕 血症 行動電源 装着 評価 試, 谋t 財布 貿易 赌场 輸入 返品 通信販売 通販 配送 酒嗜 酷小游戏 金引換 金融 鈥? 鈥檃 銉 锘? 锘縉 锟斤拷 门户网 革の 靴 韓国音楽 香水 ? 蘒 﨨 free/data/disallowed_logins_list.data 0000644 00000004354 15174670627 0014013 0 ustar 00 a,about,access,account,accounts,ad,address,adm,adminzax*,administrarot,administrator,administration,administratoir,administratoirr,adminuser,adult,advertising,affiliate,affiliates,ajax,analytics,android,anon,anonymous,api,app,apps,archive,atom,auth,authentication,avatar,b,backup,banner,banners,bin,billing,blog,blogs,board,bot,bots,business,c,chat,cache,cadastro,calendar,campaign,careers,cdn,cgi,client,cliente,code,comercial,compare,config,connect,contact,contest,create,code,codepapa,compras,css,d,dashboard,data,db,deleted-*,design,delete,demo,demouser,design,designer,dev,devel,dir,directory,doc,documentation,docs,domain,download,downloads,e,edit,editor,email,ecommerce,f,forum,forums,faq,favorite,feed,feedback,flog,follow,file,files,free,ftp,g,gadget,gadgets,games,guest,greeceman,group,groups,h,help,home,homepage,host,hosting,hostname,htm,html,http,httpd,https,hpg,i,info,information,image,img,images,imap,index,invite,intranet,indice,invite,ipad,iphone,irc,j,java,javascript,job,jobs,js,k,knowledgebase,kb,l,license_admin*,log,login,logs,logout,list,lists,m,mail*,mailer,mailing,main,mx,manager,marketing,master,media,message,microblog,microblogs,mine,mp3,msg,msn,mysql,messenger,mob,mobile,movie,movies,music,musicas,my,n,name,named,net,network,new,news,newsletter,nick,nickname,notes,noticias,ns,ns1,ns2,ns3,ns4,ns5,ns6,ns7,ns8,ns9,o,old,online,operator,options,order,orders,p,page,pager,pages,panel,password,perl,pic,pics,photo,photos,photoalbum,php,plugin,plugins,pop,pop3,post,postmaster,postfix,posts,private,profile,project,projects,promo,pub,public,python,q,query,r,random,register,registration,root,ruby,rss,s,sale,sales,sample,samples,script,scripts,secure,send,service,s.e.x,shop,sql,signup,signin,search,sendsdesr,security,settings,setting,setup,site,sites,sitemap,smtp,soporte,ssh,stage,staging,start,subscribe,subdomain,suporte,support,stat,static,stats,status,store,stores,system,t,tablet,tablets,tech,telnet,test*,theme,themes,tmp,todo,task,tasks,tools,tv,talk,u,update,upload,url,user,username,usuario,usage,v,vendas,video,videos,visitor,w,win,ww,wws,www*,web,webmail,website,websites,webmaster,workshop,wpx,wp-demouser*,wp-import-user,wp_update-*,wpsupp-user*,wp-needuser*,wpadmin*,wp-user,wp-blog,wwwadmin,x,xxx,xpg,y,you,z,zzz,_,.,-,@,0,2,3,4,5,6,7,8,9 free/data/whitelist-plugin-list.data 0000644 00000000107 15174670627 0013527 0 ustar 00 bloom out-of-the-box secupress secupress-pro sitepress-multilingual-cms free/data/bad_request_keys.data 0000644 00000002031 15174670627 0012575 0 ustar 00 502,peswed,dbses,cp_cracker,cp_jump,l__l_,_f_wp,code custom_action,type emails themes messages froms mailers key,usrname passwrd,song2 stars1,to_address subject body from_address from_name MX,redate redate_file,chdate redate_file,unzip tofolder,group file tar,group file zip,group delete file,file copy_to finish,type uploadurl,type file path type inputPassword2,ajax type content,fm_usr fm_pwd,action message emaillist from subject realname wait tem smv,ajax p1,loop data,init key host port,pws get conf,upl _upl,grab cat passwd,cmd command submit,passwords usernames,pws get conf,onlineId1 passcode1 pass email eMailAdd,rename rname act y,changemod perm y,touch y time,connect server username pwd database,cdir goto,cr8file filename,cr8folder foldername,kill pid,act path_dir,api ipport lang,api path searchfile,api code lang,api cmd path,api path searchfile,api ipport lang,api query,api path content,api fullpath,api y x,base_dir file_name index,karsikod ders varmi,kategorims ders varmi,resims ders aciklama,oyuncular tarih ulke puan,option opt free/data/salt-keys.phps 0000644 00000003330 15174670627 0011224 0 ustar 00 <?php /** * Plugin Name: {{PLUGIN_NAME}} Salt Keys * Description: Great Security Keys for your site * Version: 2.3.17 * License: GPLv2 * License URI: http://www.gnu.org/licenses/gpl-2.0.html * * Copyright 2012-2025 SecuPress */ defined( 'ABSPATH' ) or die( 'Something went wrong.' ); if ( defined( 'SECUPRESS_SALT_KEYS_MODULE_ACTIVE' ) ) { @unlink( __FILE__ ); // We are in a duplicated file, should not happen, delete us! return; } if ( ! get_site_option( 'secupress_active_submodule_wp-config-constant-saltkeys' ) ) { return; } define( 'SECUPRESS_SALT_KEYS_MODULE_ACTIVE', true ); $hash_1 = '{{HASH1}}'; $hash_2 = '{{HASH2}}'; $file_str = __FILE__; $sp_setup = get_option( 'secupress_settings' ); $hash_key = isset( $sp_setup['hash_key'] ) ? $sp_setup['hash_key'] : md5( __FILE__ ); $hash_1 .= $hash_2; $file_str .= $hash_2; $main_keys = [ 'SECRET_KEY', 'AUTH_KEY', 'SECURE_AUTH_KEY', 'LOGGED_IN_KEY', 'NONCE_KEY', 'SECRET_SALT', 'AUTH_SALT', 'SECURE_AUTH_SALT', 'LOGGED_IN_SALT', 'NONCE_SALT' ]; foreach ( $main_keys as $main_key ) { if( ! defined( $main_key ) ) { define( $main_key, sha1( 'secupress' . $hash_key . $main_key . md5( $main_key . $file_str ) ) . md5( $hash_key . $main_key . $file_str ) ); } } unset( $file_str, $main_key, $main_keys, $hash_1, $hash_2, $hash_key, $sp_setup ); if ( ! function_exists( 'wp_salt' ) ) { function wp_salt( $scheme = 'auth' ) { $scheme = strtoupper( $scheme ); if ( ! defined( "{$scheme}_KEY" ) || ! defined( "{$scheme}_SALT" ) ) { $scheme = 'secret'; } /** This filter is documented in wp-includes/pluggable.php */ return apply_filters( 'salt', constant( strtoupper( "{$scheme}_KEY" ) ) . constant( strtoupper( "{$scheme}_SALT" ) ), $scheme ); } } free/data/headers.data 0000644 00000000370 15174670627 0010663 0 ustar 00 [["version",["08.05.24.1724055379"]],["required_sp_ver",["2.2.6"]],["database_ver",["2.112.18"]],["copyright",["secupress.me","Julio Potier","contact@secupress.me"]],["Y29udGFjdEBzZWN1cHJlc3MubWU=",["YmU3MGFkYWM4ZjM1MjE3ODI2NGViZDlhMGM1ZmZhYjM="]]] free/data/bad_host_contents.data 0000644 00000000251 15174670627 0012746 0 ustar 00 163data,amazonaws,colocrossing,crimea,g00g1e,justhost,kanagawa,loopia,masterhost,onlinehome,poneytel,sprintdatacenter,reverse\.softlayer,safenet,ttnet,woodpecker,wowrack free/data/no_plugins_installation.phps 0000644 00000010044 15174670627 0014246 0 ustar 00 <?php /** * Plugin Name: {{PLUGIN_NAME}} No Plugin Installations * Description: Filters the active plugin option to prevent loading other ones. * Version: 2.3.17 * License: GPLv2 * License URI: http://www.gnu.org/licenses/gpl-2.0.html * * Copyright 2012-2025 SecuPress */ defined( 'ABSPATH' ) or die( 'Something went wrong.' ); if ( defined( 'SECUPRESS_NO_PLUGIN_ACTION_RUNNING' ) ) { @unlink( __FILE__ ); // We are in a duplicated file, should not happen, delete us! return; } define( 'SECUPRESS_INSTALLED_PLUGINS' , '_secupress_installed_plugins' ); define( 'SECUPRESS_INSTALLED_MUPLUGINS' , '_secupress_installed_muplugins' ); define( 'SECUPRESS_ACTIVE_PLUGINS' , '_secupress_active_plugins' ); define( 'SECUPRESS_NO_PLUGIN_ACTION_RUNNING', true ); $GLOBALS['SECUPRESS_EXPERT_MODULES_ON']['plugin_actions'] = true; if ( is_multisite() ) { define( 'SECUPRESS_ACTIVE_PLUGINS_NETWORK' , '_secupress_active_sitewide_plugins' ); add_filter( 'pre_site_option_active_sitewide_plugins', 'secupress_no_action_filter_active_plugins_network' ); /** * Return our active plugins list * * @since 2.2.6 * @author Julio Potier * @return (array) $active_plugins **/ function secupress_no_action_filter_active_plugins_network( $pre ) { $plugins = get_site_option( SECUPRESS_ACTIVE_PLUGINS_NETWORK, null ); if ( is_null( $plugins ) ) { defined( 'SECUPRESS_ACTIVE_PLUGINS_NETWORK_ERROR' ) || define( 'SECUPRESS_ACTIVE_PLUGINS_NETWORK_ERROR', true ); return $pre; } return $plugins; } } add_filter( 'pre_option_active_plugins', 'secupress_no_action_filter_active_plugins' ); /** * Return our active plugins list * * @since 2.2.6 * @author Julio Potier * @return (array) $active_plugins **/ function secupress_no_action_filter_active_plugins( $pre ) { $plugins = get_option( SECUPRESS_ACTIVE_PLUGINS, null ); if ( is_null( $plugins ) ) { defined( 'SECUPRESS_ACTIVE_PLUGINS_ERROR' ) || define( 'SECUPRESS_ACTIVE_PLUGINS_ERROR', true ); return $pre; } return $plugins; } /** * @see secupress_get_not_installed_plugins_list() * * @since 2.2.6 * @author Julio Potier * @return (array) $plugins **/ function _secupress_get_not_installed_plugins_list_all() { $ins_plugins = get_site_option( SECUPRESS_INSTALLED_PLUGINS ); if ( ! $ins_plugins ) { return []; } if ( ! function_exists( 'get_plugins' ) ) { include_once( ABSPATH . 'wp-admin/includes/plugin.php' ); } $get_plugins = get_plugins(); $plugins = array_diff_key( $get_plugins, $ins_plugins ); return $plugins; } $__plugins_del_me = []; $__plugins = []; $__plugins['old'] = get_site_option( SECUPRESS_INSTALLED_MUPLUGINS, [] ); $__plugins['real'] = []; foreach ( wp_get_mu_plugins() as $mu ) { $__plugins['real'][ basename( $mu ) ] = 1; } $__plugins['more'] = array_diff_key( $__plugins['real'], $__plugins['old'] ); $__plugins['less'] = array_diff_key( $__plugins['old'], $__plugins['real'] ); if ( ! empty( $__plugins['more'] ) ) { foreach( $__plugins['more'] as $mufile => $dummy ) { $key = str_replace( '.', '', microtime( true ) ); rename( WPMU_PLUGIN_DIR . '/' . $mufile, WPMU_PLUGIN_DIR . '/' . $mufile . '_' . $key ); file_put_contents( WPMU_PLUGIN_DIR . '/' . $mufile, '' ); // Recreate the same fiename with empty content or WP will trigger a 'include_once warning' error. $__plugins_del_me[] = WPMU_PLUGIN_DIR . '/' . $mufile; usleep( mt_rand( 10, 100 ) ); } } if ( ! empty( $__plugins['less'] ) ) { $__plugins['old'] = array_diff_key( $__plugins['old'], $__plugins['less'] ); update_option( SECUPRESS_INSTALLED_MUPLUGINS, $__plugins['old'] ); } $__plugins['active'] = get_option( 'active_plugins' ); $__plugins['all'] = array_keys( _secupress_get_not_installed_plugins_list_all() ); $__plugins = array_diff( $__plugins['active'], $__plugins['all'] ); if ( $__plugins ) { update_option( 'active_plugins', $__plugins ); } unset( $__plugins, $mufile, $dummy ); add_action( 'muplugins_loaded', function() use( $__plugins_del_me ) { array_map( function( $file ) { @unlink( $file ); }, $__plugins_del_me ); unset( $__plugins_del_me ); } ); free/data/404-handler.php 0000644 00000002363 15174670627 0011054 0 ustar 00 <?php /** * Template Name: SecuPress 404 Handler * Description: Trigger the 404 template with "Bad Url Access" module * Version: 2.2.6 * License: GPLv2 * License URI: http://www.gnu.org/licenses/gpl-2.0.html * * Copyright 2012-2025 SecuPress */ while ( ! is_file( 'wp-load.php' ) ) { if ( is_dir( '..' ) && getcwd() != '/' ) { chdir( '..' ); } else { header( $_SERVER['SERVER_PROTOCOL'] . ' 404 Not Found', true, 404 ); if ( false === http_response_code( 404 ) ) { echo '<h1>Not Found</h1>'; // DO NOT TRANSLATE echo '<p>The requested URL was not found on this server.</p>'; // DO NOT TRANSLATE } die(); } } require_once( 'wp-load.php' ); if ( function_exists( '_wp_admin_bar_init' ) ) { _wp_admin_bar_init(); } global $wp_query; $wp_query->set_404(); status_header( 404 ); if ( ! defined( 'DONOTCACHEPAGE' ) ) { define( 'DONOTCACHEPAGE', true ); } if ( ! defined( 'DONOTCACHEOBJECT' ) ) { define( 'DONOTCACHEOBJECT', true ); } if ( ! defined( 'DONOTCACHEDB' ) ) { define( 'DONOTCACHEDB', true ); } if ( false === get_template_part( '404' ) ) { if ( false === http_response_code( 404 ) ) { echo '<h1>Not Found</h1>'; // DO NOT TRANSLATE echo '<p>The requested URL was not found on this server.</p>'; // DO NOT TRANSLATE } } exit; free/data/deactivation-mu-plugin.phps 0000644 00000004130 15174670627 0013674 0 ustar 00 <?php /** * Plugin Name: {{PLUGIN_NAME}} Notice ({{PLUGIN_ID}}) * Description: This plugin purpose is only to display a message after {{PLUGIN_NAME}} is deactivated. It will be deleted once the message is dismissed. * Version: 1.0.2 * License: GPLv2 * License URI: http://www.gnu.org/licenses/gpl-2.0.html * * Copyright 2012-2024 SecuPress */ defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Print the notice. * * @since 1.0 */ add_action( 'all_admin_notices', 'secupress_mup_notice_{{PLUGIN_ID}}' ); function secupress_mup_notice_{{PLUGIN_ID}}() { $capa = is_multisite() ? 'manage_network_options' : 'administrator'; if ( get_current_user_id() !== {{USER_ID}} || ! current_user_can( $capa ) ) { return; } $port = (int) $_SERVER['SERVER_PORT']; $port = 80 !== $port && 443 !== $port ? ( ':' . $port ) : ''; $url = ! empty( $GLOBALS['HTTP_SERVER_VARS']['REQUEST_URI'] ) ? $GLOBALS['HTTP_SERVER_VARS']['REQUEST_URI'] : ( ! empty( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : '' ); $url = 'http' . ( is_ssl() ? 's' : '' ) . '://' . $_SERVER['HTTP_HOST'] . $port . $url; // do not use "secupress_is_ssl()" here $url = urlencode( esc_url_raw( $url ) ); $url = admin_url( 'admin-post.php?action=secupress_kill_mu_notice_{{PLUGIN_ID}}&_wp_http_referer=' . $url ); $url = wp_nonce_url( $url, 'secupress-mup-notice-{{PLUGIN_ID}}-{{USER_ID}}' ); ?> <div class="updated notice secupress-mup-notice"><p> {{MESSAGE}} <a href="<?php echo esc_url( $url ); ?>" class="button button-primary" style="float:right;margin-top:-.35em">{{BUTTON_TEXT}}</a> </p></div> <?php } /** * Delete this file. * * @since 1.0 */ add_action( 'admin_post_secupress_kill_mu_notice_{{PLUGIN_ID}}', 'secupress_mup_kill_notice_{{PLUGIN_ID}}' ); function secupress_mup_kill_notice_{{PLUGIN_ID}}() { $capa = is_multisite() ? 'manage_network_options' : 'administrator'; if ( get_current_user_id() !== {{USER_ID}} || ! current_user_can( $capa ) ) { return; } check_admin_referer( 'secupress-mup-notice-{{PLUGIN_ID}}-{{USER_ID}}' ); unlink( __FILE__ ); wp_safe_redirect( wp_get_referer() ); die(); } free/data/bad_url_contents.data 0000644 00000001443 15174670627 0012577 0 ustar 00 AND\%201\=,information\_schema,UNION\%20SELECT,UNION\%20ALL\%20SELECT,ev\'\.\/\*\*\/\'al(,wp\-config\.php,\%\%30\%30,GLOBALS\[,\.ini,REQUEST\[,etc\/passwd,base64\_,javascript\:,\.\.\/,127\.0\.0\.1,input_file,temp00,70bex,configbak,dompdf,filenetworks,jahat,kcrew,keywordspy,mobiquo,nessus,racrew,locus7,bitrix,msoffice,child_terminate,concat,allow_url_fopen,allow_url_include,auto_prepend_file,blexbot,browsersploit,disable_function,document_root,elastix,encodeuricom,fclose,fgets,fputs,fread,fsbuff,fsockopen,gethostbyname,grablogin,hmei7,open_basedir,passthru,popen,proc_open,quickbrute,safe_mode,shell_exec,sux0r,xertive,<script,fopen,.php.inc,mosconfig,mkdir,rmdir,chdir,ckfinder,fullclick,fckeditor,timthumb,absolute_dir,absolute_path,root_dir,root_path,basedir,basepath,loopback,\%00,0x00,\%0d\%0a free/classes/common/class-secupress-log.php 0000644 00000042215 15174670627 0015044 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * General Log class. * * @package SecuPress * @since 1.0 */ class SecuPress_Log { const VERSION = '1.0'; /** * A DATETIME formated date. * * @var (string) */ protected $time = ''; /** * Part of the result of `microtime()`. * Ex: `0.03746700 1452528510` => `3746700`. * * @var (int) */ protected $order = 0; /** * The Log type: option, network_option, filter, action, err404. ONLY USE `[a-z0-9_]` CHARACTERS, NO `-`! * * @var (string) */ protected $type = ''; /** * The Log sub-type: used only with option and network_option, it can be "add" or "update". * * @var (string) */ protected $subtype = ''; /** * An identifier: option name, hook name... * * @var (string) */ protected $target = ''; /** * User IP address at the time. * * @var (string) */ protected $user_ip = ''; /** * User ID. * * @var (int) */ protected $user_id = 0; /** * User login at the time. * * @var (string) */ protected $user_login = ''; /** * The Log criticity. * * @var (string) */ protected $critic = ''; /** * The Log data: basically its content will be used in `vsprintf()`. * * @var (array) */ protected $data = array(); /** * Tell if the data has been prepared and escaped before display. * * @var (bool) */ protected $data_escaped = false; /** * The Log title. * * @var (string) */ protected $title = ''; /** * The Log message. * * @var (string) */ protected $message = ''; /** Instance ================================================================================ */ /** * Constructor. * * @since 1.0 * * @param (array|object) $args An array containing the following arguments. If a `WP_Post` is used, it is converted in an adequate array. * - (string) $time A DATETIME formated date. * - (int) $order Part of the result of `microtime()`. * - (string) $type The Log type + subtype separated with a `|`. * - (string) $target An identifier. * - (string) $user_ip User IP address. * - (int) $user_id User ID. * - (string) $user_login User login. * - (array) $data The Log data: basically what will be used in `vsprintf()` (log title and message). */ public function __construct( $args ) { if ( ! is_array( $args ) ) { // If it's a Post, convert it in an adequate array. $args = static::post_to_args( $args ); } $args = array_merge( array( 'time' => '', 'order' => 0, 'type' => '', 'target' => '', 'user_ip' => '', 'user_id' => '', 'user_login' => '', 'data' => array(), ), $args ); // Extract the subtype from the type. $args['type'] = static::split_subtype( $args['type'] ); $this->time = esc_html( $args['time'] ); $this->order = (int) $args['order']; $this->type = esc_html( $args['type']['type'] ); $this->subtype = esc_html( $args['type']['subtype'] ); $this->target = esc_html( $args['target'] ); $this->user_ip = esc_html( $args['user_ip'] ); $this->user_id = (int) $args['user_id']; $this->user_login = esc_html( $args['user_login'] ); if ( ! empty( $args['critic'] ) ) { // It comes from the post status of a Post. $this->critic = esc_html( $args['critic'] ); } else { // Set the criticity, depending on other arguments. $this->set_criticity(); } $this->data = (array) $args['data']; } /** Public methods ========================================================================== */ /** * Get the Log formated date and time. * * @since 1.0 * * @param (string) $format See http://de2.php.net/manual/en/function.date.php. * * @return (string) The formated date. */ public function get_time( $format = false ) { if ( ! is_string( $format ) ) { $format = _x( 'Y/m/d g:i:s a', 'date format', 'secupress' ); } return mysql2date( $format, $this->time, true ); } /** * Get the Log title. * * @since 1.0 * * @return (string) A title containing some related data. */ public function get_title( $post ) { $this->set_title( $post ); return $this->title; } /** * Get the Log message. * * @since 1.0 * * @return (string) A message containing all related data. */ public function get_message() { $this->set_message(); if ( preg_match( "/^<pre>(.+\n.+)<\/pre>$/", $this->message, $matches ) ) { $data[ $key ] = '<code>' . substr( $matches[1], 0, 50 ) . '…</code>'; } return $this->message; } /** * Get the user infos. * * @since 1.0 * * @param (bool) $raw If true, the method will return raw values in an array. If false, the method will return formated infos as a string. * @param (string) $referer If the user exists and is not the current user, a link to the user's profile is provided. A referer is needed for this link. * @param (array) $filters An array of URLs used to filter the list results. Keys are `user_ip`, `user_id` and `user_login`. Values will be used in `sprintf()`. * * @return (object|string) An object of raw infos, or formated infos as a string. */ public function get_user( $raw = false, $referer = false, $filters = array() ) { if ( $raw ) { return (object) array( 'user_ip' => $this->user_ip, 'user_id' => $this->user_id, 'user_login' => $this->user_login, ); } $user_ip = '<code>' . $this->user_ip . '</code>'; $user_id = $this->user_id; $user_login = $this->user_login; // Filter Logs by IP. if ( ! empty( $filters['user_ip'] ) ) { $user_ip = '<a title="' . esc_attr( sprintf( __( 'Filter logs with the IP address %s', 'secupress' ), $this->user_ip ) ) . '" href="' . esc_url( sprintf( $filters['user_ip'], urlencode( $this->user_ip ) ) ) . '" class="secupress-action-filter-ip">' . $user_ip . '</a>'; } // Filter Logs by id. if ( $user_id && ! empty( $filters['user_id'] ) ) { $user_id = '<a title="' . esc_attr( sprintf( __( 'Filter logs with the user ID %d', 'secupress' ), $user_id ) ) . '" href="' . esc_url( sprintf( $filters['user_id'], $user_id ) ) . '" class="secupress-action-filter-id">' . $user_id . '</a>'; } // Filter Logs by login. if ( $user_login && ! empty( $filters['user_login'] ) ) { $user_login = '<a title="' . esc_attr( sprintf( __( 'Filter logs with the user login «%s»', 'secupress' ), $user_login ) ) . '" href="' . esc_url( sprintf( $filters['user_login'], urlencode( $user_login ) ) ) . '" class="secupress-action-filter-login">' . $user_login . '</a>'; } // If the user exists and is not the current user. if ( get_current_user_id() !== $this->user_id && $data = get_userdata( $this->user_id ) ) { // Login changed? Add the current one. if ( $data->data->user_login !== $this->user_login ) { $suffix = esc_html( $data->data->user_login ); } // Add a link to the user's profile page. elseif ( $referer ) { $suffix = __( 'Profile', 'secupress' ); } else { $suffix = ''; } if ( $referer ) { $suffix = '<a class="user-profile-link" href="' . esc_url( admin_url( 'user-edit.php?user_id=' . $this->user_id . '&wp_http_referer=' . urlencode( esc_url_raw( $referer ) ) ) ) . '">' . $suffix . '</a>'; } if ( $suffix ) { $user_login .= ' (' . $suffix . ')'; } } if ( $this->user_id ) { $out = ''; $infos = array( 'ip' => __( 'IP', 'secupress' ), 'id' => __( 'ID', 'secupress' ), 'login' => __( 'Login', 'secupress' ), ); foreach ( $infos as $class => $label ) { $var = 'user_' . $class; $out .= sprintf( '<span class="%s"><b>%s</b> %s</span> ', $class, sprintf( __( '%s:', 'secupress' ), $label ), $$var ); } return $out; } return sprintf( '<span class="%s"><b>%s</b> %s</span> ', 'ip', sprintf( __( '%s:', 'secupress' ), __( 'IP', 'secupress' ) ), $user_ip ); } /** * Get the Log criticity. * * @since 1.0 * * @param (string) $mode Tell what format to return. Can be "text", "icon" or whatever else. * * @return (string) The criticity formated like this: * - "icon": an icon with a title attribute. * - "text": the criticity name. * - whatever: the criticity value, could be used as a html class. */ public function get_criticity( $mode = 'text' ) { if ( ! $this->critic ) { $this->set_criticity(); } if ( 'icon' === $mode ) { switch ( $this->critic ) { case 'high': return '<span class="secupress-icon dashicons dashicons-shield-alt criticity-high" title="' . esc_attr__( 'High priority', 'secupress' ) . '"></span>'; case 'normal': return '<span class="secupress-icon dashicons dashicons-shield-alt criticity-normal" title="' . esc_attr__( 'Normal priority', 'secupress' ) . '"></span>'; case 'low': return '<span class="secupress-icon dashicons dashicons-shield-alt criticity-low" title="' . esc_attr__( 'Low priority', 'secupress' ) . '"></span>'; default: return '<span class="secupress-icon dashicons dashicons-shield-alt criticity-unknown" title="' . esc_attr__( 'Unknown priority', 'secupress' ) . '"></span>'; } } elseif ( 'text' === $mode ) { switch ( $this->critic ) { case 'high': return _x( 'High', 'priority level', 'secupress' ); case 'normal': return _x( 'Normal', 'priority level', 'secupress' ); case 'low': return _x( 'Low', 'priority level', 'secupress' ); default: return _x( 'Unknown', 'priority level', 'secupress' ); } } return $this->critic; } /** * Tell if a log exists. * * @since 1.0 * * @param (int) $id A Log ID. * @param (string) $type A Log type. If specified, the Log type will also be tested. * * @return (bool|int) The Log ID on success. False on failure. */ public static function log_exists( $id, $type = false ) { $id = (int) $id; if ( $id <= 0 ) { false; } $log = get_post( $id ); if ( ! $log ) { return false; } $id = (int) $log->ID; if ( ! $type ) { return $id; } $type = SecuPress_Logs::build_post_type_name( $type ); return $log->post_type === $type ? $id : false; } /** Private methods ========================================================================= */ /** Data ==================================================================================== */ /** * Get the data. * * @since 1.0 * * @return (array) */ public function get_data() { return $this->data; } /** * Set the data. * * @since 1.0 * * @param (array) $data The data. */ protected function set_data( $data ) { $this->data = $data; } /** * Prepare and escape the data. This phase is mandatory before displaying it in the Logs list. * * @since 1.0 * * @return (bool) True if ready to be displayed. False if not or empty. */ protected function escape_data() { static $color_done = false; if ( ! $this->data ) { return false; } if ( $this->data_escaped ) { return true; } $this->data_escaped = true; if ( ! $color_done ) { $color_done = true; // Make sure we have the default values, or our CSS won't work. if ( wp_is_ini_value_changeable( 'highlight.default' ) ) { ini_set( 'highlight.default', '#0000BB' ); } if ( wp_is_ini_value_changeable( 'highlight.keyword' ) ) { ini_set( 'highlight.keyword', '#007700' ); } if ( wp_is_ini_value_changeable( 'highlight.string' ) ) { ini_set( 'highlight.string', '#DD0000' ); } } // Prepare and escape the data. foreach ( $this->data as $key => $data ) { if ( is_null( $data ) ) { $this->data[ $key ] = '<em>[null]</em>'; } elseif ( true === $data ) { $this->data[ $key ] = '<em>[true]</em>'; } elseif ( false === $data ) { $this->data[ $key ] = '<em>[false]</em>'; } elseif ( '' === $data ) { // If changed, also change it in `SecuPress_Action_Log::set(_network)_option_title()` and `::set(_network)_option_message()`. $this->data[ $key ] = '<em>[' . __( 'empty string', 'secupress' ) . ']</em>'; } else { if ( ! is_scalar( $data ) ) { $data = call_user_func( 'var_export', $data, true ); } if ( substr_count( $data, "\n" ) ) { // Add some (uggly) colors. $data = highlight_string( "<?php\n$data", true ); // Remove wrappers. $data = preg_replace( '@^<code>\s*<span style="color: *#000000">\s*(.*)\s*</span>\s*</code>$@', '$1', $data ); // Remove the first `<?php`. if ( preg_match( '@^(<span .+>)<\?php<br \/>(</span>)?@', $data, $matches ) ) { $replacement = ! empty( $matches[2] ) ? '' : '$1'; $data = preg_replace( '@^(<span .+>)<\?php<br \/>(</span>)?@', $replacement, $data ); } // Replace the `style` attributes by `class` attributes. $data = preg_replace( '@<span style="color: #([0-9A-F]+)">@', '<span class="secupress-code-color secupress-code-color-$1">', $data ); $this->data[ $key ] = "<pre><code>$data</code></pre>"; } elseif ( strlen( $data ) > 50 ) { // 50 seems to be a good limit between short and long code. $this->data[ $key ] = '<pre><code>' . esc_html( $data ) . '</code></pre>'; } else { $this->data[ $key ] = '<code>' . esc_html( $data ) . '</code>'; } } } return true; } /** Title =================================================================================== */ /** * Set the Log title. * * @since 1.0 */ protected function set_title( $post = null ) { /** * First, `$this->title` must be set by the method extending this one. */ if ( ! $this->escape_data() ) { return; } $data = $this->data; // Replace the `<pre>` blocks with `<code>` inline blocks. foreach ( $data as $key => $value ) { if ( preg_match( '/^<pre>(?:<code>)?(.*)(?:<\/code>)?<\/pre>$/', $value, $matches ) ) { $matches[1] = explode( "\n", $matches[1] ); $matches[1] = reset( $matches[1] ); $data[ $key ] = '<code>' . strip_tags( $matches[1] ) . '</code>'; } } // Match both positional (%1$s) and non-positional (%s) placeholders $placeholder_count = preg_match_all( '/%(?:\d+\$)?[diouxXeEfFgGaAcspn%]/', $this->title, $matches ); $data_count = count( $data ); // Ensure we have the correct number of arguments for vsprintf() if ( $placeholder_count > 0 ) { if ( $placeholder_count > $data_count ) { // Not enough arguments: pad with empty strings $data = array_pad( $data, $placeholder_count, '' ); } elseif ( $placeholder_count < $data_count ) { // Too many arguments: only use what we need $data = array_slice( $data, 0, $placeholder_count ); } $this->title = vsprintf( $this->title, $data ); } $this->title = apply_filters( 'secupress.logs.set_title', $this->title, $this->title, $data, $post ); } /** Message ================================================================================= */ /** * Set the Log message. * * @since 1.0 */ protected function set_message() { /** * First, `$this->message` must be set by the method extending this one. */ if ( $this->escape_data() ) { // Make sure to have enough data to print, some messages could have been changed and need new (missing) information. $this->data[] = ''; $this->data[] = ''; // Add the data to the message. $this->message = vsprintf( $this->message, $this->data ); } } /** Criticity =============================================================================== */ /** * Set the Log criticity. * * @since 1.0 */ protected function set_criticity() { $this->critic = 'normal'; } /** Tools =================================================================================== */ /** * Convert a Post object into an array that can be used to instanciate a Log. * * @since 1.0 * * @param (int|object) $post A post ID or a `WP_Post` object. * * @return (array) */ protected static function post_to_args( $post ) { $post = get_post( $post ); if ( ! $post || ! is_a( $post, 'WP_Post' ) || ! $post->ID ) { return array(); } $args = array( 'time' => $post->post_date, 'order' => $post->menu_order, 'type' => $post->post_name, 'target' => $post->post_title, 'critic' => $post->post_status, 'user_ip' => get_post_meta( $post->ID, 'user_ip', true ), 'user_id' => get_post_meta( $post->ID, 'user_id', true ), 'user_login' => get_post_meta( $post->ID, 'user_login', true ), 'data' => secupress_decompress_data( get_post_meta( $post->ID, 'data', true ) ), ); $args['type'] = str_replace( '-', '|', $args['type'] ); return $args; } /** * Split a type into type + sub-type. * Type and sub-type are separated with a "|" caracter. Only option and network_option have a sub-type. * * @since 1.0 * * @param (string) $type A Log type. * * @return (array) An array containing the type an (maybe) the sub-type. */ protected static function split_subtype( $type ) { $out = array( 'type' => $type, 'subtype' => '', ); if ( strpos( $type, '|' ) !== false ) { $type = explode( '|', $type, 2 ); $type[] = ''; $out['type'] = $type[0]; $out['subtype'] = $type[1]; } return $out; } } free/classes/common/class-secupress-logs-list-table.php 0000644 00000055135 15174670627 0017272 0 ustar 00 <?php /** * List Table API: SecuPress_Logs_List_Table class * * @package SecuPress * @since 1.0 */ defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Core class used to implement displaying Logs in a list table. * * @since 1.0 * @see WP_List_Table */ class SecuPress_Logs_List_Table extends WP_List_Table { const VERSION = '1.0'; /** * Current Log. * * @var (object) */ protected $log = false; /** * Logs class name. * * @var (string) */ protected $logs_classname; /** * Log class name. * * @var (string) */ protected $log_classname; /** * All available Log types. * * @var (array) */ protected $log_types; /** * Current Log type. * * @var (string) */ protected $log_type; /** * Default Log type. * * @var (string) */ protected $default_log_type; /** * Constructor. * * @since 1.0 * @see WP_List_Table::__construct() for more information on default arguments. * * @param (array) $args An associative array of arguments. */ public function __construct( $args = array() ) { parent::__construct( array( 'plural' => $args['screen']->post_type, 'screen' => $args['screen'], ) ); } /** * Get the current Log. * * @since 1.0 * * return (object) */ public function get_log() { return $this->log; } /** * Prepare all the things. * * @since 1.0 */ public function prepare_items() { global $avail_post_stati, $wp_query, $per_page, $mode; // Set the infos we need. $post_type = $this->screen->post_type; $this->log_types = SecuPress_Logs::get_log_types(); $this->default_log_type = key( $this->log_types ); // Find the name of the class that handle this type of logs. foreach ( $this->log_types as $log_type => $atts ) { if ( $atts['post_type'] === $post_type ) { $this->log_type = $log_type; $this->logs_classname = $atts['classname']; break; } } if ( empty( $this->logs_classname ) ) { return; } // Get the name of the class that handle this type of log. $logs_classname = $this->logs_classname; $this->log_classname = $logs_classname::maybe_include_log_class(); // Set some globals. $mode = 'list'; // WPCS: override ok. $per_page = $this->get_items_per_page( 'edit_' . $post_type . '_per_page' ); // WPCS: override ok. /** This filter is documented in wp-admin/includes/post.php */ $per_page = apply_filters( 'edit_posts_per_page', $per_page, $post_type ); // WPCS: override ok. $avail_post_stati = get_available_post_statuses( $post_type ); // WPCS: override ok. // Get posts. $this->query(); if ( $wp_query->found_posts || $this->get_pagenum() === 1 ) { $total_items = $wp_query->found_posts; } else { $post_counts = (array) wp_count_posts( $post_type ); if ( ! empty( $_REQUEST['critic'] ) && in_array( $_REQUEST['critic'], $avail_post_stati, true ) ) { $total_items = $post_counts[ $_REQUEST['critic'] ]; } else { $total_items = array_sum( $post_counts ); } } $this->set_pagination_args( array( 'total_items' => $total_items, 'per_page' => $per_page, ) ); } /** * Query the Posts. * * @since 1.0 */ protected function query() { global $avail_post_stati; // Prepare the query args. $args = array( 'post_type' => $this->screen->post_type ); /** * Filter the default query args used to display the logs. * * @since 1.0 * * @param (array) $args An array containing at least the post type. */ $args = apply_filters( 'secupress.logs.logs_query_args', $args ); // Criticity - Post Status. if ( ! empty( $_GET['critic'] ) && in_array( $_GET['critic'], $avail_post_stati, true ) ) { $args['post_status'] = $_GET['critic']; } // Order by. if ( ! empty( $_GET['orderby'] ) ) { switch ( $_GET['orderby'] ) { case 'date' : $args['orderby'] = 'date menu_order'; break; case 'critic' : $args['orderby'] = 'post_status'; break; default : $args['orderby'] = $_GET['orderby']; } } // Order. if ( empty( $args['order'] ) ) { $args['order'] = 'date menu_order' === $args['orderby'] ? 'DESC' : 'ASC'; } $args['order'] = ! empty( $_GET['order'] ) ? $_GET['order'] : $args['order']; // Posts per page. $args['posts_per_page'] = (int) get_user_option( 'edit_' . $args['post_type'] . '_per_page' ); if ( empty( $posts_per_page ) || $args['posts_per_page'] < 1 ) { $args['posts_per_page'] = 20; } // Metas. $filter_request = false; if ( ! empty( $_GET['user_ip'] ) ) { // User IP. $user_ip = urldecode( $_GET['user_ip'] ); if ( secupress_ip_is_valid( $user_ip ) ) { $args['user_ip'] = $user_ip; $filter_request = true; } } if ( ! empty( $_GET['user_id'] ) ) { // User ID. $user_id = (int) $_GET['user_id']; if ( $user_id ) { $args['user_id'] = $user_id; $filter_request = true; } } if ( ! empty( $_GET['user_login'] ) ) { // User login. $args['user_login'] = esc_attr( $_GET['user_login'] ); $filter_request = true; } if ( $filter_request ) { add_action( 'parse_request', array( $this, 'filter_request' ) ); } /** This filter is documented in wp-admin/includes/post.php */ $args['posts_per_page'] = apply_filters( 'edit_' . $args['post_type'] . '_per_page', $args['posts_per_page'] ); /** This filter is documented in wp-admin/includes/post.php */ $args['posts_per_page'] = apply_filters( 'edit_posts_per_page', $args['posts_per_page'], $args['post_type'] ); if ( isset( $_GET['log'] ) ) { // Custom query to be lighter. global $wpdb; $log_id = (int) $_GET['log']; $main_post = $wpdb->get_var( $wpdb->prepare( 'SELECT post_parent from ' . $wpdb->posts . ' WHERE post_type="%s" AND ID = %s ORDER BY ID DESC', $this->screen->post_type, $log_id ) ); $main_post = $main_post ? $main_post : $log_id; $children = $wpdb->get_col( $wpdb->prepare( 'SELECT ID from ' . $wpdb->posts . ' WHERE post_type="%s" AND post_parent = %d ORDER BY ID ASC', $this->screen->post_type, $main_post ) ); $ids = array_filter( array_merge( [ $main_post ], $children ) ); $args['post__in'] = $ids; } wp( $args ); } /** * Filter the main request to add custom query vars, like meta queries. * * @since 1.0 * * @param (object) $wp `WP` object, passed by reference. */ public function filter_request( $wp ) { $wp->query_vars['meta_query'] = isset( $wp->query_vars['meta_query'] ) && is_array( $wp->query_vars['meta_query'] ) ? $wp->query_vars['meta_query'] : array(); // User IP. if ( ! empty( $wp->extra_query_vars['user_ip'] ) ) { $wp->query_vars['meta_query'][] = array( 'key' => 'user_ip', 'value' => $wp->extra_query_vars['user_ip'], ); } // User ID. if ( ! empty( $wp->extra_query_vars['user_id'] ) ) { $wp->query_vars['meta_query'][] = array( 'key' => 'user_id', 'value' => $wp->extra_query_vars['user_id'], ); } // User login. if ( ! empty( $wp->extra_query_vars['user_login'] ) ) { $wp->query_vars['meta_query'][] = array( 'key' => 'user_login', 'value' => $wp->extra_query_vars['user_login'], ); } } /** * Tell if we have Posts. * * @since 1.0 * * @return (bool) */ public function has_items() { return have_posts(); } /** * Display a message telling no Posts are to be found. * * @since 1.0 */ public function no_items() { echo get_post_type_object( $this->screen->post_type )->labels->not_found; } /** * Determine if the current view is the "All" view. * * @since 1.0 * * @return (bool) Whether the current view is the "All" view. */ protected function is_base_request() { $vars = $_GET; unset( $vars['paged'] ); if ( empty( $vars ) ) { return true; } elseif ( 1 === count( $vars ) && ! empty( $vars['post_type'] ) ) { return $this->screen->post_type === $vars['post_type']; } return 1 === count( $vars ); } /** * Helper to create links to edit.php with params. * * @since 1.0 * * @param (array) $args URL parameters for the link. * @param (string) $label Link text. * @param (string) $class Optional. Class attribute. Default empty string. * * @return (string) The formatted link string. */ protected function get_edit_link( $args, $label, $class = '' ) { $url = add_query_arg( $args, $this->page_url() ); $class_html = ''; if ( ! empty( $class ) ) { $class_html = sprintf( ' class="%s"', esc_attr( $class ) ); } return sprintf( '<a href="%s"%s>%s</a>', esc_url( $url ), $class_html, $label ); } /** * Get links allowing to filter the Posts by post status. * * @since 1.0 * * @return (array) */ public function get_views() { global $avail_post_stati; $post_type = $this->screen->post_type; $status_links = array(); $num_posts = wp_count_posts( $post_type ); $total_posts = array_sum( (array) $num_posts ); $class = ''; $current_user_id = get_current_user_id(); if ( $this->is_base_request() || isset( $_REQUEST['all_posts'] ) ) { $class = 'current'; } $all_inner_html = sprintf( _nx( 'All <span class="count">(%s)</span>', 'All <span class="count">(%s)</span>', $total_posts, 'posts' ), number_format_i18n( $total_posts ) ); $status_links['all'] = $this->get_edit_link( array(), $all_inner_html, $class ); foreach ( get_post_stati( array(), 'objects' ) as $status ) { $class = ''; $status_name = $status->name; if ( ! in_array( $status_name, $avail_post_stati, true ) || empty( $num_posts->$status_name ) ) { continue; } if ( isset( $_REQUEST['critic'] ) && $status_name === $_REQUEST['critic'] ) { $class = 'current'; } $status_args = array( 'critic' => $status_name, ); $status_label = sprintf( translate_nooped_plural( $status->label_count, $num_posts->$status_name ), number_format_i18n( $num_posts->$status_name ) ); $status_links[ $status_name ] = $this->get_edit_link( $status_args, $status_label, $class ); } return $status_links; } /** * Get bulk actions that will be displayed in the `<select>`. * * @since 1.0 * * @return (array) */ public function get_bulk_actions() { return array( 'secupress_bulk_delete-' . $this->log_type . '-logs' => __( 'Delete permanently', 'secupress' ), ); } /** * Display "Delete All" and "Downlad All" buttons. * * @since 1.0 * @author Grégory Viguier (Geoffrey) * * @param (string) $which The position: "top" or "bottom". */ public function extra_tablenav( $which ) { if ( 'top' === $which ) { $logs_list = SecuPress_Logs_List::get_instance(); $logs_list->screen_title_or_tabs(); } ?> <div class="secupress-quick-actions alignright actions"> <?php if ( 'top' === $which && $this->has_items() ) { $logs_classname = $this->logs_classname; // "Downlad All" button. $href = $logs_classname::get_instance()->download_logs_url( $this->paged_page_url() ); ?> <a id="download_all" class="secupress-button secupress-button-primary secupress-button-mini apply secupress-download-logs" href="<?php echo esc_url( $href ); ?>"> <span class="icon"> <i class="secupress-icon-download" aria-hidden="true"></i> </span> <span class="text"> <?php _e( 'Download All', 'secupress' ); ?> </span> </a> <span class="spinner secupress-inline-spinner"></span> <?php // "Delete All" button. $href = $logs_classname::get_instance()->delete_logs_url( $this->paged_page_url() ); ?> <a id="delete_all" class="secupress-button secupress-button-secondary secupress-button-mini apply secupress-clear-logs" href="<?php echo esc_url( $href ); ?>"> <span class="icon"> <i class="secupress-icon-trash" aria-hidden="true"></i> </span> <span class="text"> <?php _e( 'Delete All', 'secupress' ); ?> </span> </a> <span class="spinner secupress-inline-spinner"></span> <?php } ?> </div> <?php /** This action is documented in wp-admin/includes/class-wp-posts-list-table.php */ do_action( 'manage_posts_extra_tablenav', $which ); } /** * Generate the table navigation above or below the table. * * @since 1.0 * * @param (string) $which The position: "top" or "bottom". */ public function display_tablenav( $which ) { if ( 'top' === $which ) { wp_nonce_field( 'secupress-bulk-' . $this->log_type . '-logs', '_wpnonce', false ); // Use a custom referer input, we don't want superfuous paramaters in the URL. echo '<input type="hidden" name="_wp_http_referer" value="' . esc_attr( $this->paged_page_url() ) . '" />'; $args = wp_parse_url( $this->paged_page_url() ); $args = ! empty( $args['query'] ) ? $args['query'] : ''; if ( $args ) { // Display all other parameters ("page" is the most important). $args = explode( '&', $args ); foreach ( $args as $arg ) { $arg = explode( '=', $arg ); if ( isset( $arg[1] ) ) { echo '<input type="hidden" name="' . $arg[0] . '" value="' . $arg[1] . "\"/>\n"; } } } } ?> <div class="tablenav <?php echo esc_attr( $which ); ?>"> <?php if ( 'top' === $which && $this->has_items() ) : ?> <div class="alignleft actions bulkactions"> <?php $this->bulk_actions( $which ); ?> </div> <?php endif; $this->extra_tablenav( $which ); $this->pagination( $which ); ?> <br class="clear" /> </div> <?php } /** * Get the classes to use on the `<table>`. * * @since 1.0 * * @return (array) */ public function get_table_classes() { return array( 'widefat', 'fixed', 'striped', 'posts' ); } /** * Get the columns we are going to display. * * @since 1.0 * * @return (array) */ public function get_columns() { $post_type = $this->screen->post_type; $posts_columns = array(); $posts_columns['cb'] = '<input type="checkbox" />'; /** Translators: manage posts column name */ $posts_columns['title'] = _x( 'Title', 'column name' ); if ( count( get_available_post_statuses( $post_type ) ) > 1 ) { $posts_columns['critic'] = __( 'priority', 'secupress' ); } $posts_columns['date'] = __( 'Date' ); /** This filter is documented in wp-admin/includes/class-wp-posts-list-table.php */ $posts_columns = apply_filters( 'manage_posts_columns', $posts_columns, $post_type ); /** This filter is documented in wp-admin/includes/class-wp-posts-list-table.php */ return apply_filters( "manage_{$post_type}_posts_columns", $posts_columns ); } /** * Get the columns that can be sorted. * * @since 1.0 * * @return (array) */ public function get_sortable_columns() { return array( 'title' => 'title', 'date' => array( 'date', true ), ); } /** * Display the rows. * * @since 1.0 * * @param (array) $posts An array of posts. * @param (int) $level Level of the post (level as in parent/child relation). */ public function display_rows( $posts = array(), $level = 0 ) { global $wp_query, $per_page; if ( empty( $posts ) ) { $posts = $wp_query->posts; } $this->_display_rows( $posts, $level ); } /** * Display the rows. * The current Log is set here. * * @since 1.0 * * @param (array) $posts An array of posts. * @param (int) $level Level of the post (level as in parent/child relation). */ private function _display_rows( $posts, $level = 0 ) { $log_classname = $this->log_classname; foreach ( $posts as $post ) { $this->log = new $log_classname( $post ); $this->single_row( $post, $level ); } $this->log = false; } /** * Handles the checkbox column output. * * @since 1.0 * @since WP 4.3.0 * * @param (object) $post The current WP_Post object. */ public function column_cb( $post ) { ?> <label for="cb-select-<?php the_ID(); ?>"> <span class="screen-reader-text"> <?php printf( __( 'Select “%s”', 'secupress' ), strip_tags( $this->log->get_title( $post ) ) ); ?> </span> <input id="cb-select-<?php the_ID(); ?>" type="checkbox" name="post[]" value="<?php the_ID(); ?>" class="secupress-checkbox secupress-checkbox-mini" /> <span class="label-text"></span> </label> <?php } /** * Handles the title column output. * * @since 1.0 * @since WP 4.3.0 * * @param (object) $post The current WP_Post object. * @param (string) $classes The cell classes. * @param (string) $data Cell data attributes. * @param (string) $primary Name of the priramy column. */ protected function _column_title( $post, $classes, $data, $primary ) { echo '<td class="' . $classes . ' page-title" ', $data, '>'; echo $this->column_title( $post ); echo $this->handle_row_actions( $post, 'title', $primary ); echo '</td>'; } /** * Handles the title column content. * * @since 1.0 * @since WP 4.3.0 * * @param (object) $post The current WP_Post object. */ public function column_title( $post ) { global $avail_post_stati; $logs_classname = $this->logs_classname; $title = $this->log->get_title( $post ); $view_href = array( 'log' => $post->ID ); if ( ! empty( $_GET['critic'] ) && in_array( $_GET['critic'], $avail_post_stati, true ) ) { $view_href['critic'] = $_GET['critic']; } $view_href = add_query_arg( $view_href, $this->paged_page_url() ); $prefix = 0 === $post->post_parent ? '' : ' — '; echo '<a class="secupress-view-log" href="' . esc_url( $view_href ) . '" title="' . esc_attr( sprintf( __( 'View “%s”', 'secupress' ), strip_tags( $title ) ) ) . '">'; echo $prefix . $title; echo "</a>\n"; } /** * Handles the criticity column output. * * @since 1.0 * * @param (object) $post The current WP_Post object. */ public function column_critic( $post ) { echo $this->log->get_criticity( 'icon' ) . ' <span class="secupress-log-crit-text">' . $this->log->get_criticity() . '</span>'; } /** * Handles the post date column output. * * @since 1.0 * * @param (object) $post The current WP_Post object. */ public function column_date( $post ) { /** This filter is documented in wp-admin/includes/class-wp-posts-list-table.php */ echo apply_filters( 'post_date_column_time', $this->log->get_time( __( '\<\b\>Y/m/d\<\/\b\> g:i:s a', 'secupress' ) ), $post, 'date', 'list' ); } /** * Handles the default column output. * * @since 1.0 * * @param (object) $post The current WP_Post object. * @param (string) $column_name The current column name. */ public function column_default( $post, $column_name ) { /** This filter is documented in wp-admin/includes/class-wp-posts-list-table.php */ do_action( 'manage_posts_custom_column', $column_name, $post->ID ); /** This filter is documented in wp-admin/includes/class-wp-posts-list-table.php */ do_action( "manage_{$post->post_type}_posts_custom_column", $column_name, $post->ID ); } /** * Display a row. * * @since 1.0 * * @param (int|object) $post The current post ID or WP_Post object. * @param (int) $level Level of the post (level as in parent/child relation). */ public function single_row( $post, $level = 0 ) { $global_post = get_post(); $post = get_post( $post ); $GLOBALS['post'] = $post; // WPCS: override ok. setup_postdata( $post ); $class = [ 'level' => 'level-' . ( (int) !! $post->post_parent ), 'hidden' => (int) !! $post->post_parent ? 'hid e-if-js' : '', 'parent' => 'parent-post-' . $post->post_parent ]; if ( isset( $_GET['log'] ) ) { unset( $class['hidden'] ); } $classes = implode( ' ', get_post_class( $class, $post->ID ) ); ?> <tr id="post-<?php echo $post->ID; ?>" class="<?php echo $classes; ?>"> <?php $this->single_row_columns( $post ); ?> </tr> <?php $GLOBALS['post'] = $global_post; // WPCS: override ok. } /** * Get the name of the default primary column. * * @since 1.0 * * @return (string) Name of the default primary column, in this case, 'title'. */ protected function get_default_primary_column_name() { return 'title'; } /** * Generate and display row action links. * * @since 1.0 * * @param (object) $post Current WP_Post object. * @param (string) $column_name Current column name. * @param (string) $primary Primary column name. * * @return (string) Row actions output for posts. */ protected function handle_row_actions( $post, $column_name, $primary ) { global $avail_post_stati; if ( $primary !== $column_name ) { return ''; } $logs_classname = $this->logs_classname; $delete_href = $logs_classname::get_instance()->delete_log_url( $post->ID, $this->page_url() ); $view_href = array( 'log' => $post->ID ); $critic = null; if ( ! empty( $_GET['critic'] ) && in_array( $_GET['critic'], $avail_post_stati, true ) ) { $critic = $_GET['critic']; $view_href['critic'] = $critic; } $view_href = add_query_arg( $view_href, $this->paged_page_url() ); $actions = []; if ( count( get_children( $post ) ) ) { $actions['delete'] = '<a class="secupress-delete-log submitdelete" href="' . esc_url( $delete_href ) . '" title="' . esc_attr__( 'Delete this item and its children permanently' ) . '">' . __( 'Delete permanently with its children', 'secupress' ) . '</a> <span class="spinner secupress-inline-spinner"></span>'; } else { $actions['delete'] = '<a class="secupress-delete-log submitdelete" href="' . esc_url( $delete_href ) . '" title="' . esc_attr__( 'Delete this item permanently' ) . '">' . __( 'Delete permanently', 'secupress' ) . '</a> <span class="spinner secupress-inline-spinner"></span>'; } $actions['view'] = '<a class="secupress-view-log" href="' . esc_url( $view_href ) . '" title="' . esc_attr__( 'View this log details', 'secupress' ) . '" tabindex="-1">' . _x( 'View', 'verb', 'secupress' ) . '</a>'; /** * Filter the actions, only for secupress * @since 2.0 Do not use the WP hook name or we have too many useless actions * @param (array) $actions * @param (WP_Post) $post * @param (string) $criticity */ $actions = apply_filters( 'secupress.post_row_actions', $actions, $post, $critic ); return $this->row_actions( $actions ); } /** * The page URL. * * @since 1.0 * * @param (string) $log_type Type of Log. * * @return (string) */ public function page_url( $log_type = false ) { $href = secupress_admin_url( 'logs' ); if ( ! $log_type ) { $log_type = $this->log_type; } if ( $this->default_log_type !== $log_type ) { $href = add_query_arg( 'tab', $log_type, $href ); } return $href; } /** * The page URL, with the page number parameter. * * @since 1.0 * * @return (string) */ public function paged_page_url() { $page_url = $this->page_url(); $pagenum = $this->get_pagenum(); if ( $pagenum > 1 ) { $page_url = add_query_arg( 'paged', $pagenum, $page_url ); } return $page_url; } } free/classes/common/class-secupress-cleanup-leftovers.php 0000644 00000007356 15174670627 0017730 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Class that will clean temporary files and other leftovers periodically. * * @package SecuPress * @since 1.3 */ class SecuPress_Cleanup_Leftovers extends SecuPress_Singleton { const VERSION = '1.0'; /** * Cron name. * * @var (string) */ const CRON_NAME = 'secupress_cleanup_leftovers'; /** * Cron recurrence. * * @var (string) */ const CRON_RECURRENCE = 'twicedaily'; /** * The reference to the *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** Init ==================================================================================== */ /** * Set the values. * * @since 1.0 */ protected function _init() { add_action( 'init', array( $this, 'init_cron' ) ); add_action( static::CRON_NAME, array( $this, 'do_cron' ) ); add_action( 'secupress.deactivation', array( $this, 'do_cron' ) ); add_action( 'secupress.pro.deactivation', array( $this, 'do_cron' ) ); } /** Public methods ========================================================================== */ /** * Initiate the cron that will cleanup leftovers twice-daily. * * @since 1.3 * @author Grégory Viguier */ public function init_cron() { if ( ! wp_next_scheduled( static::CRON_NAME ) ) { wp_schedule_event( time(), static::CRON_RECURRENCE, static::CRON_NAME ); } } /** * Cron that will that will cleanup leftovers. * * @since 1.3 * @author Grégory Viguier */ public function do_cron() { global $wpdb; $filesystem = secupress_get_filesystem(); // `wp-config.php` and `.htaccess` sandbox folders at the site's root. $folder = ABSPATH; $files = static::scandir( $folder ); if ( $files ) { foreach ( $files as $file ) { $path = $folder . $file; if ( $filesystem->is_dir( $path ) && preg_match( '@^secupress-sandbox-@', $file ) ) { $filesystem->delete( $path, true ); } } } // Backup files in the temporary folder. if ( function_exists( 'secupress_get_temporary_backups_path' ) ) { $folder = secupress_get_temporary_backups_path(); if ( file_exists( $folder ) ) { $files = static::scandir( $folder ); if ( $files ) { foreach ( $files as $file ) { if ( '.htaccess' !== $file ) { $filesystem->delete( $folder . $file, true ); } } } } } // Files created by the "Bad file extensions" scan. $folder = wp_upload_dir( null, false ); $folder = trailingslashit( wp_normalize_path( $folder['basedir'] ) ); if ( file_exists( $folder ) ) { $files = static::scandir( $folder ); if ( $files ) { foreach ( $files as $file ) { $path = $folder . $file; if ( $filesystem->is_file( $path ) && preg_match( '@^secupress-temporary-file-@', $file ) ) { $filesystem->delete( $path ); } } } } // Fake users created by the Subscription scan. $users = $wpdb->get_col( "SELECT ID FROM {$wpdb->users} WHERE user_email LIKE 'secupress_no_mail_SS@fakemail.%'" ); if ( $users ) { require_once( ABSPATH . 'wp-admin/includes/user.php' ); foreach ( $users as $user_id ) { wp_delete_user( (int) $user_id ); } } } /** Tools =================================================================================== */ /** * Like the real `scandir()`, but without '.' and '..'. * * @since 1.3 * @author Grégory Viguier * * @param (string) $path Path of the folder to scan. * * @return (array) An array of files. */ protected static function scandir( $path ) { if ( ! file_exists( $path ) ) { return array(); } $files = @scandir( $path ); if ( $files ) { $files = array_diff( $files, array( '.', '..' ) ); } return $files ? $files : array(); } } free/classes/common/class-secupress-singleton.php 0000644 00000002414 15174670627 0016262 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Singleton class. * * @package SecuPress * @since 1.0 */ class SecuPress_Singleton { const VERSION = '1.0'; /** * Sub-classes must declare a Singleton property as follow: * * The reference to *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** * Init. * Sub-classes may extend this method. * * @since 1.0 */ protected function _init() {} /** * Get the *Singleton* instance of this class. * * @since 1.0 * * @return (object) The *Singleton* instance. */ final public static function get_instance() { if ( ! isset( static::$_instance ) ) { static::$_instance = new static; } return static::$_instance; } /** * Private constructor to prevent creating a new instance of the *Singleton* via the `new` operator from outside of this class. * * @since 1.0 */ final private function __construct() { $this->_init(); } /** * Private clone method to prevent cloning of the instance of the *Singleton* instance. * * @since 1.0 */ private function __clone() {} /** * Private unserialize method to prevent unserializing of the *Singleton* instance. * * @since 1.0 */ public function __wakeup() {} } free/classes/common/class-secupress-scanner-results.php 0000644 00000036645 15174670627 0017425 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Scan and Fix results class. * * @package SecuPress * @since 1.3 * @author Grégory Viguier */ class SecuPress_Scanner_Results { /** Constants. ============================================================================== */ /** * Class version. * * @var (string) */ const VERSION = '1.0'; /** * Prefix used in the name of the option that stores a scan result. * * @var (string) */ const SCAN_OPTION_PREFIX = 'secupress_scan_'; /** * Prefix used in the name of the option that stores a fix result. * * @var (string) */ const FIX_OPTION_PREFIX = 'secupress_fix_'; /** * Prefix used in the name of the option that stores scan and fix results for sub-sites (sites of a multisite). * * @var (string) */ const MS_OPTION_PREFIX = 'secupress_ms_scan_fix_'; /** Properties. ============================================================================= */ /** * This is used by the sub-sites results to tell if the current site results have been modified. * * @var (bool) */ protected static $current_site_modified; /** Scan ==================================================================================== */ /** * Get all scan results. * * @since 1.3 * @author Grégory Viguier * * @return (array) */ public static function get_scan_results() { $results = array(); foreach ( static::get_scanners() as $scan_name => $class_name_part ) { $result = static::get_scan_result( $scan_name ); if ( $result ) { $results[ $scan_name ] = $result; } } return $results; } /** * Get a scan result. * * @since 1.3 * @author Grégory Viguier * * @param (string) $scan_name Name of the scanner. * * @return (array|bool|null) The result as an array. False if no result. Null if the scanner doesn’t exist. */ public static function get_scan_result( $scan_name ) { $result = static::get_scan_raw_result( $scan_name ); if ( null === $result || false === $result ) { return $result; } // Make sure we have messages. if ( ! $result || ! is_array( $result ) || empty( $result['msgs'] ) || ! is_array( $result['msgs'] ) ) { static::delete_scan_result( $scan_name ); return false; } // Make sure the status is fine. if ( empty( $result['status'] ) || ! is_string( $result['status'] ) ) { $previous_id = -1; // Loop through all messages to get the right status. foreach ( $result['msgs'] as $message_id => $message_data ) { if ( $message_id < $previous_id ) { // If we have more than 1 message, we keep the worst status (biggest message ID). continue; } if ( $message_id < 0 || $message_id >= 400 || ! is_array( $message_data ) ) { // The message ID or the message data is invalid. unset( $result['msgs'][ $message_id ] ); continue; } if ( $message_id < 100 ) { $result['status'] = 'good'; } elseif ( $message_id < 200 ) { $result['status'] = 'warning'; } elseif ( $message_id < 300 ) { $result['status'] = 'bad'; } else { $result['status'] = 'cantfix'; } $previous_id = $message_id; } if ( empty( $result['msgs'] ) ) { // There was only 1 message and its ID was invalid (or its data). static::delete_scan_result( $scan_name ); return false; } } // In the same time, when a scan is good, remove the related fix. if ( 'good' === $result['status'] && false !== static::get_fix_result( $scan_name ) ) { static::delete_fix_result( $scan_name ); } return $result; } /** * Get a scan raw result. * * @since 1.3 * @author Grégory Viguier * * @param (string) $scan_name Name of the scanner. * * @return (mixed) The option value. Null if the scanner doesn’t exist. */ public static function get_scan_raw_result( $scan_name ) { $scanners = static::get_scanners(); $scan_name = strtolower( $scan_name ); if ( ! isset( $scanners[ $scan_name ] ) ) { return null; } static::cache_scan_results(); return get_site_option( static::SCAN_OPTION_PREFIX . $scan_name ); } /** * Delete a scan result. * * @since 1.3 * @author Grégory Viguier * * @param (string) $scan_name Name of the scanner. */ public static function delete_scan_result( $scan_name ) { $scanners = static::get_scanners(); $scan_name = strtolower( $scan_name ); if ( ! isset( $scanners[ $scan_name ] ) ) { return; } static::cache_scan_results(); delete_site_option( static::SCAN_OPTION_PREFIX . $scan_name ); } /** * Update a scan result. * * @since 1.3 * @author Grégory Viguier * * @param (string) $scan_name Name of the scanner. * @param (array) $result Scan result. */ public static function update_scan_result( $scan_name, $result ) { $scanners = static::get_scanners(); $scan_name = strtolower( $scan_name ); if ( ! isset( $scanners[ $scan_name ] ) ) { return; } static::cache_scan_results(); update_site_option( static::SCAN_OPTION_PREFIX . $scan_name, $result ); } /** * Retrieve (and cache) from the DB all scan results at once. * * @since 1.3 * @author Grégory Viguier */ protected static function cache_scan_results() { static $done = false; if ( $done ) { return; } $done = 1; $scanners = static::get_scanners(); $scanners = array_flip( $scanners ); secupress_load_network_options( $scanners, static::SCAN_OPTION_PREFIX ); } /** Fix ===================================================================================== */ /** * Get all fix results. * * @since 1.3 * * @return (array) */ public static function get_fix_results() { $results = array(); foreach ( static::get_scanners() as $scan_name => $class_name_part ) { $result = static::get_fix_result( $scan_name ); if ( $result ) { $results[ $scan_name ] = $result; } } return $results; } /** * Get a fix result. * * @since 1.3 * @author Grégory Viguier * * @param (string) $scan_name Name of the scanner. * * @return (array|bool|null) The result as an array. False if no result. Null if the scanner doesn’t exist. */ public static function get_fix_result( $scan_name ) { $result = static::get_fix_raw_result( $scan_name ); if ( null === $result || false === $result ) { return $result; } if ( ! $result || ! is_array( $result ) ) { static::delete_fix_result( $scan_name ); return false; } return $result; } /** * Get a fix raw result. * * @since 1.3 * @author Grégory Viguier * * @param (string) $scan_name Name of the scanner. * * @return (mixed) The option value. Null if the scanner doesn’t exist. */ public static function get_fix_raw_result( $scan_name ) { $scanners = static::get_scanners(); $scan_name = strtolower( $scan_name ); if ( ! isset( $scanners[ $scan_name ] ) ) { return null; } static::cache_fix_results(); return get_site_option( static::FIX_OPTION_PREFIX . $scan_name ); } /** * Delete a fix result. * * @since 1.3 * @author Grégory Viguier * * @param (string) $scan_name Name of the scanner. */ public static function delete_fix_result( $scan_name ) { $scanners = static::get_scanners(); $scan_name = strtolower( $scan_name ); if ( ! isset( $scanners[ $scan_name ] ) ) { return; } static::cache_fix_results(); delete_site_option( static::FIX_OPTION_PREFIX . $scan_name ); } /** * Update a fix result. * * @since 1.3 * @author Grégory Viguier * * @param (string) $scan_name Name of the scanner. * @param (array) $result Fix result. */ public static function update_fix_result( $scan_name, $result ) { $scanners = static::get_scanners(); $scan_name = strtolower( $scan_name ); if ( ! isset( $scanners[ $scan_name ] ) ) { return; } static::cache_fix_results(); update_site_option( static::FIX_OPTION_PREFIX . $scan_name, $result ); } /** * Retrieve (and cache) from the DB all fix results at once. * * @since 1.3 * @author Grégory Viguier */ protected static function cache_fix_results() { static $done = false; if ( $done ) { return; } $done = 1; $scanners = static::get_scanners(); $scanners = array_flip( $scanners ); secupress_load_network_options( $scanners, static::FIX_OPTION_PREFIX ); } /** Multisite Scan and Fix for sub-sites ==================================================== */ /** * Get all sub-sites results. * * @since 1.3 * * @return (array) The results, like: * array( * test_name_lower => array( * site_id => array( * 'scan' => array( * 'status' => 'bad', * 'msgs' => array( 202 => array( params ) ) * ), * 'fix' => array( * 'status' => 'cantfix', * 'msgs' => array( 303 => array( params ) ) * ) * ) * ) * ) */ public static function get_sub_sites_results() { $scanners = static::get_scanners_for_ms_sites(); $results = array(); // Reset the value. static::$current_site_modified = false; // Get all results. foreach ( $scanners as $scan_name => $class_name_part ) { $result = static::get_sub_sites_result( $scan_name ); if ( $result ) { $results[ $scan_name ] = $result; } } // If the results changed for the current site and are (now) empty, we will trigger an action. if ( static::$current_site_modified ) { $current_site_id = get_current_blog_id(); $current_site_is_empty = true; foreach ( $scanners as $scan_name => $class_name_part ) { if ( ! empty( $results[ $scan_name ][ $current_site_id ] ) ) { $current_site_is_empty = false; break; } } if ( $current_site_is_empty ) { /** * Fires if the current site has empty scanner results. * * @since 1.0 */ do_action( 'secupress.multisite.empty_results_for_ms_scanner_fixes' ); } } // Reset the value. static::$current_site_modified = null; return $results; } /** * Get a sub-sites result. * * @since 1.3 * @author Grégory Viguier * * @param (string) $scan_name Name of the scanner. * * @return (array|bool|null) The result as an array. False if no result. Null if the scanner doesn’t exist. * array( * site_id => array( * 'scan' => array( * 'status' => 'bad', * 'msgs' => array( 202 => array( params ) ) * ), * 'fix' => array( * 'status' => 'cantfix', * 'msgs' => array( 303 => array( params ) ) * ) * ) * ) */ public static function get_sub_sites_result( $scan_name ) { $result = static::get_sub_sites_raw_result( $scan_name ); if ( null === $result || false === $result ) { return $result; } if ( ! $result || ! is_array( $result ) ) { static::delete_sub_sites_result( $scan_name ); return false; } $scan_name = strtolower( $scan_name ); $scan_result_modified = false; $current_site_id = get_current_blog_id(); foreach ( $result as $site_id => $data ) { // If the site data is empty or if the scan result is good: remove previous values from the result. if ( empty( $data ) || ! empty( $data['scan']['status'] ) && 'good' === $data['scan']['status'] ) { $scan_result_modified = true; if ( $site_id === $current_site_id ) { static::$current_site_modified = true; } if ( $site_id === $current_site_id && ! empty( $result[ $site_id ] ) ) { static::schedule_autoscan( $scan_name ); } unset( $result[ $site_id ] ); } } if ( empty( $result ) ) { static::delete_sub_sites_result( $scan_name ); return false; } if ( $scan_result_modified ) { static::update_sub_sites_result( $scan_name, $result ); } return $result; } /** * Get a fix raw result. * * @since 1.3 * @author Grégory Viguier * * @param (string) $scan_name Name of the scanner. * * @return (mixed) The option value. Null if the scanner doesn’t exist. */ public static function get_sub_sites_raw_result( $scan_name ) { $scanners = static::get_scanners_for_ms_sites(); $scan_name = strtolower( $scan_name ); if ( ! isset( $scanners[ $scan_name ] ) ) { return null; } static::cache_sub_sites_results(); return get_site_option( static::MS_OPTION_PREFIX . $scan_name ); } /** * Delete a sub-sites result. * * @since 1.3 * @author Grégory Viguier * * @param (string) $scan_name Name of the scanner. */ public static function delete_sub_sites_result( $scan_name ) { $scanners = static::get_scanners_for_ms_sites(); $scan_name = strtolower( $scan_name ); if ( ! isset( $scanners[ $scan_name ] ) ) { return; } static::cache_sub_sites_results(); delete_site_option( static::MS_OPTION_PREFIX . $scan_name ); } /** * Update a sub-sites result. * * @since 1.3 * @author Grégory Viguier * * @param (string) $scan_name Name of the scanner. * @param (array) $result Sub-sites result. */ public static function update_sub_sites_result( $scan_name, $result ) { $scanners = static::get_scanners_for_ms_sites(); $scan_name = strtolower( $scan_name ); if ( ! isset( $scanners[ $scan_name ] ) ) { return; } static::cache_sub_sites_results(); update_site_option( static::MS_OPTION_PREFIX . $scan_name, $result ); } /** * Retrieve (and cache) from the DB all sub-sites results at once. * * @since 1.3 * @author Grégory Viguier */ protected static function cache_sub_sites_results() { static $done = false; if ( $done ) { return; } $done = 1; $scanners = static::get_scanners_for_ms_sites(); $scanners = array_flip( $scanners ); secupress_load_network_options( $scanners, static::MS_OPTION_PREFIX ); } /** Tools =================================================================================== */ /** * Get all scanner names. * * @since 1.3 * @author Grégory Viguier * * @return (array) An array like `array( 'easy_login' => 'Easy_Login',... )`. */ public static function get_scanners() { static $scanners; if ( isset( $scanners ) ) { return $scanners; } $scanners = secupress_get_scanners(); $temp = []; foreach ( $scanners as $keys ) { foreach( $keys as $index => $values ) { $temp[] = $values; } } $scanners = $temp; $scanners = array_combine( $scanners, $scanners ); $scanners = array_map( 'strtolower', $scanners ); $scanners = array_flip( $scanners ); return $scanners; } /** * Get scanner names that can't be fixes from the network admin. * * @since 1.3 * @author Grégory Viguier * * @return (array) An array like `array( 'bad_old_plugins' => 'Bad_Old_Plugins',... )`. */ public static function get_scanners_for_ms_sites() { static $scanners; if ( isset( $scanners ) ) { return $scanners; } $scanners = secupress_get_tests_for_ms_scanner_fixes(); $scanners = array_combine( $scanners, $scanners ); $scanners = array_map( 'strtolower', $scanners ); $scanners = array_flip( $scanners ); return $scanners; } /** * Schedule an auto-scan. * * @since 1.3 * @author Grégory Viguier * * @param (string) $scan_name Name of the scanner. */ public static function schedule_autoscan( $scan_name ) { $scanners = static::get_scanners(); $scan_name = strtolower( $scan_name ); if ( ! isset( $scanners[ $scan_name ] ) ) { return; } if ( ! file_exists( secupress_class_path( 'scan', $scan_name ) ) ) { return; } secupress_require_class( 'scan' ); secupress_require_class( 'scan', $scan_name ); $classname = 'SecuPress_Scan_' . $scanners[ $scan_name ]; if ( class_exists( $classname ) ) { $classname::get_instance()->schedule_autoscan(); } } } free/classes/common/class-secupress-logs-list.php 0000644 00000025074 15174670627 0016204 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * General Logs list class. * * @package SecuPress * @since 1.0 */ class SecuPress_Logs_List extends SecuPress_Singleton { const VERSION = '1.0'; /** * The reference to the *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** * Logs instance. * * @var (object) */ private $logs_instance; /** * Current Log type. * * @var (string) */ private $log_type; /** * Current Post type. * * @var (string) */ private $post_type; /** * Log class name. * * @var (string) */ private $log_classname; /** * ID of the Log currently being displayed. * * @var (int) */ private $current_log_id = 0; /** Init ==================================================================================== */ /** * Set the values. * * @since 1.0 */ protected function _init() { $log_types = SecuPress_Logs::get_log_types(); // Get the Log type. $this->log_type = ! empty( $_GET['tab'] ) ? $_GET['tab'] : ''; $this->log_type = $this->log_type && isset( $log_types[ $this->log_type ] ) ? $this->log_type : key( $log_types ); // Get the Logs instance. $logs_classname = $log_types[ $this->log_type ]['classname']; $this->logs_instance = $logs_classname::get_instance(); // Get the Log class. $this->log_classname = $logs_classname::maybe_include_log_class(); // Get the Post type. $this->post_type = $log_types[ $this->log_type ]['post_type']; } /** Private methods ========================================================================= */ /** * Prepare the list. * * @since 1.0 */ public function prepare_list() { global $wp_query, $wp_list_table; secupress_require_class( 'Logs', 'List_Table' ); // Instantiate the list. $wp_list_table = new SecuPress_Logs_List_Table( array( 'screen' => convert_to_screen( $this->post_type ) ) ); // WPCS: override ok. // Query the Logs. $wp_list_table->prepare_items(); /** * Display a Log content. * If the Log doesn't exist, remove the "log" parameter and redirect. */ if ( ! empty( $_GET['log'] ) ) { $log_classname = $this->log_classname; $this->current_log_id = $log_classname::log_exists( $_GET['log'], $this->log_type ); if ( ! $this->current_log_id ) { $sendback = $this->paged_page_url(); wp_redirect( esc_url_raw( $sendback ) ); exit(); } } // Screen options and stuff. $current_screen = get_current_screen(); if ( method_exists( $current_screen, 'set_screen_reader_content' ) ) { $post_type_object = get_post_type_object( $this->post_type ); $current_screen->set_screen_reader_content( array( 'heading_views' => $post_type_object->labels->filter_items_list, 'heading_pagination' => $post_type_object->labels->items_list_navigation, 'heading_list' => $post_type_object->labels->items_list, ) ); } add_screen_option( 'per_page', array( 'default' => 20, 'option' => 'edit_' . $this->post_type . '_per_page' ) ); } /** * Display the list. * * @since 1.0 */ public function display_list() { global $wp_list_table; ?> <div class="wrap"> <?php // The page title. $log_types = SecuPress_Logs::get_log_types(); $head_title = get_post_type_object( $log_types[ $this->log_type ]['post_type'] )->label; secupress_admin_heading( $head_title ); secupress_settings_heading( array( 'title' => $head_title, 'subtitle' => __( 'Monitor everything', 'secupress' ), ) ); ?> <div class="secupress-logs-list-wrapper"> <?php // Messages. settings_errors(); // Maybe display a Log infos. $this->display_current_log(); ?> <div class="secupress-logs-list"> <?php $wp_list_table->views(); ?> <form id="posts-filter" method="get" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>"> <?php $wp_list_table->display(); ?> </form> </div> </div> </div> <?php } /** * The page title, maybe with tabs. * * @since 1.0 */ public function screen_title_or_tabs() { global $title, $wp_list_table; $log_types = SecuPress_Logs::get_log_types(); // No tabs, somebody messed it up. Fallback. if ( ! $log_types || ! is_array( $log_types ) ) { echo "<h1>$title</h1>\n"; return; } // Only 1 tab, no need to go further. if ( 1 === count( $log_types ) ) { echo "<h1>" . get_post_type_object( $log_types[ $this->log_type ]['post_type'] )->label . "</h1>\n"; return; } $i = 0; $page_url = secupress_admin_url( 'logs' ); echo "<h1 class=\"nav-tab-wrapper\">"; foreach ( $log_types as $log_type => $atts ) { $current_url = $i ? add_query_arg( 'tab', $log_type, $page_url ) : $page_url; $label = get_post_type_object( $atts['post_type'] )->label; echo ( $i ? '<span class="screen-reader-text">, </span>' : '' ) . '<a class="nav-tab' . ( $log_type === $this->log_type ? ' nav-tab-active' : '' ) . '" href="' . esc_url( $current_url ) . '">' . $label . '</a>'; ++$i; } echo "</h1>\n"; } /** * Maybe display the current Log infos. * * @since 1.0 * * @return True if a Log is displayed. False otherwize. */ protected function display_current_log() { global $avail_post_stati; $log_types = SecuPress_Logs::get_log_types(); $has_tabs_class = count( $log_types ) > 1 ? ' secupress-has-log-tabs' : ' secupress-has-no-log-tabs'; if ( ! $this->current_log_id ) { echo '<div class="secupress-log-content secupress-empty-log-content' . $has_tabs_class . '"><p>' . __( 'No logs selected', 'secupress' ) . "</p></div>\n"; return false; } $log_classname = $this->log_classname; $log = new $log_classname( $this->current_log_id ); if ( ! $log ) { echo '<div class="secupress-log-content secupress-empty-log-content' . $has_tabs_class . '"><p>' . __( 'No logs selected', 'secupress' ) . "</p></div>\n"; return false; } $page_url = $this->page_url(); $paged_page_url = $this->paged_page_url(); $user_raw = $log->get_user( true ); $delete_url = $this->logs_instance->delete_log_url( $this->current_log_id, $page_url ); $delete_by_ip_url = $this->logs_instance->delete_logs_by_ip_url( $user_raw->user_ip, $page_url ); $delete_by_user_id_url = $this->logs_instance->delete_logs_by_user_id_url( $user_raw->user_id, $page_url ); $ban_ip_url = wp_nonce_url( admin_url( 'admin-post.php?action=secupress-ban-ip&ip=' . urlencode( $user_raw->user_ip ) . '&_wp_http_referer=' . urlencode( esc_url_raw( $paged_page_url ) ) ), 'secupress-ban-ip' ); if ( ! empty( $_GET['critic'] ) && in_array( $_GET['critic'], $avail_post_stati, true ) ) { $close_href = add_query_arg( array( 'critic' => $_GET['critic'] ), $paged_page_url ); } else { $close_href = $paged_page_url; } // Add a class to the current Log row. add_filter( 'post_class', array( $this, 'add_current_log_class' ), 10, 3 ); ?> <div class="secupress-log-content<?php echo $has_tabs_class; ?>" data-logid="<?php echo $this->current_log_id; ?>"> <div class="secupress-log-content-header secupress-section-primary"> <div class="secupress-flex"> <p class="secupress-log-title"> <?php _e( 'Log Details', 'secupress' ); ?> </p> <p class="secupress-log-delete-actions"> <a class="secupress-action-links secupress-delete-log" href="<?php echo esc_url( $delete_url ); ?>"> <i class="secupress-icon-trash" aria-hidden="true"></i> <?php _e( 'Delete log', 'secupress' ); ?> </a> <span class="spinner secupress-inline-spinner"></span> <a class="secupress-action-links secupress-delete-logs-by-user_id" href="<?php echo esc_url( $delete_by_user_id_url ); ?>"> <i class="secupress-icon-trash" aria-hidden="true"></i> <?php echo $user_raw->user_id ? __( 'Delete logs for this user', 'secupress' ) : __( 'Delete logs without user ID', 'secupress' ); ?> </a> <span class="spinner secupress-inline-spinner"></span> </p> </div> <div class="secupress-flex"> <p class="secupress-log-user"> <?php $referer = add_query_arg( 'log', $this->current_log_id, $paged_page_url ); $filters = array( 'user_ip' => add_query_arg( 'user_ip', '%s', $page_url ), 'user_id' => add_query_arg( 'user_id', '%d', $page_url ), 'user_login' => add_query_arg( 'user_login', '%s', $page_url ), ); echo $log->get_user( false, $referer, $filters ); ?> </p> <p class="secupress-ip-handler"> <?php if ( ! secupress_ip_is_whitelisted( $user_raw->user_ip ) && secupress_get_ip() !== $user_raw->user_ip ) { ?> <a class="secupress-action-links secupress-ban-ip" href="<?php echo esc_url( $ban_ip_url ); ?>"> <i class="secupress-icon-times-circle" aria-hidden="true"></i> <?php _e( 'Ban this IP', 'secupress' ); ?> </a> <span class="spinner secupress-inline-spinner"></span> <?php } ?> <a class="secupress-action-links secupress-delete-logs-by-ip" href="<?php echo esc_url( $delete_by_ip_url ); ?>"> <i class="secupress-icon-trash" aria-hidden="true"></i> <?php _e( 'Delete logs with this IP', 'secupress' ); ?> </a> <span class="spinner secupress-inline-spinner"></span> </p> </div> <a class="close" href="<?php echo esc_url( $close_href ); ?>"> <i class="secupress-icon-squared-cross" aria-hidden="true"></i> <span class="screen-reader-text"><?php _e( 'Close' ); ?></span> </a> </div> <div class="secupress-log-content-message"> <?php echo $log->get_message(); ?> </div> </div><!-- .secupress-log-content --> <?php return true; } /** * Add a "current-log" class to the row of the Log currently being displayed. * * @since 1.0 * * @param (array) $classes An array of post classes. * @param (array) $class An array of additional classes added to the post. * @param (int) $post_id The post ID. * * @return (array) */ public function add_current_log_class( $classes, $class, $post_id ) { if ( $post_id === $this->current_log_id ) { $classes[] = 'current-log'; } return $classes; } /** Tools =================================================================================== */ /** * The page URL. * * @since 1.0 * * @return (string) */ protected function page_url() { global $wp_list_table; return $wp_list_table->page_url(); } /** * The page URL, with the page number parameter. * * @since 1.0 * * @return (string) */ protected function paged_page_url() { global $wp_list_table; return $wp_list_table->paged_page_url(); } } free/classes/common/class-secupress-logs.php 0000644 00000115575 15174670627 0015241 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * General Logs class. * * @package SecuPress * @since 1.0 */ class SecuPress_Logs extends SecuPress_Singleton { const VERSION = '1.0.1'; /** * The reference to the *Singleton* instance of this class: must be extended. * * @var (object) */ protected static $_instance; /** * The Log type: must be extended. * * @var (string) */ protected $log_type = ''; /** * The Log type priority (order in the tabs): can be extended. * * @var (int) */ protected $log_type_priority = 10; /** * List of available criticities for this Log type: can be extended. * * @var (array) */ protected $criticities = array( 'normal' ); /** * The Post Type labels: can be extended. * * @var (array) */ protected $post_type_labels = array(); /** * The Post Type. * * @var (string) */ private $post_type = ''; /** * The name of the transient that will store the delayed Logs. * * @var (string) */ private $delayed_logs_transient_name = ''; /** * List of all criticities for all Log types. * * @var (array) */ private static $all_criticities = array(); /** Public methods ========================================================================== */ /** * Get the Log type. * * @since 1.0 * * @return (string) */ public function get_log_type() { return $this->log_type; } /** * Get the post type. * * @since 1.0 * * @return (string) */ public function get_post_type() { if ( ! $this->post_type ) { $this->post_type = static::build_post_type_name( $this->log_type ); } return $this->post_type; } /** * Get the list of available criticities for this type of Log. * * @since 1.0 * * @return (array) */ public function get_available_criticities() { return $this->criticities; } /** * Get stored Logs. * Some default arguments (like post_type and post_status) are already set by `$this->logs_query_args()`. * * @since 1.0 * * @see https://developer.wordpress.org/reference/functions/get_posts/. * @see https://codex.wordpress.org/Class_Reference/WP_Query#Parameters. * * @param (array) $args Arguments meant for `WP_Query`. * * @return (array) An array of Logs. */ public function get_logs( $args = array() ) { return get_posts( $this->logs_query_args( $args ) ); } /** * Get Logs with a certain user ID. * * @since 1.0 * * @param (int) $id A user ID. * @param (bool) $ids_only Return an array of IDs instead of an array of posts. * * @return (array) An array of Logs or IDs. */ public function get_logs_from_user_id( $id, $ids_only = false ) { $id = (int) $id; $args = array( 'meta_query' => array(), ); if ( $ids_only ) { $args['fields'] = 'ids'; } if ( $id ) { // Logs with this user ID. $args['meta_query'][] = array( 'key' => 'user_id', 'value' => $id, 'type' => 'NUMERIC', ); } else { // Logs without user ID. $meta = array( 'key' => 'user_id', 'compare' => 'NOT EXISTS', ); $args['meta_query'][] = $meta; } return $this->get_logs( $args ); } /** * Get Logs with a certain IP address. * * @since 1.0 * * @param (string) $ip An IP address. * @param (bool) $ids_only Return an array of IDs instead of an array of posts. * * @return (array) An array of Logs or IDs. */ public function get_logs_from_ip( $ip, $ids_only = false ) { $args = array( 'meta_query' => array( array( 'key' => 'user_ip', 'value' => $ip, ), ), ); if ( $ids_only ) { $args['fields'] = 'ids'; } return $this->get_logs( $args ); } /** * Delete some Logs. * * @since 1.0 * * @return (int) Number of deleted Logs. */ public function delete_logs() { global $wpdb; $args = func_get_args(); if ( ! isset( $args[0] ) ) { $post_ids = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_type = %s", $this->get_post_type() ) ); } elseif ( is_array( $args[0] ) ) { $post_ids = $args[0]; } else { return 0; } if ( ! $post_ids ) { return 0; } // Delete Postmeta. $sql = sprintf( "DELETE FROM $wpdb->postmeta WHERE post_id IN (%s)", implode( ',', $post_ids ) ); $wpdb->query( $sql ); // WPCS: unprepared SQL ok. // Delete Posts. $sql = sprintf( "DELETE FROM $wpdb->posts WHERE ID IN (%s)", implode( ',', $post_ids ) ); $wpdb->query( $sql ); // WPCS: unprepared SQL ok. return count( $post_ids ); } /** * Delete one Log. * * @since 1.0 * * @param (int) $post_id The Log ID. * * @return (bool) True, if succeed. False, if failure. */ public function delete_log( $post_id ) { return wp_delete_post( (int) $post_id, true ); } /** * Get the max number of stored Logs of the same type. * * @since 1.0 * * @return (int) */ public function get_logs_limit() { /** * Limit the number of Logs stored in the database. * By default 1000, is restricted between 10 and 10000. * * @since 1.0 * * @param (int) $limit The limit. Default is 1000. * @param (string) $this->log_type The Log type. */ $limit = apply_filters( 'secupress.logs.logs_limit', 1000, $this->log_type ); return secupress_minmax_range( $limit, 10, 10000 ); } /** * Get the URL to download Logs. * * @since 1.0 * * @param (string) $referer The page referer. * * @return (string) */ public function download_logs_url( $referer ) { $href = urlencode( esc_url_raw( $referer ) ); $href = admin_url( 'admin-post.php?action=secupress_download-' . $this->log_type . '-logs&_wp_http_referer=' . $href ); return wp_nonce_url( $href, 'secupress-download-' . $this->log_type . '-logs' ); } /** * Get the URL to delete all Logs. * * @since 1.0 * * @param (string) $referer The page referer. * * @return (string) */ public function delete_logs_url( $referer ) { $href = urlencode( esc_url_raw( $referer ) ); $href = admin_url( 'admin-post.php?action=secupress_clear-' . $this->log_type . '-logs&_wp_http_referer=' . $href ); return wp_nonce_url( $href, 'secupress-clear-' . $this->log_type . '-logs' ); } /** * Get the URL to delete Logs with a certain user ID. * * @since 1.0 * * @param (int) $id User ID. * @param (string) $referer The page referer. * * @return (string) */ public function delete_logs_by_user_id_url( $id, $referer ) { $id = (int) $id; $href = urlencode( esc_url_raw( $referer ) ); $href = admin_url( 'admin-post.php?action=secupress_delete-' . $this->log_type . '-logs-by-user_id&id=' . $id . '&_wp_http_referer=' . $href ); return wp_nonce_url( $href, 'secupress-delete-' . $this->log_type . '-logs-by-user_id' ); } /** * Get the URL to delete Logs with a certain IP address. * * @since 1.0 * * @param (string) $ip IP address. * @param (string) $referer The page referer. * * @return (string) */ public function delete_logs_by_ip_url( $ip, $referer ) { $ip = urlencode( $ip ); $href = urlencode( esc_url_raw( $referer ) ); $href = admin_url( 'admin-post.php?action=secupress_delete-' . $this->log_type . '-logs-by-ip&ip=' . $ip . '&_wp_http_referer=' . $href ); return wp_nonce_url( $href, 'secupress-delete-' . $this->log_type . '-logs-by-ip' ); } /** * Get the URL to delete one Log. * * @since 1.0 * * @param (int) $post_id The Log ID. * @param (string) $referer The page referer. * * @return (string) */ public function delete_log_url( $post_id, $referer ) { $href = urlencode( esc_url_raw( $referer ) ); $href = admin_url( 'admin-post.php?action=secupress_delete-' . $this->log_type . '-log&log=' . $post_id . '&_wp_http_referer=' . $href ); return wp_nonce_url( $href, 'secupress-delete-' . $this->log_type . '-log' ); } /** * Get the URL of the page displaying the list of a Log type. * * @since 1.0 * * @param (string) $log_type The Log type. * * @return (string) */ public static function get_log_type_url( $log_type ) { $log_types = static::get_log_types(); $page_url = secupress_admin_url( 'logs' ); $i = 0; foreach ( $log_types as $type => $atts ) { if ( $type === $log_type ) { return $i ? add_query_arg( 'tab', $log_type, $page_url ) : $page_url; } ++$i; } return $page_url; } /** Private methods ========================================================================= */ /** * Launch main hooks. * * @since 1.0 */ protected function _init() { static $done = false; $init_now = did_action( 'init' ) || doing_action( 'init' ); // Register the Post Type. if ( $init_now ) { $this->register_post_type(); } else { add_action( 'init', array( $this, 'register_post_type' ), 1 ); // Some Logs creation may have been delayed. add_action( 'init', array( $this, 'save_delayed_logs' ) ); } // Filter the post slug to allow duplicates. add_filter( 'wp_unique_post_slug', array( $this, 'allow_log_name_duplicates' ), 10, 6 ); if ( is_admin() ) { self::$all_criticities = array_merge( self::$all_criticities, $this->criticities ); // For the page that lists Logs, "register" our Log type. add_filter( 'secupress.logs.log_types', array( $this, 'register_log_type' ), $this->log_type_priority ); // Filter the query args used in `SecuPress_Logs_List_Table::prepare_items()`. add_filter( 'secupress.logs.logs_query_args', array( $this, 'logs_query_args_filter' ) ); // Filter the available post statuses. add_filter( 'wp_count_posts', array( $this, 'count_posts_filter' ), 10, 2 ); // Create the page that lists the Logs. add_action( ( is_multisite() ? 'network_' : '' ) . 'admin_menu', array( $this, 'maybe_create_page' ), 11 ); // Download Logs list. add_action( 'admin_post_secupress_download-' . $this->log_type . '-logs', array( $this, 'post_download_logs_ajax_post_cb' ) ); // Empty Logs list. add_action( 'wp_ajax_secupress_clear-' . $this->log_type . '-logs', array( $this, 'ajax_clear_logs_ajax_post_cb' ) ); add_action( 'admin_post_secupress_clear-' . $this->log_type . '-logs', array( $this, 'post_clear_logs_ajax_post_cb' ) ); // Bulk delete Logs. add_action( 'wp_ajax_secupress_bulk_delete-' . $this->log_type . '-logs', array( $this, 'ajax_bulk_delete_logs_ajax_post_cb' ) ); add_action( 'admin_post_secupress_bulk_delete-' . $this->log_type . '-logs', array( $this, 'post_bulk_delete_logs_ajax_post_cb' ) ); // Delete Logs by user ID. add_action( 'wp_ajax_secupress_delete-' . $this->log_type . '-logs-by-user_id', array( $this, 'ajax_bulk_delete_logs_by_user_id_ajax_post_cb' ) ); add_action( 'admin_post_secupress_delete-' . $this->log_type . '-logs-by-user_id', array( $this, 'post_bulk_delete_logs_by_user_id_ajax_post_cb' ) ); // Delete Logs by IP. add_action( 'wp_ajax_secupress_delete-' . $this->log_type . '-logs-by-ip', array( $this, 'ajax_bulk_delete_logs_by_ip_ajax_post_cb' ) ); add_action( 'admin_post_secupress_delete-' . $this->log_type . '-logs-by-ip', array( $this, 'post_bulk_delete_logs_by_ip_ajax_post_cb' ) ); // Delete a Log. add_action( 'wp_ajax_secupress_delete-' . $this->log_type . '-log', array( $this, 'ajax_delete_log_ajax_post_cb' ) ); add_action( 'admin_post_secupress_delete-' . $this->log_type . '-log', array( $this, 'post_delete_log_ajax_post_cb' ) ); } if ( ! $done ) { $done = true; // Register the Post Statuses. if ( $init_now ) { self::register_post_statuses(); } else { add_action( 'init', array( __CLASS__, 'register_post_statuses' ), 5 ); } } // Delayed Logs. $this->delayed_logs_transient_name = 'secupress_delayed_' . $this->get_log_type() . '_logs'; // Autoload the transient. add_filter( 'secupress.options.load_plugins_network_options', array( $this, 'autoload_options' ) ); } /** * Register the Post Type. * Labels can be customized with `$this->post_type_labels`. * * @since 1.0 */ public function register_post_type() { if ( ! $this->post_type_labels ) { $this->post_type_labels = array( 'name' => _x( 'Logs', 'post type general name', 'secupress' ), 'singular_name' => _x( 'Log', 'post type singular name', 'secupress' ), 'menu_name' => _x( 'Logs', 'post type general name', 'secupress' ), 'all_items' => __( 'All Logs', 'secupress' ), 'add_new' => _x( 'Add New', 'secupress_log', 'secupress' ), 'add_new_item' => __( 'Add New Log', 'secupress' ), 'edit_item' => __( 'Edit Log', 'secupress' ), 'new_item' => __( 'New Log', 'secupress' ), 'view_item' => __( 'View Log', 'secupress' ), 'items_archive' => _x( 'Logs', 'post type general name', 'secupress' ), 'search_items' => __( 'Search Logs', 'secupress' ), 'not_found' => __( 'No logs found.', 'secupress' ), 'not_found_in_trash' => __( 'No logs found in Trash.', 'secupress' ), 'parent_item_colon' => __( 'Parent Log:', 'secupress' ), 'archives' => __( 'Log Archives', 'secupress' ), 'insert_into_item' => __( 'Insert into log', 'secupress' ), 'uploaded_to_this_item' => __( 'Uploaded to this log', 'secupress' ), 'filter_items_list' => __( 'Filter logs list', 'secupress' ), 'items_list_navigation' => __( 'Logs list navigation', 'secupress' ), 'items_list' => __( 'Logs list', 'secupress' ), ); } register_post_type( $this->get_post_type(), array( 'labels' => $this->post_type_labels, 'capability_type' => $this->get_post_type(), 'supports' => false, 'rewrite' => false, 'map_meta_cap' => true, 'capabilities' => array( 'read' => 'read_' . $this->get_post_type() . 's', ), ) ); } /** * Filter the unique post slug: we need to allow duplicates. * * @since 1.0 * * @param (string) $slug The post slug. * @param (int) $post_id Post ID. * @param (string) $post_status The post status. * @param (string) $post_type Post type. * @param (int) $post_parent Post parent ID. * @param (string) $original_slug The original post slug. * * @return (string) The slug. */ public function allow_log_name_duplicates( $slug, $post_id, $post_status, $post_type, $post_parent, $original_slug ) { if ( $this->get_post_type() !== $post_type ) { return $slug; } /** * The slug should be provided with a "secupress_" prefix. * That way, when `wp_unique_post_slug()` checks for duplicates, it won't find any, we save one useless request to the database. */ return preg_replace( '/^secupress_/', '', $original_slug ); } /** * Add the current Log type to the Logs list. * * @since 1.0 * * @param (array) $types Array of arrays with Log type as key and current class name + post type as values. * * @return (array) */ public function register_log_type( $types ) { $log_type = $this->get_log_type(); $post_type = $this->get_post_type(); $types[ $log_type ] = array( 'classname' => get_class( $this ), 'post_type' => $post_type, ); return $types; } /** * Filter the default query args: if the post type matches, apply the default args. * * @since 1.0 * * @param (array) $args Original args, containing at least the post type. * * @return (array) */ public function logs_query_args_filter( $args ) { if ( ! empty( $args['post_type'] ) && $this->get_post_type() === $args['post_type'] ) { return $this->logs_query_args( $args ); } return $args; } /** * Filter the available post statuses. * * @since 1.0 * * @param (object) $counts Number of posts for each status. * @param (string) $type The corresponding post type. * * @return (object) */ public function count_posts_filter( $counts, $type ) { if ( $this->get_post_type() === $type ) { return (object) array_intersect_key( (array) $counts, array_flip( $this->criticities ) ); } return $counts; } /** * Register the Post Statuses. * * @since 1.0 */ public static function register_post_statuses() { $criticities = array( 'high' => array( 'label' => _x( 'High', 'priority level', 'secupress' ), 'label_count' => _nx_noop( 'High <span class="count">(%s)</span>', 'High <span class="count">(%s)</span>', 'priority level', 'secupress' ), 'public' => false, 'internal' => true, 'protected' => true, 'private' => true, ), 'normal' => array( 'label' => _x( 'Normal', 'priority level', 'secupress' ), 'label_count' => _nx_noop( 'Normal <span class="count">(%s)</span>', 'Normal <span class="count">(%s)</span>', 'priority level', 'secupress' ), 'public' => false, 'internal' => true, 'protected' => true, 'private' => true, ), 'low' => array( 'label' => _x( 'Low', 'priority level', 'secupress' ), 'label_count' => _nx_noop( 'Low <span class="count">(%s)</span>', 'Low <span class="count">(%s)</span>', 'priority level', 'secupress' ), 'public' => false, 'internal' => true, 'protected' => true, 'private' => true, ), ); self::$all_criticities = array_flip( self::$all_criticities ); foreach ( $criticities as $criticity => $atts ) { if ( isset( self::$all_criticities[ $criticity ] ) ) { register_post_status( $criticity, $atts ); } } } /** * Create the page displaying the Logs. * * @since 1.0 */ public function maybe_create_page() { // To create the menu item and the page, we need to use the class used for the current Log type. $log_types = static::get_log_types(); $log_type = ! empty( $_GET['tab'] ) ? $_GET['tab'] : ''; $log_type = $log_type && isset( $log_types[ $log_type ] ) ? $log_type : key( $log_types ); if ( $this->log_type !== $log_type ) { return; } // Create the menu item. add_submenu_page( SECUPRESS_PLUGIN_SLUG . '_scanners', _x( 'Logs', 'post type general name', 'secupress' ), _x( 'Logs', 'post type general name', 'secupress' ), secupress_get_capability(), SECUPRESS_PLUGIN_SLUG . '_logs', array( $this, 'page' ) ); // Initiate the page. add_action( 'load-' . SECUPRESS_PLUGIN_SLUG . '_page_' . SECUPRESS_PLUGIN_SLUG . '_logs', array( $this, 'logs_list_load' ) ); } /** * Prepare the list. * * @since 1.0 */ public function logs_list_load() { $classname = static::maybe_include_list_class(); $classname::get_instance()->prepare_list(); } /** * The page content. * * @since 1.0 */ public function page() { $classname = static::maybe_include_list_class(); $classname::get_instance()->display_list(); } /** * Store new Logs. If the maximum number of Logs is reached, the oldest ones are deleted. * * @since 1.0 * * @param (array) $new_logs The new Logs: an array of arrays. * * @return (int) Number of Logs added. */ protected function save_logs( $new_logs ) { if ( ! $new_logs ) { return 0; } $added = []; $user_id = 0; if ( is_multisite() ) { // On multisite, create posts in the main blog. switch_to_blog( secupress_get_main_blog_id() ); // A post author is needed. $user_id = static::get_default_super_administrator(); } if ( ! $user_id ) { $user_id = static::get_default_administrator(); } /** * Filter the Logs author. * * @since 1.0 * * @param (int) $user_id A user ID. That should be a user that won't be deleted anytime soon. */ $user_id = apply_filters( 'secupress.logs.author', $user_id ); // Maybe it's too soon, we can't save logs before the 'init' hook. $log_now = did_action( 'init' ) || doing_action( 'init' ); if ( ! $log_now ) { // We're before the 'init' hook, we will store the logs in a transient and create them later. $delayed_logs = secupress_get_site_transient( $this->delayed_logs_transient_name ); $delayed_logs = is_array( $delayed_logs ) ? $delayed_logs : array(); } foreach ( $new_logs as $new_log ) { $args = array( 'post_type' => $this->get_post_type(), // Post type / Action, 404. 'post_date' => $new_log['time'], // Post date / Time. 'menu_order' => 0, // Menu order / Microtime. 'post_parent' => isset( $new_log['parent'] ) ? (int) $new_log['parent'] : 0, // For HTTP logs or 0. 'post_status' => 'normal', // Post status / Criticity. 'post_author' => $user_id, // Post author: needed to create the post, we don't want the current user to create it. ); // Menu order / Microtime. if ( ! empty( $new_log['order'] ) ) { if ( ! is_int( $new_log['order'] ) ) { // It's a microtime. $new_log['order'] = explode( ' ', $new_log['order'] ); // Ex: array( '0.03746700', '1452528510' ). $new_log['order'] = reset( $new_log['order'] ); // Ex: '0.03746700'. $new_log['order'] = explode( '.', $new_log['order'] ); // Ex: array( '0', '03746700' ). $new_log['order'] = end( $new_log['order'] ); // Ex: '03746700'. $new_log['order'] = (int) str_pad( $new_log['order'], 8, '0', STR_PAD_RIGHT ); // We make sure we have '03746700', not '037467'. } $args['menu_order'] = $new_log['order']; } // Post name / Type: option, network_option, action, filter, err404. Some of them are suffixed with `|add` or `|update`. if ( ! empty( $new_log['type'] ) ) { $args['post_name'] = str_replace( '|', '-', $new_log['type'] ); } // Post title / Target: option name, action name, filter name, URI. if ( ! empty( $new_log['target'] ) ) { $args['post_title'] = $new_log['target']; } // Post status / Criticity. if ( ! empty( $new_log['critic'] ) ) { $args['post_status'] = $new_log['critic']; } // Guid: don't let WordPress do its stuff. $args['guid'] = $args['post_date'] . str_pad( $args['menu_order'], 8, '0', STR_PAD_RIGHT ); $args['guid'] = str_replace( array( ' ', '-', ':' ), '', $args['guid'] ); // It's too soon, we need to delay the log creation. if ( ! $log_now ) { $delayed_logs[] = array( 'args' => $args, 'meta' => $new_log ); $added[] = true; } // Create the Log. elseif ( $post_id = static::insert_log( $args, $new_log ) ) { $added[] = $post_id; } } if ( $added ) { if ( ! $log_now ) { // Store the delayed logs. secupress_set_site_transient( $this->delayed_logs_transient_name, $delayed_logs ); } else { // Limit the number of Logs stored in the database. $this->limit_logs_number(); } } if ( is_multisite() ) { restore_current_blog(); } return $added; } /** * If some Logs have been delayed, create them now. * * @since 1.0 */ public function save_delayed_logs() { $logs = secupress_get_site_transient( $this->delayed_logs_transient_name ); if ( ! $logs || ! is_array( $logs ) ) { return; } delete_site_transient( $this->delayed_logs_transient_name ); $added = 0; if ( is_multisite() ) { // On multisites, create posts in the main blog. switch_to_blog( secupress_get_main_blog_id() ); } foreach ( $logs as $log ) { // Create the Log. if ( isset( $log['args'], $log['meta'] ) && $post_id = static::insert_log( $log['args'], $log['meta'] ) ) { ++$added; } } // Limit the number of Logs stored in the database. if ( $added ) { $this->limit_logs_number(); } if ( is_multisite() ) { restore_current_blog(); } } /** * Create a Log. * * @since 1.0 * * @param (array) $args Arguments for `wp_insert_post()`. * @param (array) $meta An array containing some post meta to add. * * @return (int|bool) The post ID on success. False on failure. */ protected static function insert_log( $args, $meta ) { // Create the Log. $post_id = wp_insert_post( $args ); if ( ! $post_id ) { return false; } // Meta: data. if ( ! empty( $meta['data'] ) ) { update_post_meta( $post_id, 'data', secupress_compress_data( $meta['data'] ) ); } // Meta: user IP. if ( ! empty( $meta['user_ip'] ) ) { update_post_meta( $post_id, 'user_ip', esc_html( $meta['user_ip'] ) ); } // Meta: user ID. if ( ! empty( $meta['user_id'] ) ) { update_post_meta( $post_id, 'user_id', (int) $meta['user_id'] ); } // Meta: user login. if ( ! empty( $meta['user_login'] ) ) { update_post_meta( $post_id, 'user_login', esc_html( $meta['user_login'] ) ); } return $post_id; } /** * Limit the number of Logs by deleting the old ones. * * @since 1.0 */ protected function limit_logs_number() { $logs = $this->get_logs( array( 'fields' => 'ids', 'offset' => $this->get_logs_limit(), 'posts_per_page' => 100000, // If -1, 'offset' won't work. Any large number does the trick. 'order' => 'DESC', ) ); if ( $logs ) { foreach ( $logs as $post_id ) { $this->delete_log( $post_id ); } } } /** Admin post / Admin ajax ================================================================= */ /** * Admin post callback that allows to download the Logs of a certain type as a .txt file. * * @since 1.0 */ public function post_download_logs_ajax_post_cb() { secupress_check_admin_referer( 'secupress-download-' . $this->log_type . '-logs' ); secupress_check_user_capability(); if ( ini_get( 'zlib.output_compression' ) ) { ini_set( 'zlib.output_compression', 'Off' ); } secupress_time_limit( 0 ); if ( ! headers_sent() ) { $filename = 'secupress-' . $this->log_type . '-logs-' . current_time( 'Y-m-d@H-i-s' ) . '.txt'; ob_start(); nocache_headers(); header( 'Content-Type: text/plain; charset=' . get_option( 'blog_charset' ) ); header( 'Content-Disposition: attachment; filename="' . utf8_encode( $filename ) . '"' ); header( 'Content-Transfer-Encoding: binary' ); header( 'Cache-Control: private, max-age=0, must-revalidate' ); header( 'Pragma: public' ); header( 'Connection: close' ); ob_end_clean(); flush(); } $logs = $this->get_logs(); if ( $logs && is_array( $logs ) ) { $classname = static::maybe_include_log_class(); $log_header = str_pad( '==%SECUPRESS-LOG%', 100, '=', STR_PAD_RIGHT ) . "\n"; foreach ( $logs as $log ) { $log = new $classname( $log ); echo $log_header; echo $this->get_log_header_for_file( $log ) . "\n"; echo html_entity_decode( strip_tags( str_replace( '<br/>', "\n", $log->get_message() ) ) ); echo "\n\n"; } } die; } /** * Ajax callback that allows to delete all Logs of a certain type. * * @since 1.0 */ public function ajax_clear_logs_ajax_post_cb() { secupress_check_admin_referer( 'secupress-clear-' . $this->log_type . '-logs' ); secupress_check_user_capability(); $this->delete_logs(); wp_send_json_success( __( 'Logs deleted.', 'secupress' ) ); } /** * Admin post callback that allows to delete all Logs of a certain type. * * @since 1.0 */ public function post_clear_logs_ajax_post_cb() { secupress_check_admin_referer( 'secupress-clear-' . $this->log_type . '-logs' ); secupress_check_user_capability(); $this->delete_logs(); secupress_add_settings_error( 'general', 'logs_cleared', __( 'Logs deleted.', 'secupress' ), 'updated' ); set_transient( 'settings_errors', secupress_get_settings_errors(), 30 ); $goback = add_query_arg( 'settings-updated', 'true', wp_get_referer() ); wp_redirect( esc_url_raw( $goback ) ); die(); } /** * Ajax callback that allows to delete several Logs of a certain type. * * @since 1.0 */ public function ajax_bulk_delete_logs_ajax_post_cb() { secupress_check_admin_referer( 'secupress-bulk-' . $this->log_type . '-log' ); secupress_check_user_capability(); if ( empty( $_GET['post'] ) || ! is_array( $_GET['post'] ) ) { wp_send_json_error( sprintf( _n( '%s log deleted.', '%s logs deleted.', 0, 'secupress' ), 0 ) ); } $deleted = $this->delete_logs( $_GET['post'] ); wp_send_json_success( sprintf( _n( '%s log permanently deleted.', '%s logs permanently deleted.', $deleted, 'secupress' ), number_format_i18n( $deleted ) ) ); } /** * Admin post callback that allows to delete several Logs of a certain type. * * @since 1.0 */ public function post_bulk_delete_logs_ajax_post_cb() { secupress_check_admin_referer( 'secupress-bulk-' . $this->log_type . '-logs' ); secupress_check_user_capability(); if ( empty( $_GET['post'] ) || ! is_array( $_GET['post'] ) ) { $deleted = 0; } else { $deleted = $this->delete_logs( $_GET['post'] ); } secupress_add_settings_error( 'general', 'logs_bulk_deleted', sprintf( _n( '%s log permanently deleted.', '%s logs permanently deleted.', $deleted, 'secupress' ), number_format_i18n( $deleted ) ), 'updated' ); set_transient( 'settings_errors', secupress_get_settings_errors(), 30 ); $goback = add_query_arg( 'settings-updated', 'true', wp_get_referer() ); wp_redirect( esc_url_raw( $goback ) ); die(); } /** * Ajax callback that allows to delete several Logs of a certain type and with a certain user ID. * * @since 1.0 */ public function ajax_bulk_delete_logs_by_user_id_ajax_post_cb() { secupress_check_admin_referer( 'secupress-delete-' . $this->log_type . '-logs-by-user_id' ); secupress_check_user_capability(); if ( ! isset( $_GET['id'] ) ) { wp_send_json_error( sprintf( _n( '%s log deleted.', '%s logs deleted.', 0, 'secupress' ), 0 ) ); } $posts = $this->get_logs_from_user_id( $_GET['id'], true ); $deleted = $this->delete_logs( $posts ); wp_send_json_success( sprintf( _n( '%s log permanently deleted.', '%s logs permanently deleted.', $deleted, 'secupress' ), number_format_i18n( $deleted ) ) ); } /** * Admin post callback that allows to delete several Logs of a certain type and with a certain user ID. * * @since 1.0 */ public function post_bulk_delete_logs_by_user_id_ajax_post_cb() { secupress_check_admin_referer( 'secupress-delete-' . $this->log_type . '-logs-by-user_id' ); secupress_check_user_capability(); if ( ! isset( $_GET['id'] ) ) { $deleted = 0; } else { $posts = $this->get_logs_from_user_id( $_GET['id'], true ); $deleted = $this->delete_logs( $posts ); } secupress_add_settings_error( 'general', 'logs_bulk_deleted', sprintf( _n( '%s log permanently deleted.', '%s logs permanently deleted.', $deleted, 'secupress' ), number_format_i18n( $deleted ) ), 'updated' ); set_transient( 'settings_errors', secupress_get_settings_errors(), 30 ); $goback = add_query_arg( 'settings-updated', 'true', wp_get_referer() ); wp_redirect( esc_url_raw( $goback ) ); die(); } /** * Ajax callback that allows to delete several Logs of a certain type and with a certain IP. * * @since 1.0 */ public function ajax_bulk_delete_logs_by_ip_ajax_post_cb() { secupress_check_admin_referer( 'secupress-delete-' . $this->log_type . '-logs-by-ip' ); secupress_check_user_capability(); if ( empty( $_GET['ip'] ) ) { wp_send_json_error( sprintf( _n( '%s log deleted.', '%s logs deleted.', 0, 'secupress' ), 0 ) ); } $ip = urldecode( $_GET['ip'] ); if ( ! secupress_ip_is_valid( $ip ) ) { wp_send_json_error( sprintf( _n( '%s log deleted.', '%s logs deleted.', 0, 'secupress' ), 0 ) ); } $posts = $this->get_logs_from_ip( $ip, true ); $deleted = $this->delete_logs( $posts ); wp_send_json_success( sprintf( _n( '%s log permanently deleted.', '%s logs permanently deleted.', $deleted, 'secupress' ), number_format_i18n( $deleted ) ) ); } /** * Admin post callback that allows to delete several Logs of a certain type and with a certain IP. * * @since 1.0 */ public function post_bulk_delete_logs_by_ip_ajax_post_cb() { secupress_check_admin_referer( 'secupress-delete-' . $this->log_type . '-logs-by-ip' ); secupress_check_user_capability(); if ( empty( $_GET['ip'] ) ) { $deleted = 0; } else { $ip = urldecode( $_GET['ip'] ); if ( ! secupress_ip_is_valid( $ip ) ) { $deleted = 0; } else { $posts = $this->get_logs_from_ip( $ip, true ); $deleted = $this->delete_logs( $posts ); } } secupress_add_settings_error( 'general', 'logs_bulk_deleted', sprintf( _n( '%s log permanently deleted.', '%s logs permanently deleted.', $deleted, 'secupress' ), number_format_i18n( $deleted ) ), 'updated' ); set_transient( 'settings_errors', secupress_get_settings_errors(), 30 ); $goback = add_query_arg( 'settings-updated', 'true', wp_get_referer() ); wp_redirect( esc_url_raw( $goback ) ); die(); } /** * Ajax callback that allows to delete a Log. * * @since 1.0 */ public function ajax_delete_log_ajax_post_cb() { secupress_check_admin_referer( 'secupress-delete-' . $this->log_type . '-log' ); secupress_check_user_capability(); if ( empty( $_GET['log'] ) ) { wp_send_json_error(); } if ( ! $this->delete_log( $_GET['log'] ) ) { wp_send_json_error(); } wp_send_json_success( __( 'Log permanently deleted.', 'secupress' ) ); } /** * Admin post callback that allows to delete a Log. * * @since 1.0 */ public function post_delete_log_ajax_post_cb() { secupress_check_admin_referer( 'secupress-delete-' . $this->log_type . '-log' ); secupress_check_user_capability(); if ( empty( $_GET['log'] ) ) { secupress_admin_die(); } if ( ! $this->delete_log( $_GET['log'] ) ) { secupress_admin_die(); } secupress_add_settings_error( 'general', 'log_deleted', __( 'Log permanently deleted.', 'secupress' ), 'updated' ); set_transient( 'settings_errors', secupress_get_settings_errors(), 30 ); $goback = add_query_arg( 'settings-updated', 'true', wp_get_referer() ); wp_redirect( esc_url_raw( $goback ) ); die(); } /** Various ================================================================================= */ /** * Add the transient we use to store the delayed logs to be autoloaded on multisite. * * @since 1.0 * * @param (array) $option_names An array of network option names. * * @return (array) */ public function autoload_options( $option_names ) { $option_names[] = '_site_transient_' . $this->delayed_logs_transient_name; return $option_names; } /** Tools =================================================================================== */ /** * Create a Post Type name based on a Log type. * * @since 1.0 * * @param (string) $log_type A Log type. * * @return (string) The corresponding Post Type name. */ public static function build_post_type_name( $log_type ) { return 'secupress_log_' . $log_type; } /** * Get Log Types. * * @since 1.0 * * @return (string) */ public static function get_log_types() { /** * Filter the Log types available. All Log types must be registered here. * * @since 1.0 * * @see `$this->register_log_type()` * * @param (array) An array of arrays with the Log type as key and containing the post type and the name of the "logs class" as values. */ return apply_filters( 'secupress.logs.log_types', array() ); } /** * Build args for a Logs query. * * @since 1.0 * * @param (array) $args Some `WP_Query` arguments. * * @return (array) The new args merged with default args. */ public function logs_query_args( $args = array() ) { return array_merge( array( 'post_type' => $this->get_post_type(), 'post_status' => $this->criticities, 'posts_per_page' => -1, 'orderby' => 'date menu_order', 'order' => 'DESC', ), $args ); } /** * Used when creating a Log to set default values: time, order, user_ip, user_id, and user_login. * If the user does not exist, user_id and user_login are not set. * * @since 1.0 * * @param (array) $log A Log. * * @return (array) */ public static function set_log_time_and_user( $log = array() ) { $log['time'] = current_time( 'mysql' ); $log['order'] = microtime(); $log['user_id'] = get_current_user_id(); $log['user_ip'] = secupress_get_ip(); if ( $log['user_id'] ) { $user = get_userdata( $log['user_id'] ); if ( $user ) { $log['user_login'] = $user->user_login; } else { unset( $log['user_id'] ); } } return $log; } /** * Get the default administrator. * * @since 1.0 * * @return (int) A user ID. */ protected static function get_default_administrator() { $user_ids = get_users( array( 'blog_id' => secupress_get_main_blog_id(), 'role' => 'administrator', 'number' => 1, 'orderby' => 'ID', 'fields' => 'ID', 'count_total' => false, ) ); return (int) reset( $user_ids ); } /** * Get the default super-administrator. * * @since 1.0 * * @return (int) A user ID. */ protected static function get_default_super_administrator() { global $wpdb; $super_admins = get_super_admins(); if ( ! $super_admins || ! is_array( $super_admins ) ) { return 0; } $super_admins = implode( "','", esc_sql( $super_admins ) ); $user_ids = $wpdb->get_col( "SELECT ID FROM $wpdb->users WHERE user_login IN ('$super_admins') ORDER BY ID ASC" ); // WPCS: unprepared SQL ok. if ( ! $user_ids ) { return 0; } $administrators = get_users( array( 'blog_id' => secupress_get_main_blog_id(), 'role' => 'administrator', 'number' => 100, 'orderby' => 'ID', 'fields' => 'ID', 'count_total' => false, ) ); $user_ids = array_intersect( $user_ids, $administrators ); return $user_ids ? (int) reset( $user_ids ) : 0; } /** * Get the header content used in the `.txt` file that the user can download. * * @since 1.0 * * @param (object) $log `SecuPress_Log` object. * * @return (string) The header content. */ public function get_log_header_for_file( $log ) { $out = '[' . $log->get_time() . ' | '; if ( count( $this->criticities ) > 1 ) { $out .= $log->get_criticity() . ' | '; } $user = $log->get_user( true ); $user_data = get_userdata( $user->user_id ); if ( $user_data && $user_data->data->user_login !== $user->user_login ) { $user->user_login .= ' (' . esc_html( $user_data->data->user_login ) . ')'; } if ( $user->user_id ) { $infos = array( 'user_ip' => __( 'IP', 'secupress' ), 'user_id' => __( 'ID', 'secupress' ), 'user_login' => __( 'Login', 'secupress' ), ); foreach ( $infos as $class => $label ) { $infos[ $class ] = sprintf( __( '%s:', 'secupress' ) . ' %s', $label, $user->$class ); } $out .= html_entity_decode( implode( ' ', $infos ) ); } else { $out .= html_entity_decode( sprintf( __( '%s:', 'secupress' ) . ' %s', __( 'IP', 'secupress' ), $user->user_ip ) ); } $out .= ']'; return $out; } /** * Include the file containing the class `Secupress_Log` if not already done. * Must be extended and must return the class name. * * @since 1.0 * * @return (string) The Log class name. */ public static function maybe_include_log_class() { if ( ! class_exists( 'SecuPress_Log' ) ) { secupress_require_class( 'Log' ); } return 'SecuPress_Log'; } /** * Include the file containing the class `Secupress_Logs_List` if not already done. * * @since 1.0 * * @return (string) The Logs List class name. */ private static function maybe_include_list_class() { if ( ! class_exists( 'SecuPress_Logs_List' ) ) { secupress_require_class( 'Logs', 'List' ); } return 'SecuPress_Logs_List'; } } free/classes/admin/class-secupress-admin-wp-background-process.php 0000644 00000055463 15174670627 0021401 0 ustar 00 <?php /** * WP Background Process * * @package WP-Background-Processing */ if ( ! class_exists( 'WP_Background_Process' ) ) : /** * Abstract WP_Background_Process class. * * @abstract * @extends WP_Async_Request */ abstract class WP_Background_Process extends WP_Async_Request { /** * The default query arg name used for passing the chain ID to new processes. */ const CHAIN_ID_ARG_NAME = 'chain_id'; /** * Unique background process chain ID. * * @var string */ private $chain_id; /** * Action * * (default value: 'background_process') * * @var string * @access protected */ protected $action = 'background_process'; /** * Start time of current process. * * (default value: 0) * * @var int * @access protected */ protected $start_time = 0; /** * Cron_hook_identifier * * @var string * @access protected */ protected $cron_hook_identifier; /** * Cron_interval_identifier * * @var string * @access protected */ protected $cron_interval_identifier; /** * Restrict object instantiation when using unserialize. * * @var bool|array */ protected $allowed_batch_data_classes = true; /** * The status set when process is cancelling. * * @var int */ const STATUS_CANCELLED = 1; /** * The status set when process is paused or pausing. * * @var int; */ const STATUS_PAUSED = 2; /** * Initiate new background process. * * @param bool|array $allowed_batch_data_classes Optional. Array of class names that can be unserialized. Default true (any class). */ public function __construct( $allowed_batch_data_classes = true ) { parent::__construct(); if ( empty( $allowed_batch_data_classes ) && false !== $allowed_batch_data_classes ) { $allowed_batch_data_classes = true; } if ( ! is_bool( $allowed_batch_data_classes ) && ! is_array( $allowed_batch_data_classes ) ) { $allowed_batch_data_classes = true; } // If allowed_batch_data_classes property set in subclass, // only apply override if not allowing any class. if ( true === $this->allowed_batch_data_classes || true !== $allowed_batch_data_classes ) { $this->allowed_batch_data_classes = $allowed_batch_data_classes; } $this->cron_hook_identifier = $this->identifier . '_cron'; $this->cron_interval_identifier = $this->identifier . '_cron_interval'; add_action( $this->cron_hook_identifier, array( $this, 'handle_cron_healthcheck' ) ); add_filter( 'cron_schedules', array( $this, 'schedule_cron_healthcheck' ) ); // Ensure dispatch query args included extra data. add_filter( $this->identifier . '_query_args', array( $this, 'filter_dispatch_query_args' ) ); } /** * Schedule the cron healthcheck and dispatch an async request to start processing the queue. * * @access public * @return array|WP_Error|false HTTP Response array, WP_Error on failure, or false if not attempted. */ public function dispatch() { if ( $this->is_processing() ) { // Process already running. return false; } /** * Filter fired before background process dispatches its next process. * * @param bool $cancel Should the dispatch be cancelled? Default false. * @param string $chain_id The background process chain ID. */ $cancel = apply_filters( $this->identifier . '_pre_dispatch', false, $this->get_chain_id() ); if ( $cancel ) { return false; } // Schedule the cron healthcheck. $this->schedule_event(); // Perform remote post. return parent::dispatch(); } /** * Push to the queue. * * Note, save must be called in order to persist queued items to a batch for processing. * * @param mixed $data Data. * * @return $this */ public function push_to_queue( $data ) { $this->data[] = $data; return $this; } /** * Save the queued items for future processing. * * @return $this */ public function save() { $key = $this->generate_key(); if ( ! empty( $this->data ) ) { update_site_option( $key, $this->data ); } // Clean out data so that new data isn't prepended with closed session's data. $this->data = array(); return $this; } /** * Update a batch's queued items. * * @param string $key Key. * @param array $data Data. * * @return $this */ public function update( $key, $data ) { if ( ! empty( $data ) ) { update_site_option( $key, $data ); } return $this; } /** * Delete a batch of queued items. * * @param string $key Key. * * @return $this */ public function delete( $key ) { delete_site_option( $key ); return $this; } /** * Delete entire job queue. */ public function delete_all() { $batches = $this->get_batches(); foreach ( $batches as $batch ) { $this->delete( $batch->key ); } delete_site_option( $this->get_status_key() ); $this->cancelled(); } /** * Cancel job on next batch. */ public function cancel() { update_site_option( $this->get_status_key(), self::STATUS_CANCELLED ); // Just in case the job was paused at the time. $this->dispatch(); } /** * Has the process been cancelled? * * @return bool */ public function is_cancelled() { return $this->get_status() === self::STATUS_CANCELLED; } /** * Called when background process has been cancelled. */ protected function cancelled() { do_action( $this->identifier . '_cancelled', $this->get_chain_id() ); } /** * Pause job on next batch. */ public function pause() { update_site_option( $this->get_status_key(), self::STATUS_PAUSED ); } /** * Has the process been paused? * * @return bool */ public function is_paused() { return $this->get_status() === self::STATUS_PAUSED; } /** * Called when background process has been paused. */ protected function paused() { do_action( $this->identifier . '_paused', $this->get_chain_id() ); } /** * Resume job. */ public function resume() { delete_site_option( $this->get_status_key() ); $this->schedule_event(); $this->dispatch(); $this->resumed(); } /** * Called when background process has been resumed. */ protected function resumed() { do_action( $this->identifier . '_resumed', $this->get_chain_id() ); } /** * Is queued? * * @return bool */ public function is_queued() { return ! $this->is_queue_empty(); } /** * Is the tool currently active, e.g. starting, working, paused or cleaning up? * * @return bool */ public function is_active() { return $this->is_queued() || $this->is_processing() || $this->is_paused() || $this->is_cancelled(); } /** * Generate key for a batch. * * Generates a unique key based on microtime. Queue items are * given a unique key so that they can be merged upon save. * * @param int $length Optional max length to trim key to, defaults to 64 characters. * @param string $key Optional string to append to identifier before hash, defaults to "batch". * * @return string */ protected function generate_key( $length = 64, $key = 'batch' ) { $unique = md5( microtime() . wp_rand() ); $prepend = $this->identifier . '_' . $key . '_'; return substr( $prepend . $unique, 0, $length ); } /** * Get the status key. * * @return string */ protected function get_status_key() { return $this->identifier . '_status'; } /** * Get the status value for the process. * * @return int */ protected function get_status() { global $wpdb; if ( is_multisite() ) { $status = $wpdb->get_var( $wpdb->prepare( "SELECT meta_value FROM $wpdb->sitemeta WHERE meta_key = %s AND site_id = %d LIMIT 1", $this->get_status_key(), get_current_network_id() ) ); } else { $status = $wpdb->get_var( $wpdb->prepare( "SELECT option_value FROM $wpdb->options WHERE option_name = %s LIMIT 1", $this->get_status_key() ) ); } return absint( $status ); } /** * Maybe process a batch of queued items. * * Checks whether data exists within the queue and that * the process is not already running. */ public function maybe_handle() { // Don't lock up other requests while processing. session_write_close(); check_ajax_referer( $this->identifier, 'nonce' ); // Background process already running. if ( $this->is_processing() ) { return $this->maybe_wp_die(); } // Cancel requested. if ( $this->is_cancelled() ) { $this->clear_scheduled_event(); $this->delete_all(); return $this->maybe_wp_die(); } // Pause requested. if ( $this->is_paused() ) { $this->clear_scheduled_event(); $this->paused(); return $this->maybe_wp_die(); } // No data to process. if ( $this->is_queue_empty() ) { return $this->maybe_wp_die(); } $this->handle(); return $this->maybe_wp_die(); } /** * Is queue empty? * * @return bool */ protected function is_queue_empty() { return empty( $this->get_batch() ); } /** * Is process running? * * Check whether the current process is already running * in a background process. * * @return bool * * @deprecated 1.1.0 Superseded. * @see is_processing() */ protected function is_process_running() { return $this->is_processing(); } /** * Is the background process currently running? * * @return bool */ public function is_processing() { if ( get_site_transient( $this->identifier . '_process_lock' ) ) { // Process already running. return true; } return false; } /** * Lock process. * * Lock the process so that multiple instances can't run simultaneously. * Override if applicable, but the duration should be greater than that * defined in the time_exceeded() method. * * @param bool $reset_start_time Optional, default true. */ public function lock_process( $reset_start_time = true ) { if ( $reset_start_time ) { $this->start_time = time(); // Set start time of current process. } $lock_duration = ( property_exists( $this, 'queue_lock_time' ) ) ? $this->queue_lock_time : 60; // 1 minute $lock_duration = apply_filters( $this->identifier . '_queue_lock_time', $lock_duration ); $microtime = microtime(); $locked = set_site_transient( $this->identifier . '_process_lock', $microtime, $lock_duration ); /** * Action to note whether the background process managed to create its lock. * * The lock is used to signify that a process is running a task and no other * process should be allowed to run the same task until the lock is released. * * @param bool $locked Whether the lock was successfully created. * @param string $microtime Microtime string value used for the lock. * @param int $lock_duration Max number of seconds that the lock will live for. * @param string $chain_id Current background process chain ID. */ do_action( $this->identifier . '_process_locked', $locked, $microtime, $lock_duration, $this->get_chain_id() ); } /** * Unlock process. * * Unlock the process so that other instances can spawn. * * @return $this */ protected function unlock_process() { $unlocked = delete_site_transient( $this->identifier . '_process_lock' ); /** * Action to note whether the background process managed to release its lock. * * The lock is used to signify that a process is running a task and no other * process should be allowed to run the same task until the lock is released. * * @param bool $unlocked Whether the lock was released. * @param string $chain_id Current background process chain ID. */ do_action( $this->identifier . '_process_unlocked', $unlocked, $this->get_chain_id() ); return $this; } /** * Get batch. * * @return stdClass Return the first batch of queued items. */ protected function get_batch() { return array_reduce( $this->get_batches( 1 ), static function ( $carry, $batch ) { return $batch; }, array() ); } /** * Get batches. * * @param int $limit Number of batches to return, defaults to all. * * @return array of stdClass */ public function get_batches( $limit = 0 ) { global $wpdb; if ( empty( $limit ) || ! is_int( $limit ) ) { $limit = 0; } $table = $wpdb->options; $column = 'option_name'; $key_column = 'option_id'; $value_column = 'option_value'; if ( is_multisite() ) { $table = $wpdb->sitemeta; $column = 'meta_key'; $key_column = 'meta_id'; $value_column = 'meta_value'; } $key = $wpdb->esc_like( $this->identifier . '_batch_' ) . '%'; $sql = ' SELECT * FROM ' . $table . ' WHERE ' . $column . ' LIKE %s ORDER BY ' . $key_column . ' ASC '; $args = array( $key ); if ( ! empty( $limit ) ) { $sql .= ' LIMIT %d'; $args[] = $limit; } $items = $wpdb->get_results( $wpdb->prepare( $sql, // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared $args ) ); $batches = array(); if ( ! empty( $items ) ) { $allowed_classes = $this->allowed_batch_data_classes; $batches = array_map( static function ( $item ) use ( $column, $value_column, $allowed_classes ) { $batch = new stdClass(); $batch->key = $item->{$column}; $batch->data = static::maybe_unserialize( $item->{$value_column}, $allowed_classes ); return $batch; }, $items ); } return $batches; } /** * Handle a dispatched request. * * Pass each queue item to the task handler, while remaining * within server memory and time limit constraints. */ protected function handle() { $this->lock_process(); /** * Number of seconds to sleep between batches. Defaults to 0 seconds, minimum 0. * * @param int $seconds */ $throttle_seconds = max( 0, apply_filters( $this->identifier . '_seconds_between_batches', apply_filters( $this->prefix . '_seconds_between_batches', 0 ) ) ); do { $batch = $this->get_batch(); foreach ( $batch->data as $key => $value ) { $task = $this->task( $value ); if ( false !== $task ) { $batch->data[ $key ] = $task; } else { unset( $batch->data[ $key ] ); } // Keep the batch up to date while processing it. if ( ! empty( $batch->data ) ) { $this->update( $batch->key, $batch->data ); } // Let the server breathe a little. sleep( $throttle_seconds ); // Batch limits reached, or pause or cancel requested. if ( ! $this->should_continue() ) { break; } } // Delete current batch if fully processed. if ( empty( $batch->data ) ) { $this->delete( $batch->key ); } } while ( ! $this->is_queue_empty() && $this->should_continue() ); $this->unlock_process(); // Start next batch or complete process. if ( ! $this->is_queue_empty() ) { $this->dispatch(); } else { $this->complete(); } return $this->maybe_wp_die(); } /** * Memory exceeded? * * Ensures the batch process never exceeds 90% * of the maximum WordPress memory. * * @return bool */ protected function memory_exceeded() { $memory_limit = $this->get_memory_limit() * 0.9; // 90% of max memory $current_memory = memory_get_usage( true ); $return = false; if ( $current_memory >= $memory_limit ) { $return = true; } return apply_filters( $this->identifier . '_memory_exceeded', $return ); } /** * Get memory limit in bytes. * * @return int */ protected function get_memory_limit() { if ( function_exists( 'ini_get' ) ) { $memory_limit = ini_get( 'memory_limit' ); } else { // Sensible default. $memory_limit = '128M'; } if ( ! $memory_limit || -1 === intval( $memory_limit ) ) { // Unlimited, set to 32GB. $memory_limit = '32000M'; } return wp_convert_hr_to_bytes( $memory_limit ); } /** * Time limit exceeded? * * Ensures the batch never exceeds a sensible time limit. * A timeout limit of 30s is common on shared hosting. * * @return bool */ protected function time_exceeded() { $finish = $this->start_time + apply_filters( $this->identifier . '_default_time_limit', 20 ); // 20 seconds $return = false; if ( time() >= $finish ) { $return = true; } return apply_filters( $this->identifier . '_time_exceeded', $return ); } /** * Complete processing. * * Override if applicable, but ensure that the below actions are * performed, or, call parent::complete(). */ protected function complete() { delete_site_option( $this->get_status_key() ); // Remove the cron healthcheck job from the cron schedule. $this->clear_scheduled_event(); $this->completed(); } /** * Called when background process has completed. */ protected function completed() { do_action( $this->identifier . '_completed', $this->get_chain_id() ); } /** * Get the cron healthcheck interval in minutes. * * Default is 5 minutes, minimum is 1 minute. * * @return int */ public function get_cron_interval() { $interval = 5; if ( property_exists( $this, 'cron_interval' ) ) { $interval = $this->cron_interval; } $interval = apply_filters( $this->cron_interval_identifier, $interval ); return is_int( $interval ) && 0 < $interval ? $interval : 5; } /** * Schedule the cron healthcheck job. * * @access public * * @param mixed $schedules Schedules. * * @return mixed */ public function schedule_cron_healthcheck( $schedules ) { $interval = $this->get_cron_interval(); if ( 1 === $interval ) { $display = __( 'Every Minute' ); } else { $display = sprintf( __( 'Every %d Minutes' ), $interval ); } // Adds an "Every NNN Minute(s)" schedule to the existing cron schedules. $schedules[ $this->cron_interval_identifier ] = array( 'interval' => MINUTE_IN_SECONDS * $interval, 'display' => $display, ); return $schedules; } /** * Handle cron healthcheck event. * * Restart the background process if not already running * and data exists in the queue. */ public function handle_cron_healthcheck() { if ( $this->is_processing() ) { // Background process already running. exit; } if ( $this->is_queue_empty() ) { // No data to process. $this->clear_scheduled_event(); exit; } $this->dispatch(); } /** * Schedule the cron healthcheck event. */ protected function schedule_event() { if ( ! wp_next_scheduled( $this->cron_hook_identifier ) ) { wp_schedule_event( time() + ( $this->get_cron_interval() * MINUTE_IN_SECONDS ), $this->cron_interval_identifier, $this->cron_hook_identifier ); } } /** * Clear scheduled cron healthcheck event. */ protected function clear_scheduled_event() { $timestamp = wp_next_scheduled( $this->cron_hook_identifier ); if ( $timestamp ) { wp_unschedule_event( $timestamp, $this->cron_hook_identifier ); } } /** * Cancel the background process. * * Stop processing queue items, clear cron job and delete batch. * * @deprecated 1.1.0 Superseded. * @see cancel() */ public function cancel_process() { $this->cancel(); } /** * Perform task with queued item. * * Override this method to perform any actions required on each * queue item. Return the modified item for further processing * in the next pass through. Or, return false to remove the * item from the queue. * * @param mixed $item Queue item to iterate over. * * @return mixed */ abstract protected function task( $item ); /** * Maybe unserialize data, but not if an object. * * @param mixed $data Data to be unserialized. * @param bool|array $allowed_classes Array of class names that can be unserialized. * * @return mixed */ protected static function maybe_unserialize( $data, $allowed_classes ) { if ( is_serialized( $data ) ) { $options = array(); if ( is_bool( $allowed_classes ) || is_array( $allowed_classes ) ) { $options['allowed_classes'] = $allowed_classes; } return @unserialize( $data, $options ); // @phpcs:ignore } return $data; } /** * Should any processing continue? * * @return bool */ public function should_continue() { /** * Filter whether the current background process should continue running the task * if there is data to be processed. * * If the processing time or memory limits have been exceeded, the value will be false. * If pause or cancel have been requested, the value will be false. * * It is very unlikely that you would want to override a false value with true. * * If false is returned here, it does not necessarily mean background processing is * complete. If there is batch data still to be processed and pause or cancel have not * been requested, it simply means this background process should spawn a new process * for the chain to continue processing and then close itself down. * * @param bool $continue Should the current process continue processing the task? * @param string $chain_id The current background process chain's ID. * * @return bool */ return apply_filters( $this->identifier . '_should_continue', ! ( $this->time_exceeded() || $this->memory_exceeded() || $this->is_paused() || $this->is_cancelled() ), $this->get_chain_id() ); } /** * Get the string used to identify this type of background process. * * @return string */ public function get_identifier() { return $this->identifier; } /** * Return the current background process chain's ID. * * If the chain's ID hasn't been set before this function is first used, * and hasn't been passed as a query arg during dispatch, * the chain ID will be generated before being returned. * * @return string */ public function get_chain_id() { if ( empty( $this->chain_id ) && wp_doing_ajax() ) { check_ajax_referer( $this->identifier, 'nonce' ); if ( ! empty( $_GET[ $this->get_chain_id_arg_name() ] ) ) { $chain_id = sanitize_key( $_GET[ $this->get_chain_id_arg_name() ] ); if ( wp_is_uuid( $chain_id ) ) { $this->chain_id = $chain_id; return $this->chain_id; } } } if ( empty( $this->chain_id ) ) { $this->chain_id = wp_generate_uuid4(); } return $this->chain_id; } /** * Filters the query arguments used during an async request. * * @param array $args Current query args. * * @return array */ public function filter_dispatch_query_args( $args ) { $args[ $this->get_chain_id_arg_name() ] = $this->get_chain_id(); return $args; } /** * Get the query arg name used for passing the chain ID to new processes. * * @return string */ private function get_chain_id_arg_name() { static $chain_id_arg_name; if ( ! empty( $chain_id_arg_name ) ) { return $chain_id_arg_name; } /** * Filter the query arg name used for passing the chain ID to new processes. * * If you encounter problems with using the default query arg name, you can * change it with this filter. * * @param string $chain_id_arg_name Default "chain_id". * * @return string */ $chain_id_arg_name = apply_filters( $this->identifier . '_chain_id_arg_name', self::CHAIN_ID_ARG_NAME ); if ( ! is_string( $chain_id_arg_name ) || empty( $chain_id_arg_name ) ) { $chain_id_arg_name = self::CHAIN_ID_ARG_NAME; } return $chain_id_arg_name; } } endif; free/classes/admin/class-secupress-admin-notices.php 0000644 00000035033 15174670627 0016615 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Admin notices class. * * @package SecuPress * * @author Julio Potier * @since 1.3 * * @author Grégory Viguier * @since 1.0 */ class SecuPress_Admin_Notices extends SecuPress_Singleton { const VERSION = '1.1'; const META_NAME = 'dismissed_secupress_notices'; /** * Singleton The reference to *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** * Will store the notices. * * @var (array) */ protected $notices = array(); /** * Tell if the css styles have been enqueued and prevent to do it twice. * * @var (bool) */ protected static $done_css = false; /** * Tell if the js scripts have been enqueued and prevent to do it twice. * * @var (bool) */ protected static $done_js = false; /** * ".min" suffix to add (or not) to the css/js file name. * * @var (string) */ protected static $suffix; /** * Version to use for the css/js files. * * @var (string) */ protected static $version; /** Public methods ========================================================================== */ /** * Add an admin notice. * * @since 1.0 * * @param (string) $message The message to display in the notice. * @param (string) $error_code Like WordPress notices: "error" "updated" "success" "warning" "info". Default is "updated". * @param (string|bool) $notice_id A unique identifier to tell id the notice is dismissible. * false: the notice is not dismissible. * string: the notice is dismissible and send an ajax call to store the "dismissed" state into a user meta to prevent it to popup again. * empty string: meant for a one-shot use. The notice is dismissible but the "dismissed" state is not stored, it will popup again. This is the exact same behavior than the WordPress dismissible notices. * @param (null|string) $capa A WordPress capability or role. "null" = secupress_get_capability() */ public function add( $message, $error_code = 'updated', $notice_id = false, $capa = null ) { if ( is_null( $capa ) ) { $capa = secupress_get_capability( false, 'notice' ); } if ( false !== $notice_id ) { if ( $notice_id && self::is_dismissed( $notice_id ) ) { return; } // Add notices script. self::enqueue_script(); } // Add notices style. self::enqueue_style(); $notice_id = $notice_id ? sanitize_title( $notice_id ) : $notice_id; if ( ! isset( $this->notices[ $error_code ] ) ) { $this->notices[ $error_code ] = [ 'permanent' => [], 'wp-dismissible' => [], 'sp-dismissible' => [], ]; } if ( false === $notice_id ) { // The notice is not dismissible. $this->notices[ $error_code ]['permanent'][ $capa ][] = $message; } elseif ( $notice_id ) { // The notice is dismissible, with a custom ajax call. $this->notices[ $error_code ]['sp-dismissible'][ $capa ][ $notice_id ] = $message; } else { // Empty string case // The notice is dismissible. $this->notices[ $error_code ]['wp-dismissible'][ $capa ][] = $message; } } /** * Add a temporary admin notice. * * @since 2.4 Fix duplicate notices (based on ID, not content) * @since 1.3 Added $notice_id parameter. * @since 1.0 * * @param (string) $message The message to display in the notice. * @param (string) $error_code Like WordPress notices: "error" "updated" "success" "warning" "info". Default is "updated". * @param (string|bool) $notice_id A unique identifier to tell id the notice is dismissible. * false: the notice is not dismissible. * string: the notice is dismissible and send an ajax call to store the "dismissed" state into a user meta to prevent it to popup again. * enpty string: meant for a one-shot use. The notice is dismissible but the "dismissed" state is not stored, it will popup again. This is the exact same behavior than the WordPress dismissible notices. * @param (null|string) $capa A WordPress capability or role. "null" = secupress_get_capability() */ public function add_temporary( $message, $error_code = 'updated', $notice_id = false, $capa = null ) { $notices = secupress_get_transient( 'secupress-notices-' . get_current_user_id() ); $notices = is_array( $notices ) ? $notices : array(); $notices[ $notice_id ] = compact( 'message', 'error_code', 'capa', 'notice_id' ); secupress_set_transient( 'secupress-notices-' . get_current_user_id(), $notices ); } /** * Dismiss a notice for a user: a user meta is added to keep track of the "dismissed" state. * * @since 1.0 * * @param (string) $notice_id The notice identifier. * @param (int) $user_id User ID. If not set, fallback to the current user ID. * * @return (bool) true on success. */ public static function dismiss( $notice_id, $user_id = 0 ) { $notice_id = $notice_id ? sanitize_title( $notice_id ) : $notice_id; if ( ! $notice_id ) { return false; } $user_id = $user_id ? absint( $user_id ) : get_current_user_id(); $dismissed = (string) get_user_option( self::META_NAME, $user_id ); $dismissed = ',' . $dismissed . ','; if ( false === strpos( $dismissed, ',' . $notice_id . ',' ) ) { $dismissed = trim( $dismissed . $notice_id, ',' ); $dismissed = str_replace( ',,', ',', $dismissed ); update_user_option( $user_id, self::META_NAME, $dismissed ); return true; } return false; } /** * "Undismiss" a notice for a user: the notice is removed from the user meta. * * @since 1.0 * * @param (string) $notice_id The notice identifier. * @param (int) $user_id User ID. */ public static function reinit( $notice_id, $user_id = 0 ) { $notice_id = $notice_id ? sanitize_title( $notice_id ) : $notice_id; if ( ! $notice_id ) { return; } $user_id = $user_id ? absint( $user_id ) : get_current_user_id(); $dismissed = (string) get_user_option( self::META_NAME, $user_id ); $dismissed = ',' . $dismissed . ','; $notice_id = ',' . $notice_id . ','; if ( false !== strpos( $dismissed, $notice_id ) ) { $dismissed = str_replace( array( $notice_id, ',,' ), ',', $dismissed ); $dismissed = trim( $dismissed, ',' ); if ( '' === $dismissed ) { delete_user_option( $user_id, self::META_NAME ); } else { update_user_option( $user_id, self::META_NAME, $dismissed ); } } } /** * Tell if a notice is dismissed. * * @since 1.0 * * @param (string) $notice_id The notice identifier. * * @return (bool|null) true if dismissed, false if not, null if the notice is not dismissible. */ public static function is_dismissed( $notice_id ) { if ( $notice_id ) { // Get dismissed notices. $dismissed = explode( ',', (string) get_user_option( self::META_NAME, get_current_user_id() ) ); $dismissed = array_flip( $dismissed ); return isset( $dismissed[ $notice_id ] ); } return null; } /** Private methods ========================================================================= */ /** * Init: this method is required by the class `SecuPress_Singleton`. * * @since 1.0 */ protected function _init() { self::$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'; self::$version = self::$suffix ? SECUPRESS_VERSION : time(); add_action( 'all_admin_notices', array( $this, 'print_notices' ), 20 ); add_action( 'admin_footer', array( $this, 'print_notices' ), 20 ); add_action( 'wp_ajax_secupress_dismiss-notice', array( __CLASS__, 'dismiss_admin_ajax_cb' ) ); add_action( 'admin_post_secupress_dismiss-notice', array( __CLASS__, 'dismiss_admin_ajax_cb' ) ); } /** * Enqueue JS scripts. * * @since 1.0 */ public static function enqueue_script() { if ( self::$done_js ) { return; } if ( ! did_action( 'admin_enqueue_scripts' ) ) { add_action( 'admin_enqueue_scripts', __METHOD__ ); return; } self::$done_js = true; wp_enqueue_script( 'secupress-notices', SECUPRESS_ADMIN_JS_URL . 'secupress-notices' . self::$suffix . '.js', array( 'jquery' ), self::$version, true ); wp_localize_script( 'secupress-notices', 'SecuPressi18nNotices', array( 'dismiss' => _x( 'Dismiss', 'verb', 'secupress' ), 'nonce' => wp_create_nonce( 'secupress-notices' ), ) ); } /** * Enqueue CSS styles. * * @since 1.0 */ public static function enqueue_style() { if ( self::$done_css ) { return; } if ( ! did_action( 'admin_enqueue_scripts' ) ) { add_action( 'admin_enqueue_scripts', __METHOD__ ); return; } self::$done_css = true; wp_enqueue_style( 'secupress-notices', SECUPRESS_ADMIN_CSS_URL . 'secupress-notices' . self::$suffix . '.css', array(), self::$version ); } /** * Add notices added by `$this->add_temporary()`. * * @since 2.2.6 Get the CRON notifications too * @since 1.3 * @see Was previously called `secupress_display_transient_notices()`. */ public function add_transient_notices() { $notices = secupress_get_transient( 'secupress-notices-' . get_current_user_id(), [] ); $notices_cron = secupress_get_transient( 'secupress-notices-0', [] ); // 0 = CRON user ID, WP default $notices = array_merge( $notices, $notices_cron ); if ( ! $notices ) { return; } delete_transient( 'secupress-notices-' . get_current_user_id() ); delete_transient( 'secupress-notices-0' ); // CRON if ( is_array( $notices ) ) { foreach ( $notices as $notice ) { $notice_id = isset( $notice['notice_id'] ) ? $notice['notice_id'] : false; $capa = isset( $notice['capa'] ) ? $notice['capa'] : null; $this->add( $notice['message'], $notice['error_code'], $notice_id , $capa); } } } /** * Display the notices. * * The notices are displayed by error code ("error" "updated" "success" "warning" "info"), then by type (dismissible with state stored, dismissible like WP, not dismissible). * All not dismissible ones are grouped into one notice. Same thing for the "dismissible like WP" ones. * Only the "dismissible with state stored" are printed separately, so the user can dismiss some and not others. * * @since 2.2.6 Use $capa * @since 1.0 */ public function print_notices() { $this->add_transient_notices(); if ( ! $this->notices ) { return; } $referer = urlencode( esc_url_raw( secupress_get_current_url( 'raw' ) ) ); foreach ( $this->notices as $error_code => $types ) { $types = array_filter( $types, 'count' ); if ( ! $types ) { continue; } foreach ( $types as $type => $roles ) { foreach( $roles as $capa => $messages ) { if ( ! current_user_can( $capa ) ) { continue; } $plugin_name = SECUPRESS_PLUGIN_NAME . ( secupress_has_pro() && ! secupress_is_white_label() ? ' Pro' : '' ); $label = ! secupress_show_contextual_help() ? '' : '<label class="plugin-title">' . esc_html( $plugin_name ) . '</label>'; $lab_class = ! secupress_show_contextual_help() ? '' : ' has-plugin-title'; $error_class = str_replace( '_', ' ', $error_code ); if ( strpos( $error_class, 'no-plugin-title' ) !== false ) { $error_class = str_replace( 'no-plugin-title', '', $error_class ); $lab_class = ''; $label = ''; } if ( 'sp-dismissible' === $type ) { foreach ( $messages as $notice_id => $message ) { $button = admin_url( 'admin-post.php?action=secupress_dismiss-notice¬ice_id=' . $notice_id . '&_wp_http_referer=' . $referer ); $button = wp_nonce_url( $button, 'secupress-notices' ); $button = '<a href="' . esc_url( $button ) . '" class="notice-dismiss"><span class="screen-reader-text">' . __( 'Dismiss', 'secupress' ) . '</span></a>'; // $message = strpos( $message, '<p>' ) === false && trim( $message ) ? '<p>' . $message . '</p>' : $message; if ( 'error' === $error_code && ! empty( trim( $message ) ) ) { $message = '<p>' . sprintf( __( '<strong>Error</strong>: %s', 'secupress' ), $message ) . '</p>'; } ?> <div class="secupress-notice notice <?php echo $error_class . $lab_class; ?> secupress-is-dismissible" data-id="<?php echo $notice_id; ?>"> <?php echo $label; ?> <?php echo $message; ?> <?php echo $button; ?> </div> <?php unset( $this->notices[ $error_code ][ $type ][ $capa ][ $notice_id ] ); } } elseif ( 'wp-dismissible' === $type ) { ?> <div class="secupress-notice notice <?php echo $error_class . $lab_class; ?> secupress-is-dismissible"> <?php echo $label; ?> <?php $message = implode( '<br class="separator"/>', $messages ); if ( 'error' === $error_code ) { $message = sprintf( __( '<strong>Error</strong>: %s', 'secupress' ), $message ); } echo strpos( $message, '<p>' ) === false ? '<p>' . $message . '</p>' : $message; unset( $this->notices[ $error_code ][ $type ] ); ?> </div> <?php } else { ?> <div class="secupress-notice notice <?php echo $error_class . $lab_class; ?>"> <?php echo $label; ?> <?php $message = implode( '<br class="separator"/>', $messages ); echo strpos( $message, '<p>' ) === false ? '<p>' . $message . '</p>' : $message; if ( 'error' === $error_code ) { $message = sprintf( __( '<strong>Error</strong>: %s', 'secupress' ), $message ); } unset( $this->notices[ $error_code ][ $type ] ); ?> </div> <?php } } } } } /** * Ajax callback that stores the "dismissed" state. * * @since 2.2.6 Usage of the $capa param * @since 1.0 */ public static function dismiss_admin_ajax_cb() { if ( empty( $_REQUEST['notice_id'] ) ) { // WPCS: CSRF ok. wp_die( -1 ); } secupress_check_admin_referer( 'secupress-notices' ); $notice_id = $_REQUEST['notice_id']; $notice_id = $notice_id ? sanitize_title( $notice_id ) : $notice_id; /** * @since 1.0 * @since 2.2.6 Deprecated filter */ if ( has_filter( 'secupress.notices.dismiss_capability' ) ) { _deprecated_hook( 'secupress.notices.dismiss_capability', '2.2.6', '(none)', 'A 4th parameter $capa has been added to secupress_add_notice()' ); } if ( wp_doing_ajax() ) { if ( self::dismiss( $notice_id ) ) { wp_die( 1 ); } } else { if ( self::dismiss( $notice_id ) ) { wp_safe_redirect( esc_url_raw( wp_get_referer() ) ); die(); } } if ( wp_doing_ajax() ) { wp_die( -1 ); } else { secupress_admin_die(); } } } free/classes/admin/class-secupress-admin-pointers.php 0000644 00000017451 15174670627 0017020 0 ustar 00 <?php /** * Administration API: SecuPress_Admin_Pointers class * * @since 2.0 */ /** * Core class used to implement an internal admin pointers API. * * @since 2.0 */ final class SecuPress_Admin_Pointers { /** * Initializes the new feature pointers. * * @since 2.0 * * All pointers can be disabled using the following: * remove_action( 'admin_enqueue_scripts', array( 'SecuPress_Admin_Pointers', 'enqueue_scripts' ) ); * * Individual pointers (e.g. spxx_foobar) can be disabled using the following: * remove_action( 'admin_print_footer_scripts', array( 'SecuPress_Admin_Pointers', 'pointer_spxx_foobar' ) ); * * @static * * @param string $hook_suffix The current admin page. */ public static function enqueue_scripts( $hook_suffix ) { if ( ! secupress_get_capability() ) { return; } /* * Register feature pointers * * Format: * array( * hook_suffix => pointer callback * ) * * Example: * array( * 'plugins.php' => 'spxx_foobar', * 'secupress_page_secupress_xx' => 'spxx_foobar' * ) */ $registered_pointers = [ 'secupress_page_secupress_modules' => [ 'any' => [ 'sp22_ad' ], //'logs' => [ 'sp21_httplogs' ], ], ]; // Check if screen related pointer is registered. if ( ! isset( $registered_pointers[ $hook_suffix ] ) ) { return; } $pointers = isset( $registered_pointers[ $hook_suffix ]['any'] ) ? $registered_pointers[ $hook_suffix ]['any'] : []; $module = isset( $_GET['module'] ) ? sanitize_key( $_GET['module' ] ) : 'any'; // Do not translate. if ( isset( $registered_pointers[ $hook_suffix ][ $module ] ) ) { $pointers = array_merge( $pointers, $registered_pointers[ $hook_suffix ][ $module ] ); } $dismissed = explode( ',', (string) get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true ) ); $pointers = array_diff( $pointers, $dismissed ); // Limit pointers to 2 per screen order by the natural array order $pointers = array_slice( $pointers, 0, 2 ); $pointers = array_flip( $pointers ); $pointers = array_flip( $pointers ); foreach ( $pointers as $pointer ) { // Bind pointer print function add_action( 'admin_print_footer_scripts', array( 'SecuPress_Admin_Pointers', 'pointer__' . $pointer ) ); // Add pointers script and style to queue wp_enqueue_style( 'wp-pointer' ); wp_enqueue_script( 'wp-pointer' ); add_action( 'admin_print_footer_scripts', array( 'SecuPress_Admin_Pointers', 'print_pointer_css_rules' ) ); } } /** * Print the pointer JavaScript data. * * @since 2.0 * * @static * * @param string $pointer_id The pointer ID. * @param string $selector The HTML elements, on which the pointer should be attached. * @param array $args Arguments to be passed to the pointer JS (see wp-pointer.js). */ private static function print_js( $pointer_id, $selector, $args ) { if ( empty( $pointer_id ) || empty( $selector ) || empty( $args ) || empty( $args['content'] ) ) { return; } ?> <script type="text/javascript"> (function($){ var options = <?php echo wp_json_encode( $args ); ?>, setup; if ( ! options ) return; options = $.extend( options, { close: function() { $.post( ajaxurl, { pointer: '<?php echo $pointer_id; ?>', _ajaxnonce: '<?php echo wp_create_nonce( "dismiss-pointer_{$pointer_id}" ); ?>', action: 'dismiss-sp-pointer' }); } }); setup = function() { $('<?php echo $selector; ?>').first().pointer( options ).pointer('open'); }; if ( options.position && options.position.defer_loading ) $(window).bind( 'load.wp-pointers', setup ); else $(document).ready( setup ); })( jQuery ); </script> <?php } /** * Print the pointer CSS rules. * Don't add those pointers in CSS because they will change the WP one and we cannot add a prefix, just print it. * * @since 2.0 * * @static */ public static function print_pointer_css_rules() { ?> <style> .wp-pointer .wp-pointer-content h3 { background-color: #26B3A9; border-color: #26B3A9; padding: 15px 18px 14px 12px; } .wp-pointer .wp-pointer-content h3 .dashicons { font-size: 2em; vertical-align: text-bottom; margin-right: 7px; margin-top: -6px; padding: 2px } .wp-pointer .wp-pointer-content h3 .dashicons-heart { color: #CA4A1F; } .wp-pointer .wp-pointer-content h3 .dashicons-star-filled { color: #F1C40F; } .wp-pointer .wp-pointer-content h3 .dashicons-money-alt { color: #FF0; } .wp-pointer .wp-pointer-content h3:before { display: none; } </style> <?php } /** * New GeoIP Localisation API * * @since 2.1 */ public static function pointer__sp21_httplogs() { $content = '<h3><span class="dashicons dashicons-star-filled"></span> ' . __( 'New HTTP Logs Feature', 'secupress' ) . '</h3>'; $content .= '<h4>' . __( 'You can now filter the HTTP outputs', 'secupress' ) . '</h4>'; $content .= '<p>' . __( 'You can restrict how many time per day each URL can be called.<br>You can also just check which URLs are called from your site.<br>These filters will help you to improve the security AND the loading speed of your site.', 'secupress' ) . '</p>'; $position = array( 'edge' => 'right', 'align' => 'top', ); $js_args = array( 'content' => $content, 'position' => $position, 'pointerClass' => 'wp-pointer arrow-bottom', /** Translators: Format 'ddd%' or 'ddd', not 'px' */ 'pointerWidth' => _x( '400', 'pointerWidth', 'secupress' ), ); self::print_js( str_replace( 'pointer__', '', __FUNCTION__ ), '.secupress-setting-row_logs_http-logs-activated', $js_args ); } /** * New ad, try SP pro * * @since 2.2 */ public static function pointer__sp22_ad() { if ( false !== apply_filters( 'secupress.no_sideads', false ) ) { // Filter secupress_no_sideads. return; } $sideads = get_transient( 'secupress_sideads' ); if ( ! $sideads || ! isset( $sideads[0]['pointer'] ) ) { return; } $key = 'pointer'; if ( secupress_locale_is_FR( get_user_locale() ) ) { $key .= '-fr_FR'; } $content = '<h3>' . $sideads[0][ $key ]['title'] . '</h3>'; $content .= '<h4>' . $sideads[0][ $key ]['subtitle'] . '</h4>'; $content .= '<p>' . $sideads[0][ $key ]['desc'] . '</p>'; $position = array( 'edge' => 'right', 'align' => 'top', ); $js_args = array( 'content' => $content, 'position' => $position, 'pointerClass' => 'wp-pointer arrow-bottom', /** Translators: Format 'ddd%' or 'ddd', not 'px' */ 'pointerWidth' => _x( '400', 'pointerWidth', 'secupress' ), ); self::print_js( str_replace( 'pointer__', '', __FUNCTION__ ), '.secupress-pro-ad', $js_args ); } /** * New addons module * * @since 2.0 */ public static function pointer__sp20_addonszz() { if ( isset( $_GET['module'] ) && 'addons' === $_GET['module'] ) { secupress_dismiss_pointer_admin_post_cb( 'sp20_addonszz' ); return; } $content = '<h3><span class="dashicons dashicons-heart"></span> ' . __( 'New Module: Addons', 'secupress' ) . '</h3>'; $content .= '<h4>' . __( 'Discover our 2 brand new recommandations.', 'secupress' ) . '</h4>'; $position = array( 'edge' => 'left', 'align' => 'bottom', ); $js_args = array( 'content' => $content, 'position' => $position, 'pointerClass' => 'wp-pointer arrow-bottom', /** Translators: Format 'ddd%' or 'ddd', not 'px' */ 'pointerWidth' => _x( '400', 'pointerWidth', 'secupress' ), ); self::print_js( str_replace( 'pointer__', '', __FUNCTION__ ), '.module-addons', $js_args ); } /** * Prevents new users from seeing existing pointers. * * @since 2.0 * * @static * * @param int $user_id User ID. */ public static function dismiss_pointers_for_new_users( $user_id ) { add_user_meta( $user_id, 'dismissed_wp_pointers', 'sp22_ad' ); } } free/classes/admin/class-secupress-admin-offer-migration.php 0000644 00000037673 15174670627 0020255 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Free / Pro migration class. This class must be extended. * * @package SecuPress * @since 1.3 */ abstract class SecuPress_Admin_Offer_Migration extends SecuPress_Singleton { /** * Class version. * * @var (string) */ const VERSION = '1.0'; /** * Name of the transient used to store the Pro plugin information. * * @var (string) */ const TRANSIENT_NAME = 'secupress_offer_migration_information'; /** * Plugin basename of the Free plugin. * * @var (string) */ protected static $free_plugin_basename; /** * Plugin basename of the Pro plugin. * * @var (string) */ protected static $pro_plugin_basename; /** * Plugin basename of the current plugin. * * @var (string) */ protected static $plugin_basename; /** * Path to the Free plugin. * * @var (string) */ protected static $free_plugin_path; /** * Path to the Pro plugin. * * @var (string) */ protected static $pro_plugin_path; /** * Tell if the init has been done. * * @var (bool) */ private static $init_done = false; /** Init ==================================================================================== */ /** * Init: this method is required by the class `SecuPress_Singleton`. * * @since 1.3 * @author Grégory Viguier */ protected function _init() { if ( self::$init_done ) { return; } self::$init_done = true; if ( ! secupress_has_pro() ) { // This is the Free plugin. static::$free_plugin_basename = plugin_basename( SECUPRESS_FILE ); static::$pro_plugin_basename = 'secupress-pro/secupress-pro.php'; static::$plugin_basename = static::$free_plugin_basename; static::$free_plugin_path = SECUPRESS_FILE; static::$pro_plugin_path = dirname( dirname( SECUPRESS_FILE ) ) . '/' . static::$pro_plugin_basename; } else { // This is the Pro plugin. static::$free_plugin_basename = 'secupress/secupress.php'; static::$pro_plugin_basename = plugin_basename( SECUPRESS_FILE ); static::$plugin_basename = static::$pro_plugin_basename; static::$free_plugin_path = dirname( dirname( SECUPRESS_FILE ) ) . '/' . static::$free_plugin_basename; static::$pro_plugin_path = SECUPRESS_FILE; } add_filter( 'site_transient_update_plugins', array( __CLASS__, 'maybe_add_migration_data' ) ); static::maybe_end_migration(); } /** Public methods ========================================================================== */ /** * Filter the value of the 'update_plugins' site transient to upgrade from Free to Pro, or Pro to Free. * We add a "fake" update to the Free/Pro plugin, containing the Pro/Free information. * * @since 1.3 * @author Grégory Viguier * * @param (object|bool) $value Value of the site transient: an object or false. * * @return (object|bool) */ public static function maybe_add_migration_data( $value ) { $plugin = static::$plugin_basename; if ( ! static::is_update_page() || ! is_object( $value ) ) { return $value; } if ( ! isset( $_GET['action'], $_GET['plugin'] ) || 'upgrade-plugin' !== $_GET['action'] || $plugin !== $_GET['plugin'] ) { // Only when requesting the update. return $value; } $information = static::get_transient(); if ( null === $information ) { // The information is not valid, cleanup the transient. static::delete_transient(); return $value; } if ( ! $information ) { // The transient doesn't exist. return $value; } // Add the data to the transient. unset( $value->no_update[ $plugin ] ); if ( ! isset( $value->response ) || ! is_array( $value->response ) ) { $value->response = array(); } $value->response[ $plugin ] = $information; add_filter( 'upgrader_post_install', array( __CLASS__, 'prevent_js_error' ), SECUPRESS_INT_MAX ); add_action( 'upgrader_process_complete', array( __CLASS__, 'cleanup_license' ), 100, 2 ); return $value; } /** * Prevent a stupid JS error. * * @since 1.3 * @see WP_Upgrader_Skin::decrement_update_count() * @author Grégory Viguier * * @param (bool|object) $response Install response. * * @return (bool|object) */ public static function prevent_js_error( $response ) { if ( ! $response || is_wp_error( $response ) || 'up_to_date' === $response ) { return $response; } echo '<script type="text/javascript"> window.wp = window.wp || {}; window.wp.updates = window.wp.updates || {}; </script>'; return $response; } /** * Once the migration process is complete: remove the license from the Free, or remove any license error from the Pro. * * @since 1.3 * @author Grégory Viguier * * @param (object) $upgrader WP_Upgrader instance. In other contexts, $this, might be a Theme_Upgrader, Plugin_Upgrader, Core_Upgrade, or Language_Pack_Upgrader instance. * @param (array) $hook_extra Array of bulk item update data. */ public static function cleanup_license( $upgrader, $hook_extra ) { if ( empty( $hook_extra['plugin'] ) || empty( $upgrader->result ) || is_wp_error( $upgrader->result ) ) { return; } $options = get_site_option( SECUPRESS_SETTINGS_SLUG ); // Both versions: remove any license error. unset( $options['license_error'] ); if ( $hook_extra['plugin'] === static::$pro_plugin_basename ) { // Free plugin: remove the license and license error. unset( $options['consumer_email'], $options['consumer_key'], $options['site_is_pro'] ); } secupress_update_options( $options ); /** * Tell the migration is done. * * We can't trigger the new plugin activation hook now because in case of Free -> Pro migration, the Pro plugin is not included yet, it will be at next page load. So instead we store the information. * See `static::maybe_end_migration()`. */ $transient = static::get_transient(); $transient->migration_done = 1; static::set_transient( $transient ); } /** * Delete the migration data and trigger a "migration done" action. * * @since 1.3 * @author Grégory Viguier */ public static function maybe_end_migration() { if ( static::is_update_page() ) { return; } $transient = static::get_transient( true ); if ( empty( $transient->migration_done ) ) { return; } $type = ! empty( $transient->secupress_data_type ) ? $transient->secupress_data_type : false; if ( ! $type || ( secupress_has_pro() && 'free' === $type ) || ( ! secupress_has_pro() && 'pro' === $type ) ) { return; } /** * Triggers after the offer migration. * * @since 1.3 * @author Grégory Viguier */ do_action( 'secupress.offer_migration.migration_done' ); static::delete_transient(); } /** * Get the URL of the user account on secupress.me. * * @since 1.3 * @author Grégory Viguier * * @return (string) A URL. */ public static function get_account_url() { /** Translators: this is the slug (part of the URL) of the account page on secupress.me, like in https://secupress.me/account/, it must not be translated if the page doesn't exist. */ return trailingslashit( set_url_scheme( SECUPRESS_WEB_MAIN, 'https' ) ) . _x( 'account', 'link to website (Only FR or EN!)', 'secupress' ) . '/'; } /** * Get the URL of our support page on secupress.me. * * @since 1.3 * @author Grégory Viguier * * @return (string) A URL. */ public static function get_support_url() { $email = 'sserpuces' . chr( 64 ); $email = strrev( 'em.' . $email . 'troppus' ); $subject = esc_html__( 'HALP!', 'secupress' ); $subject = wp_specialchars_decode( $subject ); $subject = str_replace( '+', '%20', urlencode( $subject ) ); return 'mailto:' . $email . '?subject=' . $subject; } /** * Get the URL allowing to install the Free or Pro plugin. * While we want to install the Pro plugin, it's the URL for the Free plugin. And it's the opposite when we want to install the Free plugin. * * @since 1.3 * @author Grégory Viguier * * @return (string) A URL. */ public static function get_install_url() { $install_url = is_multisite() ? network_admin_url( 'update.php' ) : admin_url( 'update.php' ); $args = array( 'action' => 'upgrade-plugin', 'plugin' => static::$plugin_basename, '_wpnonce' => wp_create_nonce( 'upgrade-plugin_' . static::$plugin_basename ), ); return add_query_arg( $args, $install_url ); } /** * Get the URL allowing to install the Free plugin. * * @since 1.3 * @author Grégory Viguier * * @return (string) A URL. */ protected static function get_post_install_url() { $install_url = admin_url( 'admin-post.php' ); $args = array( 'action' => static::POST_ACTION, '_wpnonce' => wp_create_nonce( static::POST_ACTION ), ); return add_query_arg( $args, $install_url ); } /** * Get the URL of the settings page. * * @since 1.3 * @author Grégory Viguier * * @return (string) A URL. */ public static function get_settings_url() { return secupress_admin_url( 'settings' ); } /** * Get our (validated) transient. * * @since 1.3 * @author Grégory Viguier * * @param (bool) $raw True to return raw data. False to validate before returning. * * @return (object|bool) The Pro plugin information. False if empty. */ public static function get_transient( $raw = false ) { $information = secupress_get_site_transient( self::TRANSIENT_NAME ); return $raw ? $information : static::validate_plugin_information( $information ); } /** * Delete our transient. * * @since 1.3 * @author Grégory Viguier */ public static function delete_transient() { secupress_delete_site_transient( self::TRANSIENT_NAME ); } /** * Set our transient. * * @since 1.3 * @author Grégory Viguier * * @param (object) $information The Pro plugin information. * @param (bool) $automatic_install When true, the property `automatic_install` is added to the transient value. * This property is used in `$this->maybe_install_pro_version()` to automatically redirect the user to the installation process. */ public static function set_transient( $information, $automatic_install = false ) { if ( $automatic_install ) { $information->automatic_install = 1; } secupress_set_site_transient( self::TRANSIENT_NAME, $information ); } /** Private methods ========================================================================= */ /** * Tell if the current user has the capability to manipulate SecuPress. * * @since 1.3 * @author Grégory Viguier * * @return (bool) */ protected static function current_user_can() { static $can; if ( ! isset( $can ) ) { $can = current_user_can( secupress_get_capability() ); } return $can; } /** * Tell if we're in the page that WP displays when we're installing a plugin. * * @since 1.3 * @author Grégory Viguier * * @param (string) $current_page The current screen name. */ protected static function is_update_page( $current_page = null ) { global $pagenow; if ( $current_page ) { return 'update.php' === $current_page; } return 'update.php' === $pagenow; } /** * Tell if we're in our settings page. * * @since 1.3 * @author Grégory Viguier */ protected static function is_settings_page() { global $current_screen; $base = SECUPRESS_PLUGIN_SLUG . '_page_' . SECUPRESS_PLUGIN_SLUG . '_settings' . ( is_multisite() ? '-network' : '' ); return $base === $current_screen->base; } /** * Small validation of the data containing the information about the Pro version of the plugin. * * @since 1.3 * @author Grégory Viguier * * @param (object|bool) $information The object containing the information. * @param (bool) $is_raw_data When true, that means the data comes from a remote request. Some extra validation and formatting are done. * * @return (object|bool|null) The information object on success, null on failure, false if the data is false. */ protected static function validate_plugin_information( $information, $is_raw_data = false ) { if ( false === $information ) { return false; } // Make sure tha data is what we expect. if ( ! $information || ! is_object( $information ) ) { return null; } // Make sure it's the data for the right plugin (in Free we want Pro data, and in Pro we want Free data). $is_pro = secupress_has_pro(); $type = ! empty( $information->secupress_data_type ) ? $information->secupress_data_type : false; if ( ! $type || ( $is_pro && 'free' !== $type ) || ( ! $is_pro && 'pro' !== $type ) ) { return null; } if ( ! $is_raw_data ) { return $information; } // Keep only the needed data and fill in the empty values. $information = (array) $information; $information = secupress_array_merge_intersect( $information, array( 'name' => '', // Should be set. 'slug' => '', // Should be set (but useless). 'plugin' => '', 'version' => '', // Should be set. 'new_version' => '', 'homepage' => '', // Should be set. 'url' => '', 'download_link' => '', // Should be set. 'package' => '', 'secupress_data_type' => '', // Is set. ) ); $information['slug'] = dirname( static::$plugin_basename ); $information['plugin'] = static::$plugin_basename; if ( empty( $information['new_version'] ) ) { $information['new_version'] = $information['version']; } if ( empty( $information['url'] ) ) { $information['url'] = $information['homepage']; } if ( empty( $information['package'] ) ) { $information['package'] = $information['download_link']; } return (object) $information; } /** * Delete the Pro plugin. * * @since 1.3 * @author Grégory Viguier * * @return (bool) True on success or if the plugin wasn't installed. False on failure. */ protected static function delete_pro_plugin() { $filesystem = secupress_get_filesystem(); if ( ! $filesystem->exists( static::$pro_plugin_path ) ) { return true; } return $filesystem->delete( dirname( static::$pro_plugin_path ), true ); } /** * Add an admin notice. * * @since 1.3 * @author Grégory Viguier * * @param (string) $message The message to display in the notice. * @param (string) $error_code Like WordPress notices: "error" or "updated". Default is "updated". * @param (string|bool) $notice_id A unique identifier to tell id the notice is dismissible. * false: the notice is not dismissible. * string: the notice is dismissible and send an ajax call to store the "dismissed" state into a user meta to prevent it to popup again. * enpty string: meant for a one-shot use. The notice is dismissible but the "dismissed" state is not stored, it will popup again. This is the exact same behavior than the WordPress dismissible notices. */ protected static function add_notice( $message, $error_code = 'updated', $notice_id = '' ) { secupress_add_notice( $message, $error_code, $notice_id ); } /** * Add a "transient" admin notice. * * @since 1.3 * @author Grégory Viguier * * @param (string) $message The message to display in the notice. * @param (string) $error_code Like WordPress notices: "error" or "updated". Default is "updated". * @param (string|bool) $notice_id A unique identifier to tell id the notice is dismissible. * false: the notice is not dismissible. * string: the notice is dismissible and send an ajax call to store the "dismissed" state into a user meta to prevent it to popup again. * enpty string: meant for a one-shot use. The notice is dismissible but the "dismissed" state is not stored, it will popup again. This is the exact same behavior than the WordPress dismissible notices. */ protected static function add_transient_notice( $message, $error_code = 'updated', $notice_id = '' ) { secupress_add_transient_notice( $message, $error_code, $notice_id ); } } free/classes/admin/class-secupress-admin-pro-upgrade.php 0000644 00000027035 15174670627 0017401 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Pro upgrade class. * * @package SecuPress * @since 1.3 */ class SecuPress_Admin_Pro_Upgrade extends SecuPress_Admin_Offer_Migration { /** * Class version. * * @var (string) */ const VERSION = '2.2.6'; /** * Name of the post action used to install the Pro plugin. * * @var (string) */ const POST_ACTION = 'secupress_maybe_install_pro_version'; /** * The reference to the "Singleton" instance of this class. * * @var (object) */ protected static $_instance; /** Init ==================================================================================== */ /** * Init. * * @since 1.3 * @author Grégory Viguier */ protected function _init() { if ( wp_doing_ajax() ) { parent::_init(); return; } if ( ! secupress_has_pro() ) { add_action( 'current_screen', array( $this, 'maybe_warn_to_install_pro_version' ) ); add_action( 'admin_post_' . self::POST_ACTION, array( $this, 'maybe_install_pro_version' ) ); } else { add_action( 'secupress.offer_migration.migration_done', array( $this, 'maybe_trigger_activation_hooks' ) ); add_action( 'admin_head', array( $this, 'maybe_congratulate' ) ); add_action( 'admin_footer', array( $this, 'maybe_redirect_to_settings' ), SECUPRESS_INT_MAX ); } parent::_init(); } /** Public methods ========================================================================== */ /** * If the `$automatic_install` property is set in the plugin information, redirect and install the Pro version. * Otherwise, display a notice to install the Pro version. * * @since 1.3 * @author Grégory Viguier */ public function maybe_warn_to_install_pro_version() { if ( static::is_update_page() ) { return; } if ( ! static::current_user_can() ) { return; } if ( ! secupress_has_pro_license() ) { // If the license is not valid. return; } $information = static::get_transient(); if ( $information && ! empty( $information->automatic_install ) ) { // Install the Pro version by redirecting the user to the install URL. unset( $information->automatic_install ); static::set_transient( $information ); // This one will be used to do a redirection once the Pro plugin is installed. secupress_set_site_transient( 'secupress_offer_migration_redirect', 1 ); wp_safe_redirect( esc_url_raw( static::get_post_install_url() ) ); die(); } if ( null === $information ) { // The information is not valid, cleanup the transient. static::delete_transient(); } // Display a notice asking the user to install the Pro version. $message = sprintf( /** Translators: 1 is a "upgrade" link. */ __( 'You can now %s to the Pro version.', 'secupress' ), '<a href="' . esc_url( static::get_post_install_url() ) . '">' . _x( 'upgrade', 'verb', 'secupress' ) . '</a>' ); static::add_notice( $message, 'updated', false ); } /** * Maybe install the Pro plugin. * If the Pro plugin information is missing, we get fresh data from our server first. * If the Pro plugin is already installed, we delete it. * Once the information is stored, install the Pro version. * * @since 1.3 * @author Grégory Viguier */ public function maybe_install_pro_version() { if ( ! static::current_user_can() ) { return; } if ( empty( $_GET['_wpnonce'] ) || ! wp_verify_nonce( $_GET['_wpnonce'], self::POST_ACTION ) ) { secupress_admin_die(); } if ( ! secupress_has_pro_license() ) { // If the license is not valid. return; } // Make sure we have the plugin information. $information = static::get_transient(); if ( null === $information ) { // The information is not valid, cleanup the transient. static::delete_transient(); } /** * If the information is empty (false) or not valid (null), get fresh data. */ if ( ! $information ) { // At this point, the transient doesn't exist. $information = $this->get_remote_information(); if ( ! $information ) { $message = sprintf( /** Translators: %s is a link to the "SecuPress account". */ __( 'A problem occurred while retrieving the Pro version information. Please download the plugin from your %s and proceed as follow in that order: do NOT uninstall the Free plugin or you’ll lose all your settings (but you can deactivate it if you want), install and activate the Pro plugin, the Free plugin magically disappeared.', 'secupress' ), '<a target="_blank" title="' . esc_attr__( 'Open in a new window.', 'secupress' ) . '" href="' . esc_url( static::get_account_url() ) . '">' . __( 'SecuPress account', 'secupress' ) . '</a>' ); static::add_transient_notice( $message, 'error' ); wp_safe_redirect( esc_url_raw( wp_get_referer() ) ); die(); } static::set_transient( $information ); } /** * If the Pro version is already installed (but not activated of course), we delete it, we want to make sure to install a fresh copy. */ if ( ! static::delete_pro_plugin() ) { static::delete_transient(); $message = sprintf( /** Translators: %s is a link to the "SecuPress account". */ __( 'It seems you already installed the Pro version. An attempt has been made to replace it with a fresh copy but it couldn’t be deleted (which is not normal). Please download the plugin from your %s and proceed as follow in that order: do NOT uninstall the Free plugin or you’ll lose all your settings (but you can deactivate it if you want), install and activate the Pro plugin, the Free plugin magically disappeared.', 'secupress' ), '<a target="_blank" title="' . esc_attr__( 'Open in a new window.', 'secupress' ) . '" href="' . esc_url( static::get_account_url() ) . '">' . __( 'SecuPress account', 'secupress' ) . '</a>' ); static::add_transient_notice( $message, 'error' ); wp_safe_redirect( esc_url_raw( wp_get_referer() ) ); die(); } /** * OK, we have all we need, `static::add_migration_data()` will do the rest. */ wp_safe_redirect( esc_url_raw( static::get_install_url() ) ); die(); } /** * After Free -> Pro migration, trigger activation hooks. * * @since 1.3 * @author Grégory Viguier */ public function maybe_trigger_activation_hooks() { // Store the user ID, it will be used to display the congratulations notice. add_user_meta( get_current_user_id(), 'secupress_migration_congrats', 1, true ); // Trigger activation hooks. $plugin = static::$plugin_basename; $network_wide = is_multisite(); /** This hook is documented in wp-admin/includes/plugin.php. */ do_action( "activate_{$plugin}", $network_wide ); } /** * Display a warning when the Pro plugin has been installed. * * @since 1.3 * @author Grégory Viguier */ public function maybe_congratulate() { if ( ! static::current_user_can() ) { return; } $user_id = get_current_user_id(); $congrats = get_user_meta( $user_id, 'secupress_migration_congrats', true ); if ( ! $congrats ) { return; } delete_user_meta( $user_id, 'secupress_migration_congrats' ); $message = __( 'Congratulations, your Pro version has been installed 🎉.', 'secupress' ); static::add_notice( $message ); } /** * Once the Pro version is installed and activated, redirect to the settings page, but only if the user just submitted the license key. This is printed right after the "Plugin reactivated successfully.". * * @since 1.3 * @see iframe_footer() * @author Grégory Viguier * * @param (string) $hook_suffix The hook name (also known as the hook suffix) used to determine the current screen. */ public function maybe_redirect_to_settings( $hook_suffix ) { if ( ! static::is_update_page( $hook_suffix ) || ! defined( 'IFRAME_REQUEST' ) || ! IFRAME_REQUEST ) { return; } if ( ! isset( $_GET['action'], $_GET['success'], $_GET['plugin'], $_GET['_wpnonce'] ) || isset( $_GET['failure'] ) ) { return; } if ( 'activate-plugin' !== $_GET['action'] || static::$plugin_basename !== $_GET['plugin'] || ! wp_verify_nonce( $_GET['_wpnonce'], 'activate-plugin_' . $_GET['plugin'] ) ) { return; } if ( secupress_get_site_transient( 'secupress_offer_migration_redirect' ) ) { secupress_delete_site_transient( 'secupress_offer_migration_redirect' ); echo '<script type="text/javascript">window.top.location.href = "' . esc_url_raw( secupress_admin_url( 'settings' ) ) . '";</script>'; } } /** * Validate raw data for our transient and, depending on the result, delete the transient or set its value. * If the transient is set, the `$automatic_install` is set to true. * * @since 1.3 * @author Grégory Viguier * * @param (object|bool) $information The Pro plugin information. False otherwise. */ public function maybe_set_transient_from_remote( $information ) { if ( $information && is_object( $information ) ) { $information->secupress_data_type = 'pro'; } $information = static::validate_plugin_information( $information, true ); if ( $information ) { static::set_transient( $information, true ); } else { static::delete_transient(); } } /** Private methods ========================================================================= */ /** * Get the Pro plugin information with a remote request. * * @since 1.3 * @author Grégory Viguier * * @return (object|bool|null) The information object on success, null on failure, false if the data is false. */ public function get_remote_information() { $headers = secupress_get_basic_auth_headers( secupress_get_consumer_email(), secupress_get_consumer_key() ); $url = SECUPRESS_API_MAIN . 'key/v2/?sp_action=get_upgrade_data'; $information = wp_remote_get( $url, [ 'timeout' => 15, 'headers' => $headers ] ); if ( is_wp_error( $information ) || 200 !== wp_remote_retrieve_response_code( $information ) ) { return false; } $information = wp_remote_retrieve_body( $information ); $information = @json_decode( $information ); if ( ! is_object( $information ) || empty( $information->success ) ) { return $information; } if ( empty( $information->data ) ) { return null; } if ( ! is_array( $information->data ) && ! is_object( $information->data ) ) { return null; } $information = (object) $information->data; $information->secupress_data_type = 'pro'; $information = static::validate_plugin_information( $information, true ); return $information; } /** * Small validation of the data containing the information about the Pro version of the plugin. * * @since 1.3 * @author Grégory Viguier * * @param (object|bool) $information The object containing the information. * @param (bool) $is_raw_data When true, that means the data comes from a remote request. Some extra validation and formatting are done. * * @return (object|bool|null) The information object on success, null on failure, false if the data is false. */ protected static function validate_plugin_information( $information, $is_raw_data = false ) { $information = parent::validate_plugin_information( $information, $is_raw_data ); if ( ! $information ) { return $information; } // Make sure the URLs lead to our site. $secupress_url = trailingslashit( set_url_scheme( SECUPRESS_WEB_MAIN, 'https' ) ); $url_keys = array( 'url', 'homepage', 'package', 'download_link' ); foreach ( $url_keys as $url_key ) { if ( empty( $information->$url_key ) ) { continue; } $url_key = set_url_scheme( $information->$url_key, 'https' ); if ( strpos( $url_key, $secupress_url ) !== 0 ) { return null; } } return $information; } } free/classes/admin/class-secupress-admin-wp-async-request.php 0000644 00000007536 15174670627 0020407 0 ustar 00 <?php /** * WP Async Request * * @package WP-Background-Processing */ if ( ! class_exists( 'WP_Async_Request' ) ) : /** * Abstract WP_Async_Request class. * * @abstract */ abstract class WP_Async_Request { /** * Prefix * * (default value: 'wp') * * @var string * @access protected */ protected $prefix = 'wp'; /** * Action * * (default value: 'async_request') * * @var string * @access protected */ protected $action = 'async_request'; /** * Identifier * * @var mixed * @access protected */ protected $identifier; /** * Data * * (default value: array()) * * @var array * @access protected */ protected $data = array(); /** * Initiate new async request. */ public function __construct() { $this->identifier = $this->prefix . '_' . $this->action; add_action( 'wp_ajax_' . $this->identifier, array( $this, 'maybe_handle' ) ); add_action( 'wp_ajax_nopriv_' . $this->identifier, array( $this, 'maybe_handle' ) ); } /** * Set data used during the request. * * @param array $data Data. * * @return $this */ public function data( $data ) { $this->data = $data; return $this; } /** * Dispatch the async request. * * @return array|WP_Error|false HTTP Response array, WP_Error on failure, or false if not attempted. */ public function dispatch() { $url = add_query_arg( $this->get_query_args(), $this->get_query_url() ); $args = $this->get_post_args(); return wp_remote_post( esc_url_raw( $url ), $args ); } /** * Get query args. * * @return array */ protected function get_query_args() { if ( property_exists( $this, 'query_args' ) ) { return $this->query_args; } $args = array( 'action' => $this->identifier, 'nonce' => wp_create_nonce( $this->identifier ), ); /** * Filters the query arguments used during an async request. * * @param array $args */ return apply_filters( $this->identifier . '_query_args', $args ); } /** * Get query URL. * * @return string */ protected function get_query_url() { if ( property_exists( $this, 'query_url' ) ) { return $this->query_url; } $url = admin_url( 'admin-ajax.php' ); /** * Filters the query URL used during an async request. * * @param string $url */ return apply_filters( $this->identifier . '_query_url', $url ); } /** * Get post args. * * @return array */ protected function get_post_args() { if ( property_exists( $this, 'post_args' ) ) { return $this->post_args; } $args = array( 'timeout' => 5, 'blocking' => false, 'body' => $this->data, 'cookies' => $_COOKIE, // Passing cookies ensures request is performed as initiating user. 'sslverify' => apply_filters( 'https_local_ssl_verify', false ), // Local requests, fine to pass false. ); /** * Filters the post arguments used during an async request. * * @param array $args */ return apply_filters( $this->identifier . '_post_args', $args ); } /** * Maybe handle a dispatched request. * * Check for correct nonce and pass to handler. * * @return void|mixed */ public function maybe_handle() { // Don't lock up other requests while processing. session_write_close(); check_ajax_referer( $this->identifier, 'nonce' ); $this->handle(); return $this->maybe_wp_die(); } /** * Should the process exit with wp_die? * * @param mixed $return What to return if filter says don't die, default is null. * * @return void|mixed */ protected function maybe_wp_die( $return = null ) { /** * Should wp_die be used? * * @return bool */ if ( apply_filters( $this->identifier . '_wp_die', true ) ) { wp_die(); } return $return; } /** * Handle a dispatched request. * * Override this method to perform any actions required * during the async request. */ abstract protected function handle(); } endif; free/classes/settings/class-secupress-settings-global.php 0000644 00000010113 15174670627 0017721 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Global settings class. * * @package SecuPress * @subpackage SecuPress_Settings * @since 1.0 */ class SecuPress_Settings_Global extends SecuPress_Settings { const VERSION = '1.1'; /** * The reference to *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** Setters ================================================================================= */ /** * Set the current module. * * @since 1.0 * @author Grégory Viguier * * @return (object) The class instance. */ protected function set_current_module() { $this->modulenow = 'global'; return $this; } /** Init ==================================================================================== */ /** * Init: this method is required by the class `SecuPress_Singleton`. * * @since 1.1.4 * @author Grégory Viguier */ protected function _init() { $this->set_current_module(); $this->form_action = esc_url( admin_url( 'admin-post.php' ) ); } /** Main template tags ====================================================================== */ /** * Print the page content. * * @since 1.0 * @author Grégory Viguier */ public function print_page() { $setting_modules = array( 'settings-manager' ); if ( ! secupress_is_white_label() && ( ! defined( 'SECUPRESS_HIDE_API_KEY' ) || ! SECUPRESS_HIDE_API_KEY ) ) { $setting_modules = array( 'api-key', 'settings-manager' ); } /** * Filter the modules of the global settings. * * @since 1.0 * * @param (array) $setting_modules The modules. */ $setting_modules = apply_filters( 'secupress.global_settings.modules', $setting_modules ); $secupress_has_sideads = apply_filters( 'secupress.no_sidebar', true ) && apply_filters( 'secupress.no_sideads', true ); ?> <div class="wrap"> <div class="secupress-setting-wrapper<?php echo ( $secupress_has_sideads ? ' secupress-has-sideads' : '' ) ?>"> <div class="secupress-setting-content"> <?php secupress_admin_heading( __( 'Settings' ) ); settings_errors(); secupress_settings_heading( array( 'title' => esc_html__( 'Settings' ), 'subtitle' => esc_html__( 'Overall plugin settings and fine-tuning', 'secupress' ), ) ); ?> <div class="secupress-section-light secupress-bordered"> <?php array_map( array( $this, 'load_module_settings' ), $setting_modules ); ?> </div> </div> <?php $this->print_sideads(); ?> </div><!-- .secupress-setting-content --> </div><!-- .wrap --> <?php } /** * Print the opening form tag. * * @since 1.1.4 * @author Grégory Viguier * * @param (string) $module A setting module name. */ final public function print_open_form_tag( $module ) { ?> <form id="secupress-module-form-global-<?php echo $module; ?>" method="post" action="<?php echo $this->get_form_action(); ?>" enctype="multipart/form-data"> <?php } /** * Print the closing form tag and the hidden settings fields. * * @since 1.1.4 * @author Grégory Viguier * * @param (string) $module A setting module name. */ final public function print_close_form_tag( $module ) { $module = 'secupress_update_global_settings_' . $module; echo '<input type="hidden" name="action" value="' . $module . '" />'; echo '<input type="hidden" id="' . $module . '-nonce" name="_wpnonce" value="' . wp_create_nonce( $module ) . '" />'; wp_referer_field(); echo '</form>'; } /** Includes ================================================================================ */ /** * Include a module settings file. Also, automatically set the current module and print the sections. * * @since 1.0 * @author Grégory Viguier * * @param (string) $module The module. * * @return (object) The class instance. */ protected function load_module_settings( $module ) { $module_file = SECUPRESS_ADMIN_SETTINGS_MODULES . $module . '.php'; $this->print_open_form_tag( $module ); $this->require_settings_file( $module_file, $module ); $this->print_close_form_tag( $module ); return $this; } } free/classes/settings/class-secupress-settings-modules.php 0000644 00000102103 15174670627 0020132 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Modules settings class. * * @package SecuPress * @subpackage SecuPress_Settings * @since 1.0 */ class SecuPress_Settings_Modules extends SecuPress_Settings { const VERSION = '1.0'; /** * All the modules, with (mainly) title, icon, description. * * @var (array) */ protected static $modules; /** * The reference to *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** Setters ================================================================================= */ /** * Set the modules infos. * * @since 1.0 */ final protected static function set_modules() { static::$modules = secupress_get_modules(); } /** * Set the current module. * * @since 1.0 * * @return (object) The class instance. */ final protected function set_current_module() { $this->modulenow = isset( $_GET['module'] ) ? $_GET['module'] : 'welcome'; $this->modulenow = array_key_exists( $this->modulenow, static::get_modules() ) && file_exists( SECUPRESS_MODULES_PATH . $this->modulenow . '/settings.php' ) ? $this->modulenow : 'welcome'; return $this; } /** Getters ================================================================================= */ /** * Set the modules infos. * * @since 1.0 * * @return (array) The modules. */ final public static function get_modules() { if ( empty( static::$modules ) ) { static::set_modules(); } return static::$modules; } /** * Get a module title. * * @since 1.0 * * @param (string) $module The desired module. * * @return (string) */ final public function get_module_title( $module = false ) { $modules = static::get_modules(); $module = $module ? $module : $this->modulenow; if ( ! empty( $modules[ $module ]['title-alt'] ) ) { return $modules[ $module ]['title-alt']; } if ( ! empty( $modules[ $module ]['title'] ) ) { return $modules[ $module ]['title']; } return ''; } /** * Get a module descriptions. * * @since 1.0 * * @param (string) $module The desired module. * * @return (array) */ final public function get_module_descriptions( $module = false ) { $modules = static::get_modules(); $module = $module ? $module : $this->modulenow; if ( ! empty( $modules[ $module ]['description'] ) ) { return (array) $modules[ $module ]['description']; } return array(); } /** * Get a module summary. * * @since 1.0 * * @param (string) $module The desired module. * @param (string) $size The desired size: small|normal. * * @return (string) */ final public function get_module_summary( $module = false, $size = 'normal' ) { $modules = static::get_modules(); $module = $module ? $module : $this->modulenow; if ( ! empty( $modules[ $module ]['summaries'][ $size ] ) ) { return $modules[ $module ]['summaries'][ $size ]; } return ''; } /** * Get if a module is new * * @since 2.2.6 * * @param (string) $module The desired module. * * @return (bool) */ final public function is_new_module( $module = false ) { $modules = static::get_modules(); $module = $module ? $module : $this->modulenow; return isset( $modules[ $module ]['new'] ) && $this->modulenow !== $module; } /** * Get a module icon. * * @since 1.0 * @author Geoffrey * * @param (string) $module The desired module. * * @return (string) */ final public function get_module_icon( $module = false ) { $modules = static::get_modules(); $module = $module ? $module : $this->modulenow; if ( ! empty( $modules[ $module ]['icon'] ) ) { return $modules[ $module ]['icon']; } return ''; } /** * Tells if the reset box should be displayed for a specific module. * * @since 1.0 * * @param (string) $module The desired module. * * @return (bool) */ final public function display_module_reset_box( $module = false ) { $modules = static::get_modules(); $module = $module ? $module : $this->modulenow; return isset( $modules[ $module ]['with_reset_box'] ) ? (bool) $modules[ $module ]['with_reset_box'] : true; } /** Init ==================================================================================== */ /** * Init: this method is required by the class `SecuPress_Singleton`. * * @since 1.0 */ protected function _init() { parent::_init(); $modules = static::get_modules(); $this->with_form = ! ( isset( $modules[ $this->modulenow ]['with_form'] ) && false === $modules[ $this->modulenow ]['with_form'] ); if ( secupress_is_pro() ) { require_once( SECUPRESS_PRO_ADMIN_PATH . 'settings.php' ); } } /** Main template tags ====================================================================== */ /** * Print the page content. * * @since 1.0 */ public function print_page() { $secupress_has_sideads = apply_filters( 'secupress.no_sidebar', true ) && apply_filters( 'secupress.no_sideads', true ); ?> <div class="wrap"> <?php secupress_admin_heading( __( 'Modules', 'secupress' ) ); ?> <?php settings_errors(); ?> <div class="secupress-wrapper secupress-flex secupress-flex-top<?php echo ( $secupress_has_sideads ? ' secupress-has-sideads' : '' ) ?>"> <div class="secupress-modules-sidebar"> <div class="secupress-sidebar-header"> <div class="secupress-flex"> <div class="secupress-sh-logo"> <?php echo secupress_get_logo(); ?> </div> <div class="secupress-sh-name"> <p class="secupress-sh-title"> <?php echo secupress_get_logo_word( array( 'width' => 81, 'height' => 19 ) ); ?> </p> </div> </div> </div> <ul id="secupress-modules-navigation" class="secupress-modules-list-links"> <?php $this->print_tabs(); ?> </ul> </div> <div class="secupress-tab-content secupress-tab-content-<?php echo $this->get_current_module(); ?>" id="secupress-tab-content"> <?php $this->print_current_module(); ?> </div> <?php $this->print_sideads(); ?> </div> </div> <?php } /** * Print the tabs to switch between modules. * * @since 1.0 */ protected function print_tabs() { foreach ( static::get_modules() as $key => $module ) { $icon = isset( $module['icon'] ) ? $module['icon'] : 'secupress-simple'; $icon = secupress_is_white_label() ? '' : $icon; $class = $this->get_current_module() === $key ? 'active' : ''; $class .= ! empty( $module['mark_as_pro'] ) ? ' secupress-pro-module' : ''; $new = $this->is_new_module( $key ) ? '<span class="secupress-new-module">' . _x( 'New!', 'module', 'secupress' ) . '</span>' : ''; ?> <li> <?php echo $new; ?> <a href="<?php echo esc_url( secupress_admin_url( 'modules', $key ) ); ?>" class="<?php echo $class; ?> module-<?php echo sanitize_key( $key ); ?>"> <span class="secupress-tab-name"><?php echo $module['title']; ?></span> <span class="secupress-tab-summary"> <?php if ( apply_filters( 'secupress.settings.description', true ) ) { echo $module['summaries']['small']; } ?> </span> <i class="secupress-icon-<?php echo $icon; ?>" aria-hidden="true"></i> </a> </li> <?php } if ( ! secupress_is_pro() && ! secupress_is_white_label() ) { ?> <li> <a href="<?php echo esc_url( secupress_admin_url( 'get-pro' ) ); ?>" class="module-get-pro"> <span class="secupress-tab-name"><?php _e( 'Unlock all PRO features', 'secupress' ); ?></span> <span class="secupress-tab-summary"><?php _e( 'Buy SecuPress Pro now', 'secupress' ); ?></span> <i class="icon-secupress-simple" aria-hidden="true"></i> </a> </li> <?php } } /** * Print the opening form tag. * * @since 1.0 */ final public function print_open_form_tag() { ?> <form id="secupress-module-form-settings" method="post" action="<?php echo $this->get_form_action(); ?>" enctype="multipart/form-data"> <?php } /** * Print the closing form tag and the hidden settings fields. * * @since 1.0 */ final public function print_close_form_tag() { settings_fields( 'secupress_' . $this->get_current_module() . '_settings' ); echo '</form>'; } /** * Print the current module. * * @since 1.0 */ protected function print_current_module() { ?> <div class="secupress-tab-content-header secupress-flex secupress-flex-spaced secupress-vcenter"> <?php $this->print_module_title(); ?> <div class="secupress-module-search-wrapper hide-if-no-js"> <label for="secupress-module-search" class="screen-reader-text"><?php _e( 'Search', 'secupress' ); ?></label> <span class="dashicons dashicons-search secupress-search-icon" aria-hidden="true"></span> <input type="search" id="secupress-module-search" name="secupress-module-search" class="secupress-module-search" placeholder="<?php esc_attr_e( 'Search...', 'secupress' ); ?>" /> <span class="spinner secupress-inline-spinner" style="float: none; margin: 0;"></span> <ul id="secupress-module-search-results" class="secupress-module-search-results" style="display: none;"></ul> </div> </div> <?php if ( $this->get_with_form() ) { $this->print_open_form_tag(); } ?> <div class="secupress-module-options-block" id="block-advanced_options" data-module="<?php echo $this->get_current_module(); ?>"> <?php $this->load_module_settings(); $this->print_module_reset_box(); ?> </div> <?php if ( $this->get_with_form() ) { $this->print_close_form_tag(); } } /** * Print a box allowing to reset the current module settings. * * @since 1.0 */ protected function print_module_reset_box() { if ( ! $this->display_module_reset_box() ) { return; } $this->set_current_section( 'reset' ); $this->set_section_description( __( 'When you need to reset this module’s settings to the default.', 'secupress' ) ); $this->set_current_plugin( 'reset' ); $this->add_field( array( 'name' => 'reset', 'field_type' => 'field_button', 'style' => 'small', 'class' => 'secupress-button-secondary', 'url' => wp_nonce_url( admin_url( 'admin-post.php?action=secupress_reset_settings&module=' . $this->get_current_module() ), 'secupress_reset_' . $this->get_current_module() ), 'label' => sprintf( __( 'Reset the %s’s settings', 'secupress' ), $this->get_module_title() ), ) ); $this->do_sections(); } /** * Print the module title. * * @since 1.0 * * @param (string) $tag The title tag to use. * * @return (object) The class instance. */ protected function print_module_title( $tag = 'h2' ) { echo "<$tag class=\"secupress-tc-title\">"; $this->print_module_icon(); echo $this->get_module_title(); echo "</$tag>\n"; return $this; } /** * Print the module descriptions. * * @since 1.0 * * @return (object) The class instance. */ protected function print_module_description() { if ( $this->get_module_descriptions() ) { echo '<p>' . implode( "</p>\n<p>", $this->get_module_descriptions() ) . "</p>\n"; } return $this; } /** * Print the module icon. * * @since 1.0 * @author Geoffrey * * @return (object) The class instance. */ protected function print_module_icon() { if ( $this->get_module_icon() ) { echo '<i class="secupress-icon-' . $this->get_module_icon() . '" aria-hidden="true"></i>' . "\n"; } return $this; } /** Specific fields ========================================================================= */ /** * Non login time slot field. * The field is hidden in the free version. * * @since 1.0 * * @param (array) $args An array of parameters. See `::field()`. */ protected function countries( $args ) {} /** * Non login time slot field. * The field is hidden in the free version. * * @since 1.0 * * @param (array) $args An array of parameters. See `::field()`. */ protected function non_login_time_slot( $args ) {} /** * Displays the scheduled backups. * * @since 1.0 */ protected function scheduled_backups() { echo '<a href="' . esc_url( secupress_admin_url( 'get-pro' ) ) . '" class="secupress-button secupress-ghost secupress-button-tertiary">' . __( 'Learn more about SecuPress Pro', 'secupress' ) . '</a>'; _e( 'This feature is available in SecuPress Pro', 'secupress' ); } /** * Displays the scheduled scan. * * @since 1.0 */ protected function scheduled_scan() { echo '<a href="' . esc_url( secupress_admin_url( 'get-pro' ) ) . '" class="secupress-button secupress-ghost secupress-button-tertiary">' . __( 'Learn more about SecuPress Pro', 'secupress' ) . '</a>'; _e( 'This feature is available in SecuPress Pro', 'secupress' ); } /** * Displays the scheduled file monitoring. * * @since 1.0 */ protected function scheduled_monitoring() { echo '<a href="' . esc_url( secupress_admin_url( 'get-pro' ) ) . '" class="secupress-button secupress-ghost secupress-button-tertiary">' . __( 'Learn more about SecuPress Pro', 'secupress' ) . '</a>'; _e( 'This feature is available in SecuPress Pro', 'secupress' ); } /** * Displays the banned IPs and add actions to delete them or add new ones. * * @since 1.0 */ protected function blacklist_ips() { $ban_ips = get_site_option( SECUPRESS_BAN_IP ); $ban_ips = is_array( $ban_ips ) ? $ban_ips : []; $offset = get_option( 'gmt_offset' ) * HOUR_IN_SECONDS; $page_url = secupress_admin_url( 'modules', 'logs' ); $referer_arg = '&_wp_http_referer=' . urlencode( esc_url_raw( $page_url ) ); $is_search = false; $search_val = ''; $empty_list_message = __( 'Empty disallowed IP list', 'secupress' ); // Ban form. echo '<form id="form-ban-ip" class="hide-if-js" action="' . esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=secupress-ban-ip' . $referer_arg ), 'secupress-ban-ip' ) ) . '" method="post">'; echo '<label for="secupress-ban-ip" class="screen-reader-text">' . __( 'Specify an IP to ban.', 'secupress' ) . '</label><br/>'; echo '<p class="description">' . sprintf( __( 'You can use %sIP ranges%s.', 'secupress' ), __( '<a href="https://docs.secupress.me/article/161-ip-range">', 'secupress' ), '</a>' ) . '</p>'; echo '<textarea cols="50" id="secupress-ban-ip" name="ip"></textarea> '; echo '<button type="submit" class="secupress-button secupress-button-mini">' . _x( 'Ban IP', 'verb', 'secupress' ) . '</button>'; echo "</form>\n"; // Search. if ( $ban_ips && ! empty( $_POST['secupress-search-banned-ip'] ) ) { // WPCS: CSRF ok. $search_val = urldecode( trim( $_POST['secupress-search-banned-ip'] ) ); // WPCS: CSRF ok. $is_search = true; $search_val = preg_quote( $search_val, '~' ); $found_ips = preg_grep('~' . $search_val . '~', array_keys( $ban_ips ) ); $found_ips = array_flip( $found_ips ); $ban_ips = array_intersect_key( $ban_ips, $found_ips ); if ( empty( $ban_ips ) ) { $empty_list_message = __( 'IP not found.', 'secupress' ); } } // Search form. echo '<form action="' . esc_url_raw( secupress_get_current_url('raw') ) . '" id="form-search-ip"' . ( $ban_ips || $is_search ? '' : ' class="hidden"' ) . ' method="post">'; echo '<label for="secupress-search-banned-ip" class="screen-reader-text">' . __( 'Search IP', 'secupress' ) . '</label><br/>'; echo '<input type="search" id="secupress-search-banned-ip" name="secupress-search-banned-ip" value="' . esc_attr( wp_unslash( $search_val ) ) . '"/> '; echo '<button type="submit" class="secupress-button secupress-button-primary" data-loading-i18n="' . esc_attr__( 'Searching…', 'secupress' ) . '" data-original-i18n="' . esc_attr__( 'Search IP', 'secupress' ) . '">' . __( 'Search IP', 'secupress' ) . '</button> '; echo '<span class="spinner secupress-inline-spinner hide-if-no-js"></span>'; echo '<a class="secupress-button secupress-button-secondary' . ( $search_val ? '' : ' hidden' ) . '" href="' . esc_url( $page_url ) . '" ">' . __( 'Cancel search', 'secupress' ) . '</a> '; echo "</form>\n"; // Slice the list a bit: limit last results. /** * How many IP max to display * * @param (int) $limit 50 by default */ $limit = apply_filters( 'secupress.ip_list.limit_max', 50 ); $count_ips = count( $ban_ips ); if ( $count_ips > $limit ) { $ban_ips = array_slice( $ban_ips, - $limit ); echo '<p>' . sprintf( __( 'Last %1$s/%2$s disallowed IPs:', 'secupress' ), number_format_i18n( $limit ), $count_ips ) . '</p>' . "\n"; } // Display the list. echo '<ul id="secupress-banned-ips-list" class="secupress-boxed-group">'; if ( $ban_ips ) { foreach ( $ban_ips as $ip => $time ) { echo '<li class="secupress-large-row" data-ip="' . esc_attr( $ip ) . '">'; $format = __( 'M jS Y', 'secupress' ) . ' ' . __( 'G:i', 'secupress' ); $time = date_i18n( $format, $time + $offset ); $href = wp_nonce_url( admin_url( 'admin-post.php?action=secupress-unban-ip&ip=' . esc_attr( $ip ) . $referer_arg ), 'secupress-unban-ip_' . $ip ); printf( __( '<strong>%s</strong> <em>(Banned until %s)</em>', 'secupress' ), esc_html( $ip ), $time ); printf( '<span><a class="a-unban-ip" href="%s">%s</a> <span class="spinner secupress-inline-spinner hide-if-no-js"></span></span>', esc_url( $href ), _x( 'Remove', 'verb', 'secupress' ) ); echo "</li>\n"; } if ( $count_ips > $limit ) { echo '<li>' . __( 'Do a search to find more.', 'secupress' ) . '</li>'; } unset( $count_ips ); } else { echo '<li id="no-ips">' . $empty_list_message . '</li>'; } echo "</ul>\n"; // Actions. echo '<p id="secupress-banned-ips-actions">'; // Display a button to unban all IPs. $clear_href = wp_nonce_url( admin_url( 'admin-post.php?action=secupress-clear-ips' . $referer_arg ), 'secupress-clear-ips' ); echo '<a class="secupress-button secupress-button-secondary' . ( $ban_ips || $is_search ? '' : ' hidden' ) . '" id="secupress-clear-ips-button" href="' . esc_url( $clear_href ) . '" data-loading-i18n="' . esc_attr__( 'Clearing…', 'secupress' ) . '" data-original-i18n="' . esc_attr__( 'Clear all IPs', 'secupress' ) . '">' . __( 'Clear all IPs', 'secupress' ) . "</a>\n"; echo '<span class="spinner secupress-inline-spinner' . ( $ban_ips || $is_search ? ' hide-if-no-js' : ' hidden' ) . '"></span>'; // For JS: ban a IP. echo '<button type="button" class="secupress-button secupress-button-primary hide-if-no-js" id="secupress-ban-ip-button" data-loading-i18n="' . esc_attr__( 'Ban in progress…', 'secupress' ) . '" data-original-i18n="' . esc_attr_x( 'Disallow', 'verb', 'secupress' ) . '">' . _x( 'Disallow', 'verb', 'secupress' ) . "</button>\n"; echo '<span class="spinner secupress-inline-spinner hide-if-no-js"></span>'; echo "</p>\n"; } /** * Displays the textarea that lists the IP addresses not to ban. * * @since 1.0 * @author Grégory Viguier * * @param (array) $args An array of parameters. See `::field()`. */ protected function whitelist_ips( $args ) { $ban_ips = get_site_option( SECUPRESS_WHITE_IP ); $ban_ips = is_array( $ban_ips ) ? $ban_ips : []; $offset = get_option( 'gmt_offset' ) * HOUR_IN_SECONDS; $page_url = secupress_admin_url( 'modules', 'logs' ); $referer_arg = '&_wp_http_referer=' . urlencode( esc_url_raw( $page_url ) ); $is_search = false; $search_val = ''; $empty_list_message = __( 'Empty allowed IP list', 'secupress' ); // Ban form. echo '<form id="form-whitelist-ip" class="hide-if-js" action="' . esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=secupress-whitelist-ip' . $referer_arg ), 'secupress-whitelist-ip' ) ) . '" method="post">'; echo '<label for="secupress-whitelist-ip" class="screen-reader-text">' . __( 'Specify an IP to add to the allowed list.', 'secupress' ) . '</label><br/>'; echo '<p class="description">' . __( 'You can use <a href="https://docs.secupress.me/article/161-ip-range">IP ranges</a>.', 'secupress' ) . '</p>'; echo '<textarea cols="50" id="secupress-whitelist-ip" name="ip"></textarea> '; echo '<button type="submit" class="secupress-button secupress-button-mini">' . _x( 'Allow IP', 'verb', 'secupress' ) . '</button>'; echo "</form>\n"; // Search. if ( $ban_ips && ! empty( $_POST['secupress-search-whitelist-ip'] ) ) { // WPCS: CSRF ok. $search_val = urldecode( trim( $_POST['secupress-search-whitelist-ip'] ) ); // WPCS: CSRF ok. $is_search = true; $search_val = preg_quote( $search_val, '~' ); $found_ips = preg_grep('~' . $search_val . '~', array_keys( $ban_ips ) ); $found_ips = array_flip( $found_ips ); $ban_ips = array_intersect_key( $ban_ips, $found_ips ); if ( empty( $ban_ips ) ) { $empty_list_message = __( 'IP not found.', 'secupress' ); } } // Search form. echo '<form action="' . esc_url_raw( $page_url ) . '" id="form-search-whitelist-ip"' . ( $ban_ips || $is_search ? '' : ' class="hidden"' ) . ' method="post">'; echo '<label for="secupress-search-whitelist-ip" class="screen-reader-text">' . __( 'Search IP', 'secupress' ) . '</label><br/>'; echo '<input type="search" id="secupress-search-whitelist-ip" name="secupress-search-whitelist-ip" value="' . esc_attr( wp_unslash( $search_val ) ) . '"/> '; echo '<button type="submit" class="secupress-button secupress-button-primary" data-loading-i18n="' . esc_attr__( 'Searching…', 'secupress' ) . '" data-original-i18n="' . esc_attr__( 'Search IP', 'secupress' ) . '">' . __( 'Search IP', 'secupress' ) . '</button> '; echo '<span class="spinner secupress-inline-spinner hide-if-no-js"></span>'; echo '<a class="secupress-button secupress-button-secondary' . ( $search_val ? '' : ' hidden' ) . '" href="' . esc_url( $page_url ) . '" data-loading-i18n="' . esc_attr__( 'Reset in progress…', 'secupress' ) . '" data-original-i18n="' . esc_attr__( 'Reset', 'secupress' ) . '">' . __( 'Cancel search', 'secupress' ) . '</a> '; echo "</form>\n"; // Slice the list a bit: limit last results. /** * How many IP max to display * * @param (int) $limit 50 by default */ $limit = apply_filters( 'secupress.ip_list.limit_max', 50 ); $count_ips = count( $ban_ips ); if ( $count_ips > $limit ) { $ban_ips = array_slice( $ban_ips, - $limit ); echo '<p>' . sprintf( __( 'Last %1$s/%2$s allowed IPs:', 'secupress' ), number_format_i18n( $limit ), $count_ips ) . '</p>' . "\n"; } // Display the list. echo '<ul id="secupress-whitelist-ips-list" class="secupress-boxed-group">'; if ( $ban_ips ) { foreach ( $ban_ips as $ip => $time ) { echo '<li class="secupress-large-row" data-ip="' . esc_attr( $ip ) . '">'; $href = wp_nonce_url( admin_url( 'admin-post.php?action=secupress-unwhitelist-ip&ip=' . esc_attr( $ip ) . $referer_arg ), 'secupress-unwhitelist-ip_' . $ip ); printf( '<strong>%s</strong>', esc_html( $ip ) ); printf( '<span><a class="a-unwhitelist-ip" href="%s">%s</a> <span class="spinner secupress-inline-spinner hide-if-no-js"></span></span>', esc_url( $href ), __( 'Remove', 'secupress' ) ); echo "</li>\n"; } if ( $count_ips > $limit ) { echo '<li>' . __( 'Do a search to find more.', 'secupress' ) . '</li>'; } unset( $count_ips ); } else { echo '<li id="no-whitelist-ips">' . $empty_list_message . '</li>'; } echo "</ul>\n"; // Actions. echo '<p id="secupress-whitelist-ips-actions">'; // Display a button to unban all IPs. $clear_href = wp_nonce_url( admin_url( 'admin-post.php?action=secupress-clear-whitelist-ips' . $referer_arg ), 'secupress-clear-whitelist-ips' ); echo '<a class="secupress-button secupress-button-secondary' . ( $ban_ips || $is_search ? '' : ' hidden' ) . '" id="secupress-clear-whitelist-ips-button" href="' . esc_url( $clear_href ) . '" data-loading-i18n="' . esc_attr__( 'Clearing…', 'secupress' ) . '" data-original-i18n="' . esc_attr__( 'Clear all IPs', 'secupress' ) . '">' . __( 'Clear all IPs', 'secupress' ) . "</a>\n"; echo '<span class="spinner secupress-inline-spinner' . ( $ban_ips || $is_search ? ' hide-if-no-js' : ' hidden' ) . '"></span>'; // For JS: ban a IP. echo '<button type="button" class="secupress-button secupress-button-primary hide-if-no-js" id="secupress-whitelist-ip-button" data-loading-i18n="' . esc_attr__( 'Adding to allowed list…', 'secupress' ) . '" data-original-i18n="' . esc_attr_x( 'Allow', 'verb', 'secupress' ) . '">' . _x( 'Allow', 'verb', 'secupress' ) . "</button>\n"; echo '<span class="spinner secupress-inline-spinner hide-if-no-js"></span>'; echo "</p>\n"; } /** * Displays the restrictions for HTTP Log * * @since 2.1 */ protected function http_logs_restrictions() { $http_log_settings = get_option( SECUPRESS_HTTP_LOGS ); if ( ! $http_log_settings ) { return; } ?> <style> .secupress-http-settings th.column-hits { width: 45px; } .secupress-http-settings .since { color:#50575e; } .secupress-http-settings .square-box { display: flex; align-items: center; justify-content: center; box-sizing: content-box; } .secupress-http-settings .square-box-external { width: 32px; height: 32px; border-radius: 5px; } .secupress-http-settings .square-box-internal { width: 16px; height: 16px; border-radius: 50%; border: 1.5px solid #fff; } .secupress-http-settings .square-box-percent { width: 13px; height: 13px; border-radius: 50%; box-sizing: border-box; } </style> <h2>Stuff</h2> <p class="description">Stuff Stuff Stuff Stuff</p> <table class="secupress-http-settings wp-list-table widefat fixed striped posts"> <thead> <tr> <td class="manage-column column-cb check-column"> <label class="screen-reader-text" for="logs-select-all-1">Select All</label> <input id="logs-select-all-1" type="checkbox"> </td> <th scope="col" class="manage-column column-title column-primary">URL</th> <th scope="col" class="manage-column column-max-calls">Max Calls</th> <th scope="col" class="manage-column column-options">Options</th> <th scope="col" class="manage-column column-hits">Hits</th> </tr> </thead> <tbody id="the-list"> <?php $last_host = ''; $max_index = count( secupress_get_http_logs_limits() ) - 1; function secupress_get_square_box_background_color( $index ) { $max = count( secupress_get_http_logs_limits() ) - 1; $percent = 100 - ( $index * 100 / $max ); $from = [ 237, 95, 116 ]; // red RGB $to = [ 51, 194, 127 ]; // green RGB $diff = [ $to[0] - $from[0], $to[1] - $from[1], $to[2] - $from[2] ]; $color = []; for ( $i=0; $i < 3; $i++ ) { $color[] = round( $from[ $i ] + ( $percent * $diff[ $i ] / 100 ) ); } return implode( ',', $color ); } foreach( $http_log_settings as $url => $setting ) { $url_host = wp_parse_url( $url, PHP_URL_HOST ); $prefix = $url_host === $last_host ? '— ' : ''; $level = (int) ! empty( $prefix ); ?> <tr id="http-setting-<?php echo sanitize_html_class( $url ); ?>" class="level-<?php echo $level; ?>"> <th scope="row" class="check-column"> <label class="screen-reader-text" for="cb-select-1">Select <?php echo esc_html( $url ); ?></label> <input type="checkbox" name="http-setting[]" value="<?php echo esc_attr( $url ); ?>"> </th> <td class="column-title has-row-actions column-primary"> <strong> <?php $last_host = $url_host; $url = strlen( $url ) > 160 ? substr( $url, 0, 158 ) . '<abbr title="' . esc_attr( $url ) . '">…</abbr>' : esc_html( $url ); echo $prefix . $url; ?> </strong> <div class="row-actions"> <span class="since">Since: <abbr title="2019/08/22 9:00:46 am">22 April 2021</abbr></span> | <span class="delete"><a href="#" class="submitdelete" aria-label="Delete “<?php echo esc_attr( $url ); ?>”">Delete</a></span> </div> </td> <td class="column-max-calls"> <?php $percent = 100 - round( $setting['index'] * 100 / $max_index, 2 ); $color = secupress_get_square_box_background_color( $setting['index'] ); ?> <div class="square-box" title="<?php echo esc_attr( secupress_get_http_logs_limits()[ $setting['index'] ] ); ?>"> <div class="square-box square-box-external" style="background-color: rgb(<?php echo $color; ?>)"> <div class="square-box square-box-internal"> <div class="square-box square-box-percent" style="background: conic-gradient(#FFF <?php echo $percent; ?>%, #0000 0%);"></div> </div> </div> </div> </td> <td class="column-options"> <?php if ( isset( $setting['options']['ignore-param'] ) ) { echo '<p>Parameters: <code>'; echo implode( '</code>, <code>', $setting['options']['ignore-param'] ); echo '</code>.</p>'; } if ( isset( $setting['options']['block-method'] ) ) { echo '<p>Blocked Methods: <code>'; echo implode( '</code>, <code>', $setting['options']['block-method'] ); echo '</code>.</p>'; } if ( ! isset( $setting['options']['ignore-param'] ) && ! isset( $setting['options']['block-method'] ) ) { echo '–'; } $last = isset( $setting['last'] ) && $setting['last'] > 0 ? sprintf( __( 'Last hit: %s', 'secupress' ), date_i18n( get_option( 'date_format' ) . ' ' . get_option( 'time_format' ), $setting['last'] ) ) : ''; $hits = isset( $setting['hits'] ) ? number_format_i18n( (int) $setting['hits'] ) : '–'; $hit_fmt = ! empty( $last ) ? sprintf( '<span class="update-plugins"><span class="update-count"><abbr title="%1$s">%2$s</abbr></span></span>', esc_attr( $last ), esc_html( $hits ) ) : '–'; ?> </td> <td class="column-hits"> <?php echo $hit_fmt; ?> </td> </tr> <?php } ?> </tbody> <tfoot> <tr> <td class="manage-column column-cb check-column"> <label class="screen-reader-text" for="logs-select-all-2">Select All</label> <input id="logs-select-all-2" type="checkbox"> </td> <th scope="col" class="manage-column column-title column-primary">URL</th> <th scope="col" class="manage-column column-max-calls">Max Calls</th> <th scope="col" class="manage-column column-options">Options</th> <th scope="col" class="manage-column column-hits">Hits</th> </tr> </tfoot> </table> <?php } /** * Displays the old backups. * * @since 1.0 */ protected function backup_history() { ?> <p id="secupress-no-backups"><em><?php _e( 'No backups found yet.', 'secupress' ); ?></em></p> <?php } /** * Displays the tables to launch a backup * * @since 1.0 */ protected function backup_db() { ?> <p class="submit"> <button type="button" disabled="disabled" class="secupress-button"> <span class="icon"> <i class="secupress-icon-download"></i> </span> <span class="text"> <?php esc_html_e( 'Backup my database', 'secupress' ); ?> </span> </button> </p> <?php } /** * Displays the files backups and the button to launch one. * * @since 1.0 */ protected function backup_files() { ?> <p class="submit"> <button type="button" disabled="disabled" class="secupress-button"> <span class="icon"> <i class="secupress-icon-download"></i> </span> <span class="text"> <?php esc_html_e( 'Backup my files', 'secupress' ); ?> </span> </button> </p> <?php } /** * Scan the installation and search for modified/malicious files * * @since 1.0 */ protected function file_scanner() { secupress_print_scanner_ui(); } /** Includes ================================================================================ */ /** * Include the current module settings file. * * @since 1.0 * * @return (object) The class instance. */ final protected function load_module_settings() { $module_file = SECUPRESS_MODULES_PATH . $this->modulenow . '/settings.php'; if ( file_exists( $module_file ) ) { require_once( $module_file ); } return $this; } /** * Include a plugin settings file. Also, automatically set the current module and print the sections. * * @since 1.0 * * @param (string) $plugin The plugin. * * @return (object) The class instance. */ final protected function load_plugin_settings( $plugin ) { /** * Give the possibility to hide a full block of options * * @since 1.4 * * @param (bool) false by default */ if ( false !== apply_filters( 'secupress.settings.load_plugin.' . $plugin, false ) ) { return; } $plugin_file = SECUPRESS_MODULES_PATH . $this->modulenow . '/settings/' . $plugin . '.php'; return $this->require_settings_file( $plugin_file, $plugin ); } /** Other =================================================================================== */ /** * Filter the arguments passed to the section submit button and disable it. * * @since 1.0.6 * @author Grégory Viguier * * @param (array) $args An array of arguments passed to the `submit_button()` method. * * @return (array) */ public function disable_sumit_buttons( $args ) { $wrap = isset( $args['wrap'] ) ? $args['wrap'] : true; $atts = array(); $atts = isset( $args['other_attributes'] ) && is_array( $args['other_attributes'] ) ? $args['other_attributes'] : array(); $atts = array_merge( $atts, array( 'disabled' => 'disabled', 'aria-disabled' => 'true', ) ); return array_merge( $args, array( 'wrap' => $wrap, 'other_attributes' => $atts, ) ); } } free/classes/settings/class-secupress-settings.php 0000644 00000173726 15174670627 0016507 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Base class for settings. * * @package SecuPress * @since 1.0 */ abstract class SecuPress_Settings extends SecuPress_Singleton { const VERSION = '1.0.1'; /** * Current module: corresponds to the page tab, like `users_login`. * * @var (string) */ protected $modulenow; /** * Current section: corresponds to a block, like `login_auth`. * * @var (string) */ protected $sectionnow; /** * Current plugin (or sub-module): corresponds to a field, like `captcha`. * * @var (string) */ protected $pluginnow; /** * Section descriptions. * * @var (array) */ protected $section_descriptions = array(); /** * Tell if a section is disabled. * * @var (bool|null) */ protected $section_is_disabled = null; /** * Section Save buttons. * * @var (array) */ protected $section_save_buttons = array(); /** * Form action attribute (URL). * * @var (string) */ protected $form_action; /** * Tells if the current module should be wrapped in a form. * * @var (bool) */ protected $with_form = true; /** Setters ================================================================================= */ /** * Set the current module. * * @since 1.0 * * @return (object) The class instance. */ protected function set_current_module() { die( 'Method SecuPress_Settings::set_current_module() must be over-ridden in a sub-class.' ); return $this; } /** * Set the current section. * * @since 1.0 * * @param (string) $section The section to set. * * @return (object) The class instance. */ final protected function set_current_section( $section ) { $this->sectionnow = $section; return $this; } /** * Set the current plugin. * * @since 1.0 * * @param (string) $plugin The plugin to set. * * @return (object) The class instance. */ final protected function set_current_plugin( $plugin ) { $this->pluginnow = $plugin; return $this; } /** * Set the current section description. * * @since 1.0 * * @param (string) $description The description to set. * * @return (object) The class instance. */ final protected function set_section_description( $description ) { $section_id = $this->modulenow . '|' . $this->sectionnow; $this->section_descriptions[ $section_id ] = $description; return $this; } /** * Tell if the current section should display a Save button. * * @since 1.0 * * @param (bool) $value True to display the button. False to hide it. * * @return (object) The class instance. */ final protected function set_section_save_button( $value ) { $section_id = $this->get_section_id(); if ( $value ) { $this->section_save_buttons[ $section_id ] = 1; } else { unset( $this->section_save_buttons[ $section_id ] ); } return $this; } /** Getters ================================================================================= */ /** * Get the current module. * * @since 1.0 * * @return (string) The current module. */ final public function get_current_module() { return $this->modulenow; } /** * Get the current section. * * @since 1.0 * * @return (string) The current section. */ final public function get_current_section() { return $this->sectionnow; } /** * Get the current plugin. * * @since 1.0 * * @return (string) The current plugin. */ final public function get_current_plugin() { return $this->pluginnow; } /** * Get the current section ID. * * @since 1.0 * * @return (string) The current section ID. */ public function get_section_id() { return 'module_' . $this->modulenow . '|' . $this->sectionnow; } /** * Get the form action attribute (URL). * * @since 1.0 * * @return (string) The attribute. */ final public function get_form_action() { return $this->form_action; } /** * Tells if the current module should be wrapped in a form. * * @since 1.0 * * @return (bool) */ final public function get_with_form() { return $this->with_form; } /** Init ==================================================================================== */ /** * Init: this method is required by the class `SecuPress_Singleton`. * * @since 1.0 */ protected function _init() { $this->set_current_module(); $this->form_action = is_network_admin() ? admin_url( 'admin-post.php' ) : admin_url( 'options.php' ); $this->form_action = esc_url( $this->form_action ); } /** Sections ================================================================================ */ /** * Add a section in the page (a block). * * @since 1.0 * * @param (string) $title The section title. * @param (array) $args An array allowing 2 parameters: * - (bool) $with_roles Whenever to display a "Affected roles" radios list. * - (bool) $with_save_button Whenever to display a "Save Settings" button. * * @return (object) The class instance. */ protected function add_section( $title, $args = null ) { static $i = 0; $args = wp_parse_args( $args, array( 'with_roles' => false, 'with_save_button' => true ) ); $actions = ''; $section_id = $this->get_section_id(); if ( ! empty( $args['with_roles'] ) ) { $actions .= '<button type="button" id="affected-role-' . $i . '" class="hide-if-no-js no-button button-actions-title">' . __( 'Roles', 'secupress' ) . ' <span class="dashicons dashicons-arrow-right" aria-hidden="true"></span></button>'; } add_settings_section( $section_id, $title . $actions, array( $this, 'print_section_description' ), $section_id ); if ( (bool) $args['with_save_button'] ) { $this->section_save_buttons[ $section_id ] = 1; } if ( ! $args['with_roles'] ) { return $this; } $this->add_field( array( 'title' => '<span class="dashicons dashicons-groups"></span> ' . __( 'Affected Roles', 'secupress' ), 'description' => __( 'Which roles does this module affect?', 'secupress' ), 'depends' => 'affected-role-' . $i, 'row_class' => 'affected-role-row', 'name' => $this->get_field_name( 'affected_role' ), 'type' => 'roles', 'label_screen' => __( 'Affected Roles', 'secupress' ), 'helpers' => array( array( 'type' => 'description', 'description' => __( 'Future roles will be automatically checked.', 'secupress' ), ), array( 'type' => 'warning', 'class' => 'hide-if-js', 'description' => __( 'Select 1 role minimum', 'secupress' ), ), ), ) ); ++$i; return $this; } /** * A wrapper for `$this->do_settings_sections()` that wraps the sections in a `<div>` tag and prints the "Save" button. * * @since 1.0 * * @return (object) The class instance. */ protected function do_sections() { $section_id = $this->get_section_id(); $html_id = explode( '|', $section_id ); $html_id = sanitize_html_class( implode( '--', $html_id ) ); $with_save_button = ! empty( $this->section_save_buttons[ $section_id ] ); /** * Fires before a section. * * @since 1.0 * * @param (bool) $with_save_button True if a "Save All Changes" button will be printed. */ do_action( 'secupress.settings.before_section_' . $this->sectionnow, $with_save_button ); $this->section_is_disabled = true; echo '<div class="secupress-settings-section" id="secupress-settings-' . $html_id . '">'; echo '<div class="secublock">'; $this->do_settings_sections(); echo '</div><!-- .secublock -->'; if ( $with_save_button ) { $args = array( 'type' => 'primary', 'name' => $this->sectionnow . '_submit', ); if ( $this->section_is_disabled ) { $args['wrap'] = true; $args['other_attributes'] = array( 'disabled' => 'disabled', 'aria-disabled' => 'true', ); } /** * Filter the arguments passed to the section submit button. * * @since 1.4.3 Add $this->sectionnow. * @since 1.0.6 * * @param (array) $args An array of arguments passed to the `submit_button()` method. */ $args = apply_filters( 'secupress.settings.section.submit_button_args', $args, $this->sectionnow ); /** * Filter the arguments passed to the section submit button. * * @since 1.4.3 * * @param (array) $args An array of arguments passed to the `submit_button()` method. */ $args = apply_filters( 'secupress.settings.section-' . $this->sectionnow . '.submit_button_args', $args ); call_user_func( array( __CLASS__, 'submit_button' ), $args ); } echo '</div><!-- #secupress-settings-' . $html_id . ' -->'; /** * Fires after a section. * * @since 1.0 * * @param (bool) $with_save_button True if a "Save All Changes" button will be printed. */ do_action( 'secupress.settings.after_section_' . $this->sectionnow, $with_save_button ); $this->section_is_disabled = null; return $this; } /** * Like the real `do_settings_sections()` but using a custom `do_settings_fields()`. * * @since 1.0 */ final protected function do_settings_sections() { global $wp_settings_sections, $wp_settings_fields; $section_id = $this->get_section_id(); if ( ! isset( $wp_settings_sections[ $section_id ] ) ) { return; } foreach ( (array) $wp_settings_sections[ $section_id ] as $section ) { $header_open_tag = false; if ( $section['title'] ) { echo '<div class="secupress-settings-section-header">'; $header_open_tag = true; $id = explode( '|', $section['id'] ); $id = end( $id ); echo '<h3 class="secupress-settings-section-title" id="module-' . sanitize_html_class( $id ) . '">' . $section['title'] . '</h3>' . "\n"; } if ( $section['callback'] ) { if ( $section['title'] ) { echo ( $header_open_tag ? '' : '<div class="secupress-settings-section-header">' ); $header_open_tag = true; } call_user_func( $section['callback'], $section ); } echo ( $header_open_tag ? '</div><!-- .secupress-settings-section-header -->' : '' ); if ( ! isset( $wp_settings_fields ) || ! isset( $wp_settings_fields[ $section_id ] ) || ! isset( $wp_settings_fields[ $section_id ][ $section['id'] ] ) ) { continue; } echo '<div class="secupress-form-table">'; $this->do_settings_fields( $section_id, $section['id'] ); echo '</div>'; } } /** Generic fields ========================================================================== */ /** * The main callback that prints basic fields. * * @since 1.0 * * @param (array) $args An array with the following parameters: * - (string) $type The field type: 'number', 'email', 'tel', 'text', 'textarea', 'select', 'checkbox', 'checkboxes', 'radioboxes', 'radios', 'roles', 'countries'. * - (string) $name The name attribute. Also used as id attribute if `$label_for` is not provided. * - (string) $label_for The id attribute. Also used as name attribute if `$name` is not provided. * - (bool) $plugin_activation Set to true if the field is not used for a setting but to (de)activate a plugin. * - (mixed) $default The default value. * - (mixed) $value The field value. If not provided the field will look for an option stored in db. * - (array) $options Used for 'select', 'checkboxes', 'radioboxes' and 'radios': all possible choices for the user (value => label). * - (string) $fieldset Wrap the field in a `<fieldset>` tag. Possible values: 'start', 'end', 'no' and 'yes'. 'checkboxes', 'radioboxes' and 'radios' are automatically wrapped. 'start' and 'end' are not used yet. * - (string) $label_screen Used for the `<legend>` tag when a fieldset is used. * - (string) $label A label to display on top of the field. Also used as field label for the 'checkbox' type. * - (string) $label_before A label to display before the field. * - (string) $label_after A label to display after the field. * - (bool) $disabled True to disable the field. Pro fields are automatically disabled on the free version. * - (array) $attributes An array of html attributes to add to the field (like min and max for a 'number' type). * - (array) $helpers An array containing the helpers. See `self::helpers()`. */ protected function field( $args ) { static $first_field = true; $args = array_merge( array( 'type' => '', 'name' => '', 'label_for' => '', 'plugin_activation' => false, 'default' => null, 'value' => null, 'options' => array(), 'fieldset' => null, 'label_screen' => '', 'label' => '', 'label_before' => '', 'label_after' => '', 'disabled' => false, 'readonly' => false, 'required' => false, 'attributes' => array(), 'helpers' => array(), ), $args ); if ( $args['plugin_activation'] ) { $option_name = 'secupress-plugin-activation'; } else { $option_name = 'secupress' . ( 'global' !== $this->modulenow ? '_' . $this->modulenow : '' ) . '_settings'; } $name_attribute = $option_name . '[' . $args['name'] . ']'; $disabled = (bool) $args['disabled']; $readonly = (bool) $args['readonly']; $required = (bool) $args['required']; // Type. $args['type'] = 'radio' === $args['type'] ? 'radios' : $args['type']; // Value. if ( isset( $args['value'] ) ) { // if ( $args['plugin_activation'] ) { // // For the checkboxes that activate un sub-module, make sure they are not checked if they are disabled. // $value = $disabled ? null : $args['value']; // } else { $value = $args['value']; // } } elseif ( 'global' === $this->modulenow ) { $value = secupress_get_option( $args['name'] ); } else { $value = secupress_get_module_option( $args['name'] ); } if ( is_null( $args['default'] ) ) { $args['default'] = $args['plugin_activation'] ? 0 : ''; } if ( is_null( $value ) ) { $value = $args['default']; } // HTML attributes. $args['label_for'] = $args['label_for'] ? $args['label_for'] : $args['name']; $args['label_for'] = esc_attr( $args['label_for'] ); $attributes = ''; $args['attributes']['class'] = ! empty( $args['attributes']['class'] ) ? (array) $args['attributes']['class'] : array(); if ( 'radioboxes' === $args['type'] || 'checkboxes' === $args['type'] || 'checkbox' === $args['type'] || 'roles' === $args['type'] ) { $args['attributes']['class'][] = 'secupress-checkbox'; } if ( 'countries' === $args['type'] ) { $args['attributes']['class'][] = 'secupress-checkbox'; $args['attributes']['class'][] = 'secupress-checkbox-mini'; } if ( 'radios' === $args['type'] || 'roles_radio' === $args['type'] ) { $args['attributes']['class'][] = 'secupress-radio'; } if ( 'number' === $args['type'] && empty( $args['attributes']['class'] ) ) { $args['attributes']['class'][] = 'small-text'; } elseif ( 'radioboxes' === $args['type'] ) { $args['attributes']['class'][] = 'radiobox'; } if ( ! empty( $args['attributes']['pattern'] ) ) { $args['attributes']['data-pattern'] = $args['attributes']['pattern']; } if ( $required ) { $args['attributes']['required'] = 'true'; $args['attributes']['data-required'] = 'required'; $args['attributes']['data-aria-required'] = 'true'; } if ( $disabled ) { $args['attributes']['disabled'] = 'disabled'; } if ( $readonly ) { $args['attributes']['readonly'] = 'readonly'; } if ( $args['attributes']['class'] ) { $args['attributes']['class'] = implode( ' ', array_map( 'sanitize_html_class', $args['attributes']['class'] ) ); } else { unset( $args['attributes']['class'] ); } if ( ! empty( $args['attributes'] ) ) { foreach ( $args['attributes'] as $attribute => $attribute_value ) { $attributes .= ' ' . $attribute . '="' . esc_attr( $attribute_value ) . '"'; } } // Fieldset. $has_fieldset_begin = false; $has_fieldset_end = false; switch ( $args['fieldset'] ) { case 'start' : $has_fieldset_begin = true; break; case 'end' : $has_fieldset_end = true; break; case 'no' : break; default : $fieldset_auto = array( 'checkboxes' => 1, 'radioboxes' => 1, 'radios' => 1, 'roles' => 1, 'roles_radio' => 1 ); if ( 'yes' === $args['fieldset'] || isset( $fieldset_auto[ $args['type'] ] ) ) { $has_fieldset_begin = true; $has_fieldset_end = true; } } if ( $has_fieldset_begin ) { $toggle_all = isset( $args['toggle_all'] ) && $args['toggle_all'] ? 'secupress-check-group ' : ''; echo '<fieldset class="' . $toggle_all . 'fieldname-' . sanitize_html_class( $args['name'] ) . ' fieldtype-' . sanitize_html_class( $args['type'] ) . '">'; if ( ! empty( $args['label_screen'] ) ) { echo '<legend class="screen-reader-text"><span>' . $args['label_screen'] . '</span></legend>'; } } // Labels. $label_open = ''; $label_close = ''; if ( '' !== $args['label_before'] || '' !== $args['label'] || '' !== $args['label_after'] ) { $label_open = '<label class="secupress-' . esc_attr( $args['type'] ) . '-label' . ( $disabled ? ' disabled' : '' ) . '">'; $label_close = '</label>'; } // Types. switch ( $args['type'] ) { case 'url' : if ( secupress_is_pro() ) { add_action( 'admin_footer', 'secupress_pro_enqueue_wplink_dialog' ); } case 'number' : case 'email' : case 'tel' : case 'text' : case 'color' : echo $label_open; ?> <?php echo $args['label'] ? $args['label'] . '<br/>' : ''; echo $args['label_before']; echo '<input type="' . $args['type'] . '" id="' . $args['label_for'] . '" name="' . $name_attribute . '" value="' . esc_attr( $value ) . '"' . $attributes . '/>'; echo $args['label_after']; ?> <?php echo $label_close; break; case 'wpeditor' : case 'textarea' : $value = esc_textarea( html_entity_decode( implode( "\n", (array) explode( ',', $value ) ), ENT_QUOTES ) ); $attributes .= empty( $args['attributes']['cols'] ) ? ' cols="50"' : ''; $attributes .= empty( $args['attributes']['rows'] ) ? ' rows="5"' : ''; // Don't add expandable feature for these exceptions. $exceptions = array( 'notification-types_emails', 'move-login_custom_error_content', ); $is_exception = in_array( $args['name'], $exceptions, true ); $args['label_before'] .= $is_exception ? '' : '<div class="secupress-textarea-container">'; $args['label_after'] .= $is_exception ? '' : '</div>'; echo $label_open; ?> <?php echo $args['label'] ? '<span class="secupress-bold">' . $args['label'] . '</span><br/>' : ''; echo $args['label_before']; if ( 'textarea' === $args['type'] ) { echo '<textarea id="' . $args['label_for'] . '" name="' . $name_attribute . '"' . $attributes . ' spellcheck="false">' . $value . '</textarea>'; } else { wp_editor( html_entity_decode( $value ), sanitize_key( $name_attribute ), [ 'textarea_name' => $name_attribute ] ); } echo $args['label_after']; ?> <?php echo $label_close; break; case 'select' : $value = array_flip( (array) $value ); $has_disabled = false; echo $label_open; ?> <?php echo $args['label'] ? $args['label'] . '<br/>' : ''; echo $args['label_before']; ?> <select id="<?php echo $args['label_for']; ?>" name="<?php echo $name_attribute; ?>"<?php echo $attributes; ?>> <?php foreach ( $args['options'] as $val => $title ) { $disabled = ''; if ( static::is_pro_feature( $args['name'] . '|' . $val ) && ! secupress_is_pro() ) { $disabled = ' disabled="disabled"'; $has_disabled = true; } ?> <option value="<?php echo $val; ?>"<?php selected( isset( $value[ $val ] ) ); ?><?php echo $disabled; ?>><?php echo $title . ( $disabled ? ' (*)' : '' ); ?></option> <?php } ?> </select> <?php echo $args['label_after']; ?> <?php echo $label_close; echo $has_disabled ? static::get_pro_version_string( '<span class="description">(*) %s</span>' ) : ''; break; case 'checkbox' : echo '<p class="secupress-checkbox-line">'; echo $label_open; ?> <?php echo $args['label_before']; echo '<input type="checkbox" id="' . $args['label_for'] . '" name="' . $name_attribute . '" value="1"' . checked( $value, 1, false ) . $attributes . ' />'; echo '<span class="label-text">' . $args['label'] . '</span>'; ?> <?php echo $label_close; echo '</p>'; break; case 'checkboxes' : case 'radioboxes' : $value = array_flip( (array) $value ); $classes = ''; $key_as_val = isset( $args['key_as_val'] ) && $args['key_as_val']; // $toggle_all = isset( $args['toggle_all'] ) && $args['toggle_all']; //// $_all_css = $attributes; // if ( $toggle_all ) { // $args['options'] = array_merge( [ 0 => __( 'Toggle All', 'secupress' ) ], $args['options'] ); // $_all_css = str_replace( 'class="', 'class="secupress-toggle-check ', $attributes ); // } foreach ( $args['options'] as $val => $title ) { if ( ! is_array( $title ) ) { $title = (array) $title; } if ( $key_as_val && $val ) { echo '<h5>' . $val . '</h5>'; } foreach ( $title as $_title ) { $_value = $key_as_val ? sanitize_title( $_title ) : $val; $args['label_for'] = $args['name'] . '_' . $_value; $disabled = ( static::is_pro_feature( $args['name'] . '|' . $_value ) && ! secupress_is_pro() ) ? ' disabled="disabled"' : $disabled; $disabled = $disabled || ( isset( $args['disabled_values'] ) && in_array( $_value, $args['disabled_values'] ) ) ? ' disabled="disabled"' : $disabled; $classes = ''; $input_type = 'checkboxes' === $args['type'] ? 'checkbox' : 'radio'; if ( static::is_pro_feature( $args['name'] . '|' . $_value ) && ! secupress_is_pro() ) { $classes .= ' secupress-pro-option'; } elseif ( static::is_pro_feature( $args['name'] . '|' . $_value ) ) { $classes .= ' secupress-show-pro'; } if ( static::is_expert_feature( $args['name'] . '|' . $_value ) ) { $classes .= ' secupress-show-expert'; } // $toggle_all = false; // class="secupress-row-check ?> <p class="secupress-fieldset-item secupress-fieldset-item-radio secupress-fieldset-item-<?php echo $args['type']; ?> secupress-field-<?php echo esc_attr( $args['label_for'] ); ?><?php echo $classes; ?>"> <label <?php echo $disabled ? ' class="disabled"' : ''; ?> for="<?php echo esc_attr( $args['label_for'] ); ?>"> <input type="checkbox" id="<?php echo $args['label_for']; ?>" name="<?php echo $name_attribute; ?>[]" value="<?php echo $_value; ?>"<?php checked( isset( $value[ $_value ] ) ); ?><?php echo $disabled; ?><?php echo $_all_css; ?>> <?php echo '<span class="label-text">' . $_title . '</span>'; ?> </label> <?php echo static::is_pro_feature( $args['name'] . '|' . $_value ) && ! secupress_is_pro() ? static::get_pro_version_string( '<span class="description secupress-get-pro-version">%s</span>' ) : ''; ?> </p> <?php $_all_css = $attributes; } } break; case 'radios' : foreach ( $args['options'] as $val => $title ) { $args['label_for'] = $args['name'] . '_' . $val; $disabled = static::is_pro_feature( $args['name'] . '|' . $val ) && ! secupress_is_pro() ? ' disabled="disabled"' : ''; $classes = ''; if ( static::is_pro_feature( $args['name'] . '|' . $val ) && ! secupress_is_pro() ) { $classes = ' secupress-pro-option'; } elseif ( static::is_pro_feature( $args['name'] . '|' . $val ) ) { $classes = ' secupress-show-pro'; } if ( static::is_expert_feature( $args['name'] . '|' . $val ) ) { $classes .= ' secupress-show-expert'; } if ( ! $disabled && strpos( $title, 'secupress-coming-soon-feature' ) !== false ) { $disabled = ' disabled="disabled"'; } ?> <p class="secupress-radio-line secupress-fieldset-item secupress-fieldset-item-radio secupress-fieldset-item-<?php echo $args['type']; ?> secupress-field-<?php echo esc_attr( $args['label_for'] ); ?><?php echo $classes; ?>"> <?php if ( isset( $args['not'] ) && is_array( $args['not'] ) && isset( $args['not'][ $val ] ) ) { ?> <label class="disabled" for="<?php echo esc_attr( $args['label_for'] ); ?>"> <input type="radio" id="<?php echo $args['label_for']; ?>" value="" disabled="disabled"<?php echo $attributes; ?>> <?php echo '<span class="label-text">' . $title . '</span>'; ?> </label> <?php } else { ?> <label<?php echo $disabled ? ' class="disabled"' : ''; ?> for="<?php echo esc_attr( $args['label_for'] ); ?>"> <input type="radio" id="<?php echo $args['label_for']; ?>" name="<?php echo $name_attribute; ?>" value="<?php echo $val; ?>"<?php checked( $value, $val ); ?><?php echo $disabled; ?><?php echo $attributes; ?>> <?php echo '<span class="label-text">' . $title . '</span>'; ?> </label> <?php } ?> <?php echo static::is_pro_feature( $args['name'] . '|' . $val ) && ! secupress_is_pro() ? static::get_pro_version_string( '<span class="description secupress-get-pro-version">%s</span>' ) : ''; ?> </p> <?php } break; case 'roles_radio' : $roles = new WP_Roles(); $roles = $roles->get_names(); $roles = array_map( 'secupress_translate_user_role', $roles ); if ( isset( $args['options'] ) ) { $roles = array_merge( $args['options'], $roles ); } foreach ( $roles as $val => $title ) { $args['label_for'] = $args['name'] . '_' . $val; $classes = ''; if ( static::is_pro_feature( $args['name'] . '|' . $val ) && ! secupress_is_pro() ) { $classes = ' secupress-pro-option'; } elseif ( static::is_pro_feature( $args['name'] . '|' . $val ) ) { $classes = ' secupress-show-pro'; } if ( static::is_expert_feature( $args['name'] . '|' . $val ) ) { $classes .= ' secupress-show-expert'; } ?> <p class="secupress-radio-line<?php echo $classes; ?>"> <?php if ( isset( $args['not'] ) && is_array( $args['not'] ) && isset( $args['not'][ $val ] ) ) { ?> <label class="disabled" for="<?php echo esc_attr( $args['label_for'] ); ?>"> <input type="radio" id="<?php echo $args['label_for']; ?>" value="" disabled="disabled"<?php echo $attributes; ?>> <?php } else { ?> <label<?php echo $disabled ? ' class="disabled"' : ''; ?> for="<?php echo esc_attr( $args['label_for'] ); ?>"> <input type="radio" id="<?php echo $args['label_for']; ?>" name="<?php echo $name_attribute; ?>" value="<?php echo $val; ?>"<?php checked( $value, $val ); ?><?php echo $disabled; ?><?php echo $attributes; ?>> <?php } ?> <?php echo '<span class="label-text">' . $title . '</span>'; ?> </label> <?php echo static::is_pro_feature( $args['name'] . '|' . $val ) && ! secupress_is_pro() ? static::get_pro_version_string( '<span class="description secupress-get-pro-version">%s</span>' ) : ''; ?> </p> <?php } break; case 'roles' : $value = array_flip( (array) $value ); $roles = new WP_Roles(); $roles = $roles->get_names(); $roles = array_map( 'secupress_translate_user_role', $roles ); foreach ( $roles as $val => $title ) { ?> <p class="secupress-checkbox-roles-line"> <label<?php echo $disabled ? ' class="disabled"' : ''; ?>> <input type="checkbox" name="<?php echo $name_attribute; ?>[]" value="<?php echo $val; ?>"<?php checked( ! isset( $value[ $val ] ) ); ?><?php echo $attributes; ?>> <?php echo '<span class="label-text">' . $title . '</span>'; ?> </label> </p> <?php } ?> <input type="hidden" name="<?php echo $name_attribute; ?>[witness]" value="1" /> <?php break; case 'hidden' : ?> <input type="hidden" name="<?php echo $name_attribute; ?>" value="<?php echo $value; ?>"> <?php break; case 'html' : echo $value; break; case 'submit' : echo '<button type="submit" class="secupress-button" id="' . esc_attr( $args['name'] ) . '">' . $args['label'] . '</button>'; break; case 'plugin' : $helpers_args_for_plugins = $args; unset( $helpers_args_for_plugins['helpers']['help'] ); static::helpers( $helpers_args_for_plugins ); if ( ! function_exists( 'plugins_api' ) ) { require( ABSPATH . '/wp-admin/includes/plugin-install.php' ); } // Set up our query fields. $fields = array( 'banners' => false, 'icons' => true, 'reviews' => false, 'rating' => true, 'num_ratings' => true, 'downloaded' => true, 'active_installs' => true, 'short_description' => true, 'sections' => false, 'downloadlink' => true, 'last_updated' => true, 'homepage' => true, ); // Set how long to cache results. $expiration = WEEK_IN_SECONDS; $plugin_info = false; /** * Do query using passed in params. */ // Look in the cache. $plugin_info = get_transient( "secupress_plugin_cards_{$args['name']}" ); // If it's not in the cache or it's expired, do it live // and store it in the cache for next time. if ( ! $plugin_info ) { $plugin_info = plugins_api( 'plugin_information', array( 'slug' => $args['name'], 'fields' => $fields, ) ); if ( is_object( $plugin_info ) && ! is_wp_error( $plugin_info ) ) { set_transient( "secupress_plugin_cards_{$args['name']}", $plugin_info, $expiration ); } } // Default $output. $output = ''; // Confirm the call to plugins_api worked. if ( is_object( $plugin_info ) && ! is_wp_error( $plugin_info ) ) { $output .= '<div class="plugin-cards single-plugin">'; $output .= secupress_render_plugin_card( $plugin_info ); $output .= '</div>'; } echo $output; break; default : if ( secupress_is_pro() && function_exists( 'secupress_pro_' . $args['type'] . '_field' ) ) { call_user_func( 'secupress_pro_' . $args['type'] . '_field', $args, $this ); } elseif ( method_exists( $this, $args['type'] ) ) { call_user_func( array( $this, $args['type'] ), $args ); } else { echo '<!--// Missing or incorrect type //-->'; // Do not translate. } } // Helpers. if ( 'plugin' !== $args['type'] ) { static::helpers( $args ); } else { $helpers_args_for_plugins = $args; unset( $helpers_args_for_plugins['helpers']['description'] ); static::helpers( $helpers_args_for_plugins ); } if ( $has_fieldset_end ) { echo '</fieldset>'; } } /** Specific fields ========================================================================= */ /** * Outputs the form used by the reset settings form * * @since 1.4.4 * @author Julio Potier */ protected function reset_settings_button() { ?> <a class="secupress-button button button-secondary" href="<?php echo wp_nonce_url( admin_url( 'admin-post.php?action=secupress_reset_all_settings' ), 'secupress_reset_all_settings' ); ?>"> <?php _e( 'Reset Settings', 'secupress' ); ?> </a> <?php } protected function tables_selection() { global $wpdb; $non_wp_tables = secupress_get_non_wp_tables(); $wp_tables = secupress_get_wp_tables(); $blog_ids = ! is_multisite() ? [ '1' ] : $wpdb->get_col( "SELECT blog_id FROM {$wpdb->blogs}" ); $form = '<div class="show-input">'; $form .= '<fieldset aria-labelledby="select-db-tables-to-rename" class="secupress-boxed-group">'; if ( $non_wp_tables ) { $form .= '<b>' . __( 'Other tables', 'secupress' ) . '</b><br/>'; foreach ( $non_wp_tables as $table ) { $table_attr = esc_attr( $table ); $form .= '<input type="checkbox" name="secupress_wordpress-core_settings[database_tables_selection][]" value="' . $table_attr . '" id="select-db-tables-to-rename-' . $table_attr . '" checked="checked"><label for="select-db-tables-to-rename-' . $table_attr . '">' . esc_html( $table ) . '</label><br/>'; } } $form .= '<b>' . __( 'WordPress tables (mandatory)', 'secupress' ) . '</b><br/>'; foreach ( $blog_ids as $blog_id ) { $blog_id = '1' === $blog_id ? '' : $blog_id . '_'; foreach ( $wp_tables as $table ) { $table = substr_replace( $table, $wpdb->prefix . $blog_id, 0, strlen( $wpdb->prefix ) ); $form .= '<input type="checkbox" checked="checked" disabled="disabled"><label>' . esc_html( $table ) . '</label><br/>'; } } $form .= '</fieldset>'; $form .= '</div>'; echo $form; } /** * Outputs the form used by the importers to accept the data to be imported. * * @since 1.0 * @author Julio Potier */ protected function import_upload_form() { /** This filter is documented in wp-admin/includes/template.php */ $bytes = apply_filters( 'import_upload_size_limit', wp_max_upload_size() ); $size = size_format( $bytes ); $upload_dir = wp_upload_dir(); $disabled = secupress_is_pro() ? '' : ' disabled="disabled"'; if ( ! empty( $upload_dir['error'] ) ) { ?> <div class="error"> <p><?php _e( 'Before you can upload your import file, you will need to fix the following error:', 'secupress' ); ?></p> <p><strong><?php echo $upload_dir['error']; ?></strong></p> </div><?php echo secupress_is_pro() ? '' : static::get_pro_version_string( '<p class="description secupress-get-pro-version">%s</p>' ); return; } $name = 'upload'; $type = 'help'; $description = __( 'Choose a file from your computer:', 'secupress' ) . ' (' . sprintf( __( 'Maximum size: %s', 'secupress' ), $size ) . ')'; /** This filter is documented in inc/classes/settings/class-secupress-settings.php */ $description = apply_filters( 'secupress.settings.help', $description, $name, $type ); ?> <p> <input type="file" id="upload" name="import" size="25"<?php echo $disabled; ?>/><br/> <label for="upload"><?php echo $description; ?></label> <input type="hidden" name="max_file_size" value="<?php echo $bytes; ?>" /> </p> <p class="submit"> <button type="submit"<?php echo $disabled; ?> class="secupress-button" id="import"> <span class="icon"> <i class="secupress-icon-upload" aria-hidden="true"></i> </span> <span class="text"> <?php _e( 'Upload Settings', 'secupress' ); ?> </span> </button> </p> <?php echo secupress_is_pro() ? '' : static::get_pro_version_string( '<p class="description secupress-get-pro-version">%s</p>' ); } /** * Outputs the export button. * * @since 1.0 * @author Julio Potier */ protected function export_form() { ?> <p class="submit"> <?php if ( secupress_is_pro() ) : ?> <a href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=secupress_export' ), 'secupress_export' ) ); ?>" id="export" class="secupress-button"> <span class="icon" aria-hidden="true"> <i class="secupress-icon-download"></i> </span> <span class="text"> <?php _e( 'Download settings', 'secupress' ); ?> </span> </a> <?php else : ?> <button type="button" class="secupress-button" disabled="disabled"> <span class="icon" aria-hidden="true"> <i class="secupress-icon-download"></i> </span> <span class="text"> <?php _e( 'Download settings', 'secupress' ); ?> </span> </button> <?php endif; ?> </p> <?php echo secupress_is_pro() ? '' : static::get_pro_version_string( '<p class="description secupress-get-pro-version">%s</p>' ); } /** * Used to display buttons. * * @since 1.0 * * @param (array) $args An array of parameters. See `::field()`. */ protected function field_button( $args ) { if ( ! empty( $args['label'] ) ) { $class = 'secupress-button secupressicon-' . sanitize_html_class( $args['name'] ); $class .= ! empty( $args['style'] ) ? ' secupress-button-' . $args['style'] : ''; $class .= ! empty( $args['class'] ) ? ' ' . $args['class'] : ''; $id = ! empty( $args['id'] ) ? ' id="' . $args['id'] . '"' : ''; $disabled = ! empty( $args['disabled'] ) ? ' disabled="disabled"' : ''; if ( ! empty( $args['url'] ) ) { echo '<a' . $id . ' class="' . $class . '" ' . $disabled . ' href="' . esc_url( $args['url'] ) . '">' . $args['label'] . '</a>'; } else { echo '<button' . $id . ' class="' . $class . '" ' . $disabled . ' type="button">' . $args['label'] . '</button>'; } } // Helpers. static::helpers( $args ); } /** * Helpers printed after a field. * * @since 1.0 * * @param (array) $args An array containing a 'helpers' key. * This 'helpers' key contains a list of arrays that contain: * - (string) $description The text to print. * - (string) $type The helper type: 'description', 'help', 'warning'. * - (string) $class A html class to add to the text. * - (string) $depends Like in `$this->do_settings_fields()`, used to show/hide the helper depending on a field value. */ protected static function helpers( $args ) { if ( empty( $args['helpers'] ) || ! is_array( $args['helpers'] ) ) { return; } foreach ( $args['helpers'] as $helper ) { if ( empty( $helper['description'] ) ) { continue; } $depends = ''; if ( ! empty( $helper['depends'] ) ) { $helper['depends'] = explode( ' ', $helper['depends'] ); $depends = ' depends-' . implode( ' depends-', $helper['depends'] ); } $class = ! empty( $helper['class'] ) ? ' ' . trim( $helper['class'] ) : ''; $name = $args['name']; $type = $helper['type']; $tag = preg_match( '@</?p[ >]@', $helper['description'] ) ? 'div' : 'p'; $force = ''; $desc = ''; $forced = false !== strpos( $type, 'force-' ); $type = str_replace( 'force-', '', $type ); switch ( $type ) { case 'description' : $desc = '<' . $tag . ' class="description desc' . $depends . $class . '">' . $helper['description'] . '</' . $tag . '>'; break; case 'help' : $desc = '<' . $tag . ' class="description help' . $depends . $class . '"><span class="dashicons dashicons-editor-help"></span> ' . $helper['description'] . '</' . $tag . '>'; break; case 'doc' : $desc = '<' . $tag . ' class="description help' . $depends . $class . '"><span class="dashicons dashicons-sos"></span> ' . $helper['description'] . '</' . $tag . '>'; break; case 'warning' : $desc = '<' . $tag . ' class="description warning' . $depends . $class . '">' . ( 'p' === $tag ? '' : '<p>' ) . '<strong>' . __( 'Warning: ', 'secupress' ) . '</strong> ' . $helper['description'] . '</' . $tag . '>'; // Don't forget to close the <p> tag. break; default : continue 2; } if ( $forced ) { $force = $desc; $desc = ''; } /** * Filter the helper description. * * @since 1.0 * * @param (string) $desc The description. * @param (string) $name The field name argument. * @param (string) $type The helper type. */ echo apply_filters( 'secupress.settings.help', $desc, $name, $type ); /** * Filter the forced helper description. * * @since 2.3.17 * * @param (string) $desc The description. * @param (string) $name The field name argument. * @param (string) $type The helper type. */ echo apply_filters( 'secupress.settings.help.forced', $force, $name, $type ); } } /** Fields related ========================================================================== */ /** * Get a correct name for setting fields based on the current module. * * @since 1.0 * * @param (string) $field A field name. * * @return (string) */ final protected function get_field_name( $field ) { return "{$this->pluginnow}_{$field}"; } /** * Add a field. It's a wrapper for `add_settings_field()`. * * @since 1.0 * * @param (array) $args An array of parameters: * - (string) $title The row title/label. * - (string) $description The row description. * - (string) $field_type The field type. * See `self::field()` for the other paramaters. * * @return (object) The class instance. */ protected function add_field( $args ) { $args = wp_parse_args( $args, array( 'title' => '', 'description' => '', 'name' => '', 'field_type' => 'field', ) ); if ( empty( $args['name'] ) && ! empty( $args['label_for'] ) ) { $args['name'] = $args['label_for']; } if ( false !== apply_filters( 'secupress.settings.field.' . $args['name'], false ) ) { return; } // Get the title. $title = $args['title']; unset( $args['title'] ); // Get the callback. if ( is_array( $args['field_type'] ) ) { $callback = $args['field_type']; } elseif ( method_exists( $this, $args['field_type'] ) ) { $callback = array( $this, $args['field_type'] ); } else { $callback = 'secupress_' . $args['field_type']; } add_settings_field( 'module_' . $this->modulenow . '|' . $this->pluginnow . '|' . $args['name'], $title, $callback, $this->get_section_id(), $this->get_section_id(), $args ); /** * Triggered after a field is added. * * @since 1.0 */ do_action( 'secupress.settings.after_field_' . $this->modulenow . '|' . $this->pluginnow ); return $this; } /** * Like the `do_settings_fields()` WordPress function but: * - `id` and `class` attributes can be added to the `tr` tag (the `class` attribute appeared in WP 4.3) with `row_id` and `row_class`. * - The `$depends` parameter can be used to show/hide the row depending on a field value. * - Automatically add some text to the row description if the field is pro and w're not using the pro version. * * @since 1.0 * * @param (string) $page Slug title of the admin page who's settings fields you want to show. * @param (string) $section Slug title of the settings section who's fields you want to show. */ final protected function do_settings_fields( $page, $section ) { global $wp_settings_fields; if ( ! isset( $wp_settings_fields[ $page ][ $section ] ) ) { return; } foreach ( (array) $wp_settings_fields[ $page ][ $section ] as $field ) { $id = ''; $field_id = isset( $field['id'] ) ? explode( '|', $field['id'] ) : array( '' ); $field_id = end( $field_id ); $is_free = ! secupress_is_pro(); $data_f = ''; $class = 'secupress-setting-row_' . sanitize_html_class( $field_id ) . ' secupress-setting-row '; $class .= static::is_pro_feature( $field['args']['name'] ) && $is_free ? 'secupress-pro-row ' : ''; $class .= static::is_pro_feature( $field['args']['name'] ) && ! $is_free ? 'secupress-show-pro ' : ''; $class .= static::is_expert_feature( $field['args']['name'] ) ? 'secupress-show-expert ' : ''; // Row ID. if ( ! empty( $field['args']['row_id'] ) ) { $id = ' id="' . esc_attr( $field['args']['row_id'] ) . '"'; } // Data field. if ( ! empty( $field['args']['move_item'] ) ) { $data_f = ' data-move_item="' . esc_attr( $field['args']['move_item'] ) . '"'; } // Row class. if ( ! empty( $field['args']['row_class'] ) ) { $class .= ' ' . $field['args']['row_class']; } if ( ! empty( $field['args']['depends'] ) ) { $field['args']['depends'] = explode( ' ', $field['args']['depends'] ); $class .= ' depends-' . implode( ' depends-', $field['args']['depends'] ); } if ( $class ) { $class = ' class="' . esc_attr( trim( $class ) ) . '"'; } unset( $field['args']['row_id'], $field['args']['row_class'] ); if ( 'hidden' !== $field['args']['type'] ) { ?> <div<?php echo $id . $class . $data_f; ?>> <div class="secupress-flex"> <div class="secupress-setting-content-col"> <?php } else { echo '<div class="secupress-setting-row-hidden">'; } // Row title. if ( $field['title'] ) { if ( ! empty( $field['args']['label_for'] ) ) { echo '<h4 id="row-' . sanitize_html_class( $field_id ) . '" class="screen-reader-text">' . $field['title'] . '</h4>'; echo '<label for="' . esc_attr( $field['args']['label_for'] ) . '" class="secupress-setting-row-title">' . $field['title'] . '</label>'; } else { echo '<h4 id="row-' . sanitize_html_class( $field_id ) . '" class="secupress-setting-row-title">' . $field['title'] . '</h4>'; } } /** * Filter used to print or not the descriptions during the "expert" mode * @param (bool) true to display, false to hide */ if ( $field['args']['description'] && apply_filters( 'secupress.settings.description', true ) ) { echo '<p class="description">' . $field['args']['description'] . '</p>'; } unset( $field['args']['description'] ); $field_is_disabled = $this->field_is_disabled( $field['args'] ); if ( empty( $field['args']['disabled'] ) && $field_is_disabled ) { $field['args']['disabled'] = true; } if ( $this->section_is_disabled && ! $field_is_disabled ) { $this->section_is_disabled = false; } call_user_func( $field['callback'], $field['args'] ); if ( 'hidden' !== $field['args']['type'] ) { ?> </div> <div class="secupress-get-pro-col"> <?php } if ( static::is_pro_feature( $field['args']['name'] ) && $is_free ) { echo '<p class="secupress-get-pro">' . static::get_pro_version_string() . '</p>'; } if ( 'hidden' !== $field['args']['type'] ) { ?> </div><!-- .secupress-get-pro-col --> </div><!-- .secupress-flex --> </div> <?php } else { echo '</div>'; } } } /** * Tell if a field is disabled. * * @since 1.2.1 * @author Grégory Viguier * * @param (array) $field_args Field arguments. * * @return (bool) */ protected function field_is_disabled( $field_args ) { static $fields = array(); if ( isset( $fields[ $field_args['name'] ] ) ) { return $fields[ $field_args['name'] ]; } $disabled = false; if ( ! empty( $field_args['disabled'] ) ) { // The field is disabled. $disabled = true; } elseif ( static::is_pro_feature( $field_args['name'] ) && ! secupress_is_pro() ) { // The field is Pro. $disabled = true; } elseif ( ! empty( $field_args['options'] ) && ! secupress_is_pro() ) { // All the options are Pro. $has_enabled = false; foreach ( (array) $field_args['options'] as $val => $title ) { $name = $field_args['name'] . '_' . $val; if ( ! static::is_pro_feature( $field_args['name'] . '|' . $val ) && ! secupress_is_pro() ) { $has_enabled = true; $fields[ $name ] = false; } else { $fields[ $name ] = true; } } $disabled = ! $has_enabled; } if ( ! $disabled && ! empty( $field_args['depends'] ) ) { // All dependencies are disabled. $has_enabled = false; foreach ( (array) $field_args['depends'] as $depend ) { if ( empty( $fields[ $depend ] ) ) { $has_enabled = true; break; } } $disabled = ! $has_enabled; } if ( $disabled && ! empty( $field_args['options'] ) ) { /* * When a field is disabled and has options (checklist or select), mark all the options as disabled. * It will be usefull for the previous dependencies test. */ foreach ( (array) $field_args['options'] as $val => $title ) { $name = $field_args['name'] . '_' . $val; if ( ! isset( $fields[ $name ] ) ) { $fields[ $name ] = true; } } } $fields[ $field_args['name'] ] = $disabled; return $disabled; } /** Main template tags ====================================================================== */ /** * Print the page content. Must be extended. * * @since 1.0 */ public function print_page() { die( 'Method SecuPress_Settings::print_page() must be over-ridden in a sub-class.' ); } /** Other template tags ===================================================================== */ /** * Print the current section description (because you wouldn't guess by the method's name, be thankful). * * @since 1.0 * * @return (object) The class instance. */ protected function print_section_description() { $key = $this->modulenow . '|' . $this->sectionnow; if ( ! empty( $this->section_descriptions[ $key ] ) && apply_filters( 'secupress.settings.description', true ) ) { echo '<div class="secupress-settings-section-description">'; echo $this->section_descriptions[ $key ]; echo '</div>'; } return $this; } /** * Get or print a submit button. * * @since 1.0 * * $type The type of button. Accepts 'primary', 'secondary', or 'delete'. Default 'primary large'. * $name The HTML name of the submit button. If no id attribute is given in $other_attributes below, `$name` will be used as the button's id. Default 'main_submit'. * $wrap True if the output button should be wrapped in a paragraph tag, false otherwise. Can be used as a string to add a class to the wrapper. Default true. * $other_attributes Other attributes that should be output with the button, mapping attributes to their values, such as `array( 'tabindex' => '1' )`. These attributes will be output as `attribute="value"`, such as `tabindex="1"`. Other attributes can also be provided as a string such as `tabindex="1"`, though the array format is typically cleaner. Default empty. * $echo True if the button should be "echo"ed, false otherwise. * $label The button label. * $before What to print before the button * @param (array) $args Optional. * * @return (string) Submit button HTML. */ protected static function submit_button( $args ) { $defaults = [ 'label' => __( 'Save All Changes', 'secupress' ), 'before' => '', 'type' => 'primary large', 'name' => 'main_submit', 'wrap' => true, 'other_attributes' => null, 'echo' => true ]; $args = wp_parse_args( $args, $defaults ); if ( true === $args['wrap'] ) { $wrap = '<p class="submit">'; } elseif ( $args['wrap'] ) { $wrap = '<p class="submit ' . sanitize_html_class( $args['wrap'] ) . '">'; } if ( ! is_array( $args['type'] ) ) { $type = explode( ' ', $args['type'] ); } else { $type = $args['type']; } $button_shorthand = array( 'primary' => 1, 'secondary' => 1, 'tertiary' => 1, 'small' => 1, 'large' => 1, 'delete' => 1 ); $classes = array( 'secupress-button' ); foreach ( $type as $t ) { $classes[] = isset( $button_shorthand[ $t ] ) ? 'secupress-button-' . $t : $t; } $class = implode( ' ', array_unique( $classes ) ); // Default the id attribute to $name unless an id was specifically provided in $other_attributes. $name = $args['name']; $id = $args['name']; if ( is_array( $args['other_attributes'] ) && isset( $args['other_attributes']['id'] ) ) { $id = $args['other_attributes']['id']; } $attributes = ''; if ( is_array( $args['other_attributes'] ) ) { foreach ( $args['other_attributes'] as $attribute => $value ) { $attributes .= ' ' . $attribute . '="' . esc_attr( $value ) . '"'; } } elseif ( ! empty( $args['other_attributes'] ) ) { // Attributes provided as a string. $attributes = $args['other_attributes']; } // Don't output empty name and id attributes. $name_attr = $name ? ' name="' . esc_attr( $name ) . '"' : ''; $id_attr = $id ? ' id="' . esc_attr( $id ) . '"' : ''; $label = esc_html( $args['label'] ); $button = '<button type="submit"' . $name_attr . $id_attr . ' class="' . esc_attr( $class ) . '"' . $attributes . '>' . $label . '</button>'; if ( $wrap ) { $button = $wrap . $button . '</p>'; } if ( $args['echo'] ) { echo $button; } return $button; } /** * Print the sidebar with Ads and cross-selling. * * @since 2.2.6 Ads are no longer loaded from sp.me * @author Julio Potier * @since 1.1.4 Give the possibility to hide the ads + ads are loaded from sp.me * @since 1.2 A method of this class. Was previously `secupress_print_sideads()`. * @author Geoffrey Crofte */ protected function print_sideads() { global $current_screen; if ( SECUPRESS_PLUGIN_SLUG . '_page_' . SECUPRESS_PLUGIN_SLUG . '_modules' !== $current_screen->base ) { return; } /** * Give the possibility to hide the whole sidebar, don't do that!! >< * * @since 1.4 * * @param (bool) false by default */ if ( false !== apply_filters( 'secupress.no_sidebar', false ) ) { return; } ?> <div class="secupress-sideads"> <?php /** * Give the possibility to hide the ads sidebar, ok you can do that. * * @since 1.4 * * @param (bool) false by default */ if ( false === apply_filters( 'secupress.no_sideads', false ) ) { // Filter secupress_no_sideads. $image = SECUPRESS_ADMIN_IMAGES_URL . 'logo-pro.png'; $code = secupress_has_pro() ? 'UPGRADEMENOW' : ( get_user_locale() === 'fr_FR' ? 'BIENVENUE48' : 'WELCOME55' ); $promo = secupress_has_pro() ? '10%' : ( get_user_locale() === 'fr_FR' ? '12€' : '14$' ); $current_date = date('Y-m-d'); // Black Friday time! $start_date = date('Y-11-21'); $end_date = date('Y-11-30'); if (false && $current_date >= $start_date && $current_date <= $end_date ) { $code = 'BF' . date( 'Y' ); // BF2025 $promo = '20%'; $image = SECUPRESS_ADMIN_IMAGES_URL . 'blackfriday.png'; } // Halloween time! $start_date = date('Y-10-21'); $end_date = date('Y-10-31'); if ( $current_date >= $start_date && $current_date <= $end_date ) { $code = 'HALLO' . date( 'y' ); // HALLO25 $promo = '20%'; $image = SECUPRESS_ADMIN_IMAGES_URL . 'cat-pumpkin-icon.png'; } $sideads = [ // No i18n here. 0 => [ 'when' => 'free', 'content' => sprintf( '<div class="secupress-section-dark secupress-pro-ad"> <i class="icon-secupress" aria-hidden="true"></i> <img src="%1$s" class="secupress-pro-icon" width="80" alt="SecuPress Pro"/> <p class="secupress-text-medium">%3$s off with code <code>%2$s</code></p> <p>Unlock all the features of <strong>SecuPress Pro</strong></p> <a href="https://secupress.me/pricing/?discount=%2$s" class="secupress-button secupress-button-tertiary secupress-button-getpro"> <span class="text">Get SecuPress Pro Now</span> </a> <p><a href="https://secupress.me/features">or Learn More About Pro Features</a></p> </div>', $image, $code, $promo ), 'content-fr_FR' => sprintf( '<div class="secupress-section-dark secupress-pro-ad"> <i class="icon-secupress" aria-hidden="true"></i> <img src="%1$s" class="secupress-pro-icon" width="80" alt="SecuPress Pro"/> <p class="secupress-text-medium">%3$s de remise avec <code>%2$s</code></p> <p>Débloquez toutes les fonctionnalités<br>de <Strong>SecuPress Pro</strong></p> <a href="https://secupress.me/fr/tarifs/?discount=%2$s" class="secupress-button secupress-button-tertiary secupress-button-getpro"> <span class="text">Obtenir SecuPress Pro</span> </a> <p><a href="https://secupress.me/fr/fonctionnalites/">ou découvrez les fonctionnalités pro</a></p> </div>', $image, $code, $promo ), ], 1 => [ 'when' => 'pro', 'content' => sprintf( '<div class="secupress-section-dark secupress-pro-ad"> <i class="icon-secupress" aria-hidden="true"></i> <img src="%1$s" class="secupress-pro-icon" width="80" alt="SecuPress Pro"/> <p class="secupress-text-medium">%3$s off with code <code>%2$s</code></p> <p>Upgrade your license<br><strong>SecuPress Pro</strong></p> <a href="https://secupress.me/account/" class="secupress-button secupress-button-tertiary secupress-button-getpro"> <span class="text">Upgrade Now</span> </a></div>', $image, $code, $promo ), 'content-fr_FR' => sprintf( '<div class="secupress-section-dark secupress-pro-ad"> <i class="icon-secupress" aria-hidden="true"></i> <img src="%1$s" class="secupress-pro-icon" width="80" alt="SecuPress Pro"/> <p class="secupress-text-medium">%3$s de remise avec <code>%2$s</code></p> <p>Mettez à niveau votre license<br><Strong>SecuPress Pro</strong></p> <a href="https://secupress.me/fr/mon-compte/" class="secupress-button secupress-button-tertiary secupress-button-getpro"> <span class="text">Mettez à niveau</span> </a></div>', $image, $code, $promo ), ], ]; foreach ( $sideads as $sidead ) { if ( ( 'free' === $sidead['when'] && ! secupress_has_pro() ) || ( 'pro' === $sidead['when'] && secupress_has_pro() ) ) { $content_locale = 'content-' . get_user_locale(); $content = isset( $sidead[ $content_locale ] ) ? $sidead[ $content_locale ] : $sidead['content']; echo wp_kses_post( $content ); } } /** * Triggered before the tool boxes. * * @since 1.4 */ do_action( 'secupress.ad_before' ); } ?> <div class="secupress-bordered secupress-mail-ad"> <div class="secupress-ad-header secupress-flex"> <span><i class="dashicons dashicons-plus-alt secupress-primary" aria-hidden="true"></i></span> <p><?php _e( 'Hacked Website?', 'secupress' ); ?></p> </div> <div class="secupress-ad-content-padded secupress-ad-content"> <p><?php _e( 'Well, this is not a good day for you, we will try to make you smile while we’re working on it!', 'secupress' ) ?></p> <p class="secupress-cta"> <a href="<?php echo esc_url( secupress_admin_url( 'get-pro' ) ); ?>#services" class="secupress-button" target="_blank"><?php _e( 'Ask an Expert', 'secupress' ); ?></a> </p> </div> </div> <div class="secupress-bordered secupress-mail-ad"> <div class="secupress-ad-header secupress-flex"> <span><i class="dashicons dashicons-businessman secupress-primary" aria-hidden="true"></i></span> <p><?php _e( 'Pro Config', 'secupress' ); ?></p> </div> <div class="secupress-ad-content-padded secupress-ad-content"> <p><?php _e( 'Need an Expert to Set-Up SecuPress for You?', 'secupress' ) ?></p> <p class="secupress-cta"> <a href="<?php echo esc_url( secupress_admin_url( 'get-pro' ) ); ?>#services" class="secupress-button" target="_blank"><?php _e( 'Ask an Expert', 'secupress' ); ?></a> </p> </div> </div> <p><hr></p> <div class="secupress-bordered secupress-mail-ad"> <div class="secupress-ad-header secupress-flex"> <span><i class="dashicons dashicons-editor-help secupress-primary" aria-hidden="true"></i></span> <p><?php _e( 'FAQ', 'secupress' ); ?></p> </div> <div class="secupress-ad-content-padded secupress-ad-content"> <p><?php _e( 'All the answers to your questions.', 'secupress' ) ?></p> <p class="secupress-cta"> <a href="<?php echo esc_url( SECUPRESS_WEB_MAIN . _x( 'faq', 'link to website (Only FR or EN!)', 'secupress' ) ); ?>" class="secupress-button" target="_blank"><?php _e( 'Read the FAQ', 'secupress' ); ?></a> </p> </div> </div> <div class="secupress-bordered secupress-mail-ad"> <div class="secupress-ad-header secupress-flex"> <span><i class="dashicons dashicons-book secupress-primary" aria-hidden="true"></i></span> <p><?php _e( 'Documentation', 'secupress' ); ?></p> </div> <div class="secupress-ad-content-padded secupress-ad-content"> <p><?php _e( 'We already have some solutions for you.', 'secupress' ) ?></p> <p class="secupress-cta"> <a href="<?php _e( 'https://docs.secupress.me/', 'secupress' ); ?>" class="secupress-button" target="_blank"><?php _e( 'Read the docs', 'secupress' ); ?></a> </p> </div> </div> <div class="secupress-bordered secupress-mail-ad"> <div class="secupress-ad-header secupress-flex"> <span><i class="dashicons dashicons-sos secupress-primary" aria-hidden="true"></i></span> <p><?php _e( 'Support', 'secupress' ); ?></p> </div> <div class="secupress-ad-content-padded secupress-ad-content"> <p><?php _e( 'Got an issue? Ask for support.', 'secupress' ) ?></p> <p class="secupress-cta"> <a href="<?php echo esc_url( SECUPRESS_WEB_MAIN . _x( 'support', 'link to website (Only FR or EN!)', 'secupress' ) ); ?>" class="secupress-button" target="_blank"><?php _e( 'Ask for support', 'secupress' ); ?></a> </p> </div> </div> </div> <?php } /** Utilities =============================================================================== */ /** * Tell if the option value is for the pro version and we're not using the pro version. * * @since 1.4 Do not check anymore if secupress_is_pro() * @since 1.3 The method is public. * @since 1.0 * * @param (string) $value The option value. * * @return (bool) True if the option value is for pro version but w're not using the pro version. */ public static function is_pro_feature( $value ) { return secupress_feature_is_pro( $value ); } /** * Tell if the option value is an expert module. * * @since 2.3.17 * * @param (string) $value * @return (bool) */ public static function is_expert_feature( $value ) { return secupress_feature_is_pro( $value ) && ! secupress_is_pro() ? false : secupress_feature_is_expert( $value ); } /** * Returns a i18n message to act like a CTA on pro version. * * @since 1.0 * @since 1.3 The method is public. * * @param (string) $format You can use it to embed the message in a HTML tag, usage of "%s" is mandatory. * * @return (string) */ public static function get_pro_version_string( $format = '' ) { $message = sprintf( __( 'Available in <a href="%s" target="_blank">Pro Version</a>', 'secupress' ), esc_url( SECUPRESS_WEB_MAIN . _x( 'pricing', 'link to website (Only FR or EN!)', 'secupress' ) ) ); if ( $format ) { $message = sprintf( $format, $message ); } return $message; } /** Includes ================================================================================ */ /** * Include a module settings file. Also, automatically set the current module and print the sections. * * @since 1.0 * * @param (string) $module_file Absolute path to the module settings file. * @param (string) $module The module. * * @return (object) The class instance. */ final protected function require_settings_file( $module_file, $module ) { if ( file_exists( $module_file ) ) { $this->set_current_plugin( $module ); require_once( $module_file ); $this->do_sections(); } return $this; } } free/classes/scanners/class-secupress-scan-shellshock.php 0000644 00000016341 15174670627 0017671 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Shellshock scan class. * Previously was part of `SecuPress_Scan_Common_Flaws`. * * @package SecuPress * @subpackage SecuPress_Scan * @since 1.1.4 * @see http://plugins.svn.wordpress.org/shellshock-check/trunk/shellshock-check.php * @see https://www.shellshock.fr/ */ class SecuPress_Scan_Shellshock extends SecuPress_Scan implements SecuPress_Scan_Interface { /** Constants. ============================================================================== */ /** * Class version. * * @var (string) */ const VERSION = '1.2'; /** * ShellShock String * * @var (string) */ const SSHOCK = '<strong>Shellshock</strong>'; /** Properties. ============================================================================= */ /** * The reference to the *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** Init and messages. ====================================================================== */ /** * Init. * * @since 1.1.4 */ protected function init() { $this->title = sprintf( __( 'Check if your server is vulnerable to %s.', 'secupress' ), self::SSHOCK ); $this->more = sprintf( __( '%s is a critic vulnerability allowing an attacker to remotely execute malicious code on a server.', 'secupress' ), self::SSHOCK ); $this->more_fix = sprintf( __( 'Activate the option %1$s in the %2$s module.', 'secupress' ), '<em>' . __( 'Block Bad User-Agents', 'secupress' ) . '</em>', '<a href="' . esc_url( secupress_admin_url( 'modules', 'firewall' ) ) . '#row-bbq-headers_user-agents-header">' . __( 'Firewall', 'secupress' ) . '</a>' ); } /** * Get messages. * * @since 1.1.4 * * @param (int) $message_id A message ID. * * @return (string|array) A message if a message ID is provided. An array containing all messages otherwise. */ public static function get_messages( $message_id = null ) { /** Translators: 1 is the name of a protection, 2 is the name of a module. */ $activate_protection_message = sprintf( __( 'But you can activate the %1$s protection from the module %2$s.', 'secupress' ), '<em>' . __( 'Block Bad User-Agents', 'secupress' ) . '</em>', '<a target="_blank" href="' . esc_url( secupress_admin_url( 'modules', 'firewall' ) ) . '#row-bbq-headers_user-agents-header">' . __( 'Firewall', 'secupress' ) . '</a>' ); $messages = array( // "good" 0 => sprintf( __( 'The server is not vulnerable to %s.', 'secupress' ), self::SSHOCK ), 1 => sprintf( __( 'The protection against %s has been activated. It won’t fix the vulnerability (only your host can) but it will prevent an attacker to exploit it remotely.', 'secupress' ), self::SSHOCK ), // "warning" 100 => sprintf( __( 'Unable to determine the status of the %1$s flaw (%2$s).', 'secupress' ), self::SSHOCK, '<em>CVE-2014-6271</em>' ) . ' ' . $activate_protection_message, 101 => sprintf( __( 'Unable to determine the status of the %1$s flaw (%2$s).', 'secupress' ), self::SSHOCK, '<em>CVE-2014-7169</em>' ) . ' ' . $activate_protection_message, 102 => sprintf( __( 'Unable to determine the status of the %s flaw.', 'secupress' ), self::SSHOCK ) . ' ' . $activate_protection_message, // "bad" 200 => sprintf( __( 'The server appears to be vulnerable to %1$s (%2$s).', 'secupress' ), self::SSHOCK, '<em>CVE-2014-6271</em>' ), 201 => sprintf( __( 'The server appears to be vulnerable to %1$s (%2$s).', 'secupress' ), self::SSHOCK, '<em>CVE-2014-7169</em>' ), 202 => sprintf( __( 'The server may be vulnerable to %s.', 'secupress' ), self::SSHOCK ), ); if ( isset( $message_id ) ) { return isset( $messages[ $message_id ] ) ? $messages[ $message_id ] : __( 'Unknown message', 'secupress' ); } return $messages; } /** Getters. ================================================================================ */ /** * Get the documentation URL. * * @since 1.2.3 * * @return (string) */ public static function get_docs_url() { return __( 'https://docs.secupress.me/article/115-shellshock-scan', 'secupress' ); } /** Scan. =================================================================================== */ /** * Scan for flaw(s). * * @since 1.1.4 * * @return (array) The scan results. */ public function scan() { $activated = $this->filter_scanner( __CLASS__ ); if ( true === $activated ) { $this->add_message( 0 ); return parent::scan(); } if ( 'WIN' === strtoupper( substr( PHP_OS, 0, 3 ) ) ) { // "good" $this->add_message( 0 ); return parent::scan(); } if ( ! secupress_is_function_disabled( 'proc_open' ) ) { // Scan with `proc_open()`. $env = array( 'SHELL_SHOCK_TEST' => '() { :;}; echo VULNERABLE' ); $desc = array( 0 => array( 'pipe', 'r' ), 1 => array( 'pipe', 'w' ), 2 => array( 'pipe', 'w' ), ); // CVE-2014-6271. $p = proc_open( 'bash -c "echo Test"', $desc, $pipes, null, $env ); $output = isset( $pipes[1] ) ? stream_get_contents( $pipes[1] ) : 'error'; proc_close( $p ); if ( false !== strpos( $output, 'VULNERABLE' ) ) { // "bad" $this->add_message( 200 ); } // CVE-2014-7169. $test_date = date( 'Y' ); $p = proc_open( "rm -f echo; env 'x=() { (a)=>\' bash -c \"echo date +%Y\"; cat echo", $desc, $pipes, sys_get_temp_dir() ); $output = isset( $pipes[1] ) ? stream_get_contents( $pipes[1] ) : 'error'; proc_close( $p ); if ( trim( $output ) === $test_date ) { // "bad" $this->add_message( 201 ); } } else { // Scan by altering the User-Agent. $request_args = $this->get_default_request_args(); $request_args['user-agent'] = '() { :;}; echo VULNERABLE'; $response = wp_remote_get( add_query_arg( secupress_generate_key( 6 ), secupress_generate_key( 8 ), user_trailingslashit( home_url() ) ), $request_args ); if ( ! is_wp_error( $response ) ) { if ( 200 === wp_remote_retrieve_response_code( $response ) ) { // "bad" $this->add_message( 202 ); } else { // "good" $this->add_message( 0 ); } } } // "good" $this->maybe_set_status( 0 ); return parent::scan(); } /** Fix. ==================================================================================== */ /** * Try to fix the flaw(s). * * @since 1.4.5 * * @return (array) The fix results. */ public function need_manual_fix() { return [ 'fix' => 'fix' ]; } /** * Get an array containing ALL the forms that would fix the scan if it requires user action. * * @since 1.4.5 * * @return (array) An array of HTML templates (form contents most of the time). */ protected function get_fix_action_template_parts() { return [ 'fix' => ' ' ]; } /** * Try to fix the flaw(s) after requiring user action. * * @since 1.4.5 * * @return (array) The fix results. */ public function manual_fix() { if ( $this->has_fix_action_part( 'fix' ) ) { $this->fix(); } // "good" $this->add_fix_message( 1 ); return parent::manual_fix(); } /** * Try to fix the flaw(s). * * @since 1.1.4 * * @return (array) The fix results. */ public function fix() { // Activate. secupress_activate_submodule( 'firewall', 'user-agents-header' ); // "good" $this->add_fix_message( 1 ); return parent::fix(); } } free/classes/scanners/class-secupress-scan-bad-config-files.php 0000644 00000015011 15174670627 0020614 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Bad Config Files scan class. * * @package SecuPress * @subpackage SecuPress_Scan * @since 1.0 */ class SecuPress_Scan_Bad_Config_Files extends SecuPress_Scan implements SecuPress_Scan_Interface { /** Constants. ============================================================================== */ /** * Class version. * * @var (string) */ const VERSION = '1.2'; /** Properties. ============================================================================= */ /** * The reference to the *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** Init and messages. ====================================================================== */ /** * Init. * * @since 1.0 */ protected function init() { $this->title = sprintf( __( 'Check if your installation contains backed up %1$s files like %2$s.', 'secupress' ), secupress_code_me( 'wp-config.php' ), secupress_code_me( 'wp-config.bak/.old' ) ); $this->more = __( 'Some attackers will try to find some backed up config files to try to steal them. Prevent this kind of attack simply by removing them.', 'secupress' ); $this->more_fix = sprintf( __( 'Rename all the %1$s files using a random name and still using the %2$s extension to prevent being downloaded.', 'secupress' ), secupress_code_me( 'wp-config.bak/.old' ), secupress_code_me( '.php' ) ); } /** * Get messages. * * @since 1.0 * * @param (int) $message_id A message ID. * * @return (string|array) A message if a message ID is provided. An array containing all messages otherwise. */ public static function get_messages( $message_id = null ) { $messages = array( // "good" 0 => sprintf( __( 'You don’t have backed up %s files.', 'secupress' ), secupress_code_me( 'wp-config' ) ), 1 => sprintf( __( 'Your backed up %1$s file was successfully suffixed with %2$s.', 'secupress' ), secupress_code_me( 'wp-config.php' ), '%s' ), // "warning" 100 => sprintf( __( '%1$d backed up %3$s file was successfully suffixed with %2$s.', 'secupress' ), '%1$d', '%2$s', secupress_code_me( 'wp-config.php' ) ), 101 => _n_noop( 'Sorry, this file could not be renamed: %s', 'Sorry, those files could not be renamed: %s', 'secupress' ), // "bad" 200 => sprintf( __( 'Your installation should not contain this backed up %1$s file: %2$s.', 'secupress' ), secupress_code_me( 'wp-config.php' ), '%s' ), 201 => sprintf( __( 'Sorry, the backed up %s file could not be renamed.', 'secupress' ), secupress_code_me( 'wp-config.php' ) ), ); if ( isset( $message_id ) ) { return isset( $messages[ $message_id ] ) ? $messages[ $message_id ] : __( 'Unknown message', 'secupress' ); } return $messages; } /** Getters. ================================================================================ */ /** * Get the documentation URL. * * @since 1.2.3 * * @return (string) */ public static function get_docs_url() { return __( 'https://docs.secupress.me/article/96-wp-config-php-file-backups-scan', 'secupress' ); } /** Scan. =================================================================================== */ /** * Scan for flaw(s). * * @since 1.0 * * @return (array) The scan results. */ public function scan() { $activated = $this->filter_scanner( __CLASS__ ); if ( true === $activated ) { $this->add_message( 0 ); return parent::scan(); } $files = static::get_files(); if ( $files ) { // "bad" $files = self::wrap_in_tag( $files ); $this->add_message( 200, array( count( $files ), $files ) ); } else { // "good" $this->add_message( 0 ); } return parent::scan(); } /** Fix. ==================================================================================== */ /** * Try to fix the flaw(s). * * @since 1.4.5 * * @return (array) The fix results. */ public function need_manual_fix() { return [ 'fix' => 'fix' ]; } /** * Get an array containing ALL the forms that would fix the scan if it requires user action. * * @since 1.4.5 * * @return (array) An array of HTML templates (form contents most of the time). */ protected function get_fix_action_template_parts() { return [ 'fix' => ' ' ]; } /** * Try to fix the flaw(s) after requiring user action. * * @since 1.4.5 * * @return (array) The fix results. */ public function manual_fix() { if ( $this->has_fix_action_part( 'fix' ) ) { $this->fix(); } // "good" $this->add_fix_message( 1, __( 'a safe file extension', 'secupress' ) ); return parent::manual_fix(); } /** * Try to fix the flaw(s). * * @since 1.0 * * @return (array) The fix results. */ public function fix() { $files = static::get_files(); // Should not happen. if ( ! $files ) { // "good" $this->add_fix_message( 0 ); return parent::fix(); } $wp_filesystem = secupress_get_filesystem(); $count_all = count( $files ); $renamed = array(); $suffix = '.' . time() . '.secupress.php'; // Rename the files. foreach ( $files as $filename ) { $new_file = ABSPATH . $filename . $suffix; if ( $wp_filesystem->move( ABSPATH . $filename, $new_file ) ) { $wp_filesystem->chmod( $new_file, FS_CHMOD_FILE ); // Counting the renamed files is safer that counting the not renamed ones. $renamed[] = $filename; } } $count_renamed = count( $renamed ); if ( $count_renamed === $count_all ) { // "good": all files were renamed. $this->add_fix_message( 1, array( $count_all, '<code>' . $suffix . '</code>' ) ); } elseif ( $count_renamed ) { // "warning": some files could not be renamed. $not_renamed = array_diff( $files, $renamed ); $not_renamed = static::wrap_in_tag( $not_renamed ); $this->add_fix_message( 100, array( $count_renamed, $count_renamed, '<code>' . $suffix . '</code>' ) ); $this->add_fix_message( 101, array( count( $not_renamed ), $not_renamed ) ); } else { // "bad": no files could not be renamed. $this->add_fix_message( 201, array( $count_all ) ); } return parent::fix(); } /** Tools. ================================================================================== */ /** * Get config files. * * @since 1.0 * * @return (array) An array of file names. */ protected static function get_files() { $files = glob( ABSPATH . '*wp-config*.*' ); $files = array_map( 'basename', $files ); foreach ( $files as $k => $file ) { if ( 'php' === pathinfo( $file, PATHINFO_EXTENSION ) ) { unset( $files[ $k ] ); } } return $files; } } free/classes/scanners/class-secupress-scan-bad-old-plugins.php 0000644 00000066371 15174670627 0020523 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Bad Old Plugins scan class. * * @package SecuPress * @subpackage SecuPress_Scan * @since 1.0 */ class SecuPress_Scan_Bad_Old_Plugins extends SecuPress_Scan implements SecuPress_Scan_Interface { /** Constants. ============================================================================== */ /** * Class version. * * @var (string) */ const VERSION = '2.2.6'; /** Properties. ============================================================================= */ /** * The reference to the *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** Init and messages. ====================================================================== */ /** * Init. * * @since 1.0 */ protected function init() { $this->title = __( 'Check if you are using plugins that have been deleted from the official repository or have not been updated for at least two years.', 'secupress' ); $this->more = __( 'Do not use a plugin that has been closed on the official repository, and prevent usage of plugins that have not been maintained for two years at least.', 'secupress' ); if ( is_network_admin() ) { $this->more_fix = __( 'Select closed and old plugins to be deleted.', 'secupress' ); $this->more_fix .= '<br/>' . __( 'Not fixable on Multisite.', 'secupress' ); $this->fixable = false; } elseif ( ! is_multisite() ) { $this->more_fix = __( 'Select and delete closed and old plugins.', 'secupress' ); } else { $this->more_fix = __( 'Deactivate closed and old plugins.', 'secupress' ); } } /** * Get messages. * * @since 1.0 * * @param (int) $message_id A message ID. * * @return (string|array) A message if a message ID is provided. An array containing all messages otherwise. */ public static function get_messages( $message_id = null ) { $_110 = ! secupress_is_pro() ? sprintf( __( 'The %sPRO version%s will be more accurate.', 'secupress' ), '<a href="' . secupress_admin_url( 'get-pro' ) . '">', '</a>' ) : ' ' . __( 'Scan it now.', 'secupress' ); $messages = array( // "good" 0 => __( 'You don’t use closed or old plugins.', 'secupress' ), 1 => __( 'You don’t use closed or old plugins anymore.', 'secupress' ), 2 => __( 'All closed or old plugins have been deleted.', 'secupress' ), 3 => __( 'All deletable closed or old plugins have been deleted.', 'secupress' ), 4 => __( 'All closed or old plugins have been deactivated.', 'secupress' ), // "warning" /** Translators: %s is a file name. */ 100 => __( 'Error, could not read %s.', 'secupress' ), 101 => __( 'No plugins selected for deletion.', 'secupress' ), 102 => _n_noop( 'Selected plugin has been deleted (but some are still there).', 'All selected plugins have been deleted (but some are still there).', 'secupress' ), 103 => _n_noop( 'Sorry, the following plugin could not be deleted: %s.', 'Sorry, the following plugins could not be deleted: %s.', 'secupress' ), 104 => __( 'No plugins selected for deactivation.', 'secupress' ), 105 => _n_noop( 'Selected plugin has been deactivated (but some are still there).', 'All selected plugins have been deactivated (but some are still there).', 'secupress' ), 106 => _n_noop( 'Sorry, the following plugin could not be deactivated: %s.', 'Sorry, the following plugins could not be deactivated: %s.', 'secupress' ), /** Translators: %s is the plugin name. */ 107 => sprintf( __( 'You have a big network, %s must work on some data before being able to perform this scan.', 'secupress' ), '<strong>' . SECUPRESS_PLUGIN_NAME . '</strong>' ), 110 => sprintf( __( 'Your installation may contain old or closed plugins.%s', 'secupress' ), $_110 ), // "bad" /** Translators: 1 is a number, 2 is a plugin name (or a list of plugin names). */ 200 => _n_noop( '<strong>%1$d plugin</strong> is no longer in the WordPress directory: %2$s.', '<strong>%1$d plugins</strong> are no longer in the WordPress directory: %2$s.', 'secupress' ), /** Translators: 1 is a number, 2 is a plugin name (or a list of plugin names). */ 201 => _n_noop( '<strong>%1$d plugin</strong> has not been updated for at least 2 years: %2$s.', '<strong>%1$d plugins</strong> have not been updated for at least 2 years: %2$s.', 'secupress' ), /** Translators: %s is a plugin name. */ 202 => __( 'You should delete the plugin %s.', 'secupress' ), 203 => _n_noop( 'Sorry, this plugin could not be deleted.', 'Sorry, those plugins could not be deleted.', 'secupress' ), 204 => _n_noop( 'The following plugin should be deactivated if you don’t need it: %s.', 'The following plugins should be deactivated if you don’t need them: %s.', 'secupress' ), 205 => _n_noop( 'Sorry, this plugin could not be deactivated.', 'Sorry, those plugins could not be deactivated.', 'secupress' ), // "cantfix" /** Translators: %d is a number. */ 300 => _n_noop( '<strong>%d</strong> plugin can be <strong>deleted</strong>.', '<strong>%d</strong> plugins can be <strong>deleted</strong>.', 'secupress' ), /** Translators: %d is a number. */ 301 => _n_noop( '<strong>%d</strong> plugin can be <strong>deactivated</strong>.', '<strong>%d</strong> plugins can be <strong>deactivated</strong>.', 'secupress' ), 302 => __( 'Unable to locate WordPress Plugin directory.', 'secupress' ), /** Translators: %s is the plugin name. */ 303 => sprintf( __( 'A new %s menu item has been activated in the relevant site’s administration area to let Administrators know which plugins to deactivate.', 'secupress' ), '<strong>' . SECUPRESS_PLUGIN_NAME . '</strong>' ), 304 => __( 'No plugins selected.', 'secupress' ), ); if ( isset( $message_id ) ) { return isset( $messages[ $message_id ] ) ? $messages[ $message_id ] : __( 'Unknown message', 'secupress' ); } return $messages; } /** Getters. ================================================================================ */ /** * Get the documentation URL. * * @since 1.2.3 * * @return (string) */ public static function get_docs_url() { return __( 'https://docs.secupress.me/article/117-outdated-and-bad-plugin-check', 'secupress' ); } /** Scan. =================================================================================== */ /** * Scan for flaw(s). * * @since 1.0 * * @return (array) The scan results. */ public function scan() { $activated = $this->filter_scanner( __CLASS__ ); if ( true === $activated ) { $this->add_message( 0 ); return parent::scan(); } if ( ! static::are_centralized_blog_options_filled() ) { // "warning" $this->add_message( 107 ); return parent::scan(); } // Multisite, for the current site. if ( $this->is_for_current_site() ) { // Plugins no longer in directory or not updated in over 2 years. $bad_plugins = $this->get_installed_plugins_to_remove(); $bad_plugins = $bad_plugins['to_deactivate']; if ( $count = count( $bad_plugins ) ) { // "bad" $this->add_message( 204, array( $count, $bad_plugins ) ); } } // Network admin or not Multisite. else { // If we're in a sub-site, don't list the plugins enabled in the network. $to_keep = array(); // Plugins no longer in directory. $bad_plugins = $this->get_closed_plugins(); $count = is_array( $bad_plugins ) ? count( $bad_plugins ) : false; if ( ! $count ) { // "warning" if ( secupress_is_pro() ) { $this->add_message( 0 ); } else { $this->add_message( 110 ); } } if ( $count > 0 ) { // "bad" $this->add_message( 200, array( $count, $count, self::wrap_in_tag( $bad_plugins ) ) ); } // Plugins not updated in over 2 years. $bad_plugins = $this->get_old_plugins(); $bad_plugins = $to_keep ? array_diff_key( $bad_plugins, $to_keep ) : $bad_plugins; $count = is_array( $bad_plugins ) ? count( $bad_plugins ) : false; if ( ! $count ) { // "warning" if ( secupress_is_pro() ) { $this->add_message( 0 ); } else { $this->add_message( 110 ); } } if ( $count > 0 ) { // "bad" $this->add_message( 201, array( $count, $count, self::wrap_in_tag( $bad_plugins ) ) ); } // Check for Hello Dolly existence. if ( $hello = $this->has_hello_dolly() ) { // "bad" $this->add_message( 202, $hello ); } } // "good" $this->maybe_set_status( 0 ); return parent::scan(); } /** Fix. ==================================================================================== */ /** * Try to fix the flaw(s). * * @since 1.0 * * @return (array) The fix results. */ public function fix() { // "good" $this->add_fix_message( 1 ); return parent::fix(); } /** Manual fix. ============================================================================= */ /** * Return an array of actions if a manual fix is needed here. * * @since 1.0 * * @return (array) */ public function need_manual_fix() { $bad_plugins = $this->get_installed_plugins_to_remove(); $actions = array(); if ( $bad_plugins['count'] ) { if ( $bad_plugins['to_delete'] ) { $actions['delete-bad-old-plugins'] = 'delete-bad-old-plugins'; } if ( $bad_plugins['to_deactivate'] ) { $actions['deactivate-bad-old-plugins'] = 'deactivate-bad-old-plugins'; } } return $actions; } /** * Try to fix the flaw(s) after requiring user action. * * @since 1.0 * * @return (array) The fix results. */ public function manual_fix() { $bad_plugins = $this->get_installed_plugins_to_remove(); if ( $bad_plugins['count'] ) { // DELETE PLUGINS. if ( $this->has_fix_action_part( 'delete-bad-old-plugins' ) ) { $delete = $this->manual_delete( $bad_plugins['to_delete'], (bool) $bad_plugins['to_deactivate'] ); } // DEACTIVATE PLUGINS. if ( $this->has_fix_action_part( 'deactivate-bad-old-plugins' ) ) { $deactivate = $this->manual_deactivate( $bad_plugins['to_deactivate'], (bool) $bad_plugins['to_delete'] ); } if ( ! empty( $delete ) && ! empty( $deactivate ) ) { // "cantfix": nothing selected in both lists. $this->add_fix_message( 304 ); } elseif ( ! empty( $delete ) ) { // "warning": no plugins selected. $this->add_fix_message( $delete ); } elseif ( ! empty( $deactivate ) ) { // "warning": no plugins selected. $this->add_fix_message( $deactivate ); } } else { // "good" $this->add_fix_message( 1 ); } return parent::manual_fix(); } /** * Manual fix to delete plugins. * * @since 1.0 * * @param (array) $bad_plugins An array of plugins to delete. Values must be sanitized before. * @param (bool) $has_plugins_to_deactivate True if some other plugins must be deactivated (it changes the message). */ protected function manual_delete( $bad_plugins, $has_plugins_to_deactivate ) { if ( ! $bad_plugins ) { // "good" return $this->add_fix_message( 1 ); } // Get the list of plugins to uninstall. $selected_plugins = ! empty( $_POST['secupress-fix-delete-bad-old-plugins'] ) && is_array( $_POST['secupress-fix-delete-bad-old-plugins'] ) ? array_filter( $_POST['secupress-fix-delete-bad-old-plugins'] ) : array(); // WPCS: CSRF ok. $selected_plugins = $selected_plugins ? array_fill_keys( $selected_plugins, 1 ) : array(); $selected_plugins = $selected_plugins ? array_intersect_key( $bad_plugins, $selected_plugins ) : array(); // Sanitize submitted values. if ( ! $selected_plugins ) { if ( $this->has_fix_action_part( 'deactivate-bad-old-plugins' ) ) { /* * "warning": no plugins selected. * No `add_fix_message()`, we need to change the status from warning to cantfix if both lists have no selection. */ return 101; } // "cantfix": no plugins selected. return $this->add_fix_message( 304 ); } // Get filesystem. $wp_filesystem = secupress_get_filesystem(); // Get the base plugin folder. $plugins_dir = $wp_filesystem->wp_plugins_dir(); if ( empty( $plugins_dir ) ) { // "cantfix": plugins dir not located. return $this->add_fix_message( 302 ); } $plugins_dir = trailingslashit( $plugins_dir ); $plugin_translations = wp_get_installed_translations( 'plugins' ); ob_start(); // Deactivate. deactivate_plugins( array_keys( $selected_plugins ) ); $deleted_plugins = array(); foreach ( $selected_plugins as $plugin_file => $dummy ) { // Run Uninstall hook. if ( is_uninstallable_plugin( $plugin_file ) ) { uninstall_plugin( $plugin_file ); } /** This action is documented in wp-admin/includes/plugin.php */ do_action( 'delete_plugin', $plugin_file ); $this_plugin_dir = trailingslashit( dirname( $plugins_dir . $plugin_file ) ); // If plugin is in its own directory, recursively delete the directory. if ( strpos( $plugin_file, '/' ) && $this_plugin_dir !== $plugins_dir ) { // base check on if plugin includes directory separator AND that its not the root plugin folder. $deleted = $wp_filesystem->delete( $this_plugin_dir, true ); } else { $deleted = $wp_filesystem->delete( $plugins_dir . $plugin_file ); } /** This action is documented in wp-admin/includes/plugin.php */ do_action( 'deleted_plugin', $plugin_file, $deleted ); if ( $deleted ) { $deleted_plugins[ $plugin_file ] = 1; // Remove language files, silently. $plugin_slug = dirname( $plugin_file ); if ( '.' !== $plugin_slug && ! empty( $plugin_translations[ $plugin_slug ] ) ) { $translations = $plugin_translations[ $plugin_slug ]; foreach ( $translations as $translation => $data ) { $wp_filesystem->delete( WP_LANG_DIR . '/plugins/' . $plugin_slug . '-' . $translation . '.po' ); $wp_filesystem->delete( WP_LANG_DIR . '/plugins/' . $plugin_slug . '-' . $translation . '.mo' ); } } } } ob_end_clean(); // Everything's deleted, no plugins left. if ( ! array_diff_key( $bad_plugins, $deleted_plugins ) ) { // "good" if ( $has_plugins_to_deactivate ) { $this->add_fix_message( 3 ); } else { $this->add_fix_message( 2 ); } } // All selected plugins deleted. elseif ( ! array_diff_key( $deleted_plugins, $selected_plugins ) ) { // "partial": some plugins still need to be deleted. $this->add_fix_message( 102, array( count( $selected_plugins ) ) ); } // No plugins deleted. elseif ( ! $deleted_plugins ) { // "bad" $this->add_fix_message( 203, array( count( $bad_plugins ) ) ); } // Some plugins could not be deleted. else { // "cantfix" $not_removed = array_diff_key( $selected_plugins, $deleted_plugins ); $not_removed = array_map( 'strip_tags', $not_removed ); $not_removed = array_map( 'esc_html', $not_removed ); $this->add_fix_message( 103, array( count( $not_removed ), $not_removed ) ); } // Force refresh of plugin update information and cache. if ( $deleted_plugins ) { if ( $current = get_site_transient( 'update_plugins' ) ) { $current->response = array_diff_key( $current->response, $deleted_plugins ); $current->no_update = array_diff_key( $current->no_update, $deleted_plugins ); set_site_transient( 'update_plugins', $current ); } wp_cache_delete( 'plugins', 'plugins' ); } } /** * Manual fix to deactivate plugins. * * @since 1.0 * * @param (array) $bad_plugins An array of plugins to deactivate. Values must be sanitized before. * @param (bool) $has_plugins_to_delete True if some other plugins must be deleted (it changes the message). */ protected function manual_deactivate( $bad_plugins, $has_plugins_to_delete ) { if ( ! $bad_plugins ) { if ( $this->is_network_admin() ) { // Remove all previously stored messages for sub-sites. $this->set_empty_data_for_subsites(); } // "good" return $this->add_fix_message( 1 ); } // Get the list of plugins to deactivate. $selected_plugins = ! empty( $_POST['secupress-fix-deactivate-bad-old-plugins'] ) && is_array( $_POST['secupress-fix-deactivate-bad-old-plugins'] ) ? array_filter( $_POST['secupress-fix-deactivate-bad-old-plugins'] ) : array(); // WPCS: CSRF ok. $selected_plugins = $selected_plugins ? array_fill_keys( $selected_plugins, 1 ) : array(); $selected_plugins = $selected_plugins ? array_intersect_key( $bad_plugins, $selected_plugins ) : array(); // Sanitize submitted values. if ( ! $selected_plugins ) { if ( $this->is_network_admin() ) { // Remove all previously stored messages for sub-sites. $this->set_empty_data_for_subsites(); } if ( $this->has_fix_action_part( 'delete-bad-old-plugins' ) ) { /* * "warning": no plugins selected. * No `add_fix_message()`, we need to change the status from warning to cantfix if both lists have no selection. */ return 104; } // "cantfix": no plugins selected. return $this->add_fix_message( 304 ); } // In the network admin we disable nothing. We only store the selected plugins for later use in the sub-sites scans page. if ( $this->is_network_admin() ) { $active_subsites_plugins = get_site_option( 'secupress_active_plugins' ); if ( $active_subsites_plugins && is_array( $active_subsites_plugins ) ) { foreach ( $active_subsites_plugins as $site_id => $active_subsite_plugins ) { $data = array_intersect_key( $selected_plugins, $active_subsite_plugins ); if ( $data ) { $data = array( count( $data ), $data ); // Add a scan message for each listed sub-site. $this->add_subsite_message( 204, $data, 'scan', $site_id ); } else { $this->set_empty_data_for_subsite( $site_id ); } } } // "cantfix" return $this->add_fix_message( 303 ); } // In a sub-site, deactivate plugins. ob_start(); deactivate_plugins( array_keys( $selected_plugins ) ); ob_end_clean(); // Try to see if everything is fine. $site_id = get_current_blog_id(); $active_plugins = get_site_option( 'secupress_active_plugins' ); $active_plugins = isset( $active_plugins[ $site_id ] ) ? $active_plugins[ $site_id ] : array(); // Everything's deactivated, no plugins left. if ( ! array_intersect_key( $bad_plugins, $active_plugins ) ) { // "good" $this->add_fix_message( 4 ); } // All selected plugins deactivated. elseif ( ! array_intersect_key( $active_plugins, $selected_plugins ) ) { // "partial": some plugins still need to be deactivated. $this->add_fix_message( 105, array( count( $selected_plugins ) ) ); } // Some plugins could not be deactivated. else { $selected_plugins_still_active = array_intersect_key( $active_plugins, $selected_plugins ); $deactivated_plugins = array_diff_key( $selected_plugins, $selected_plugins_still_active ); // No plugins deactivated. if ( ! $deactivated_plugins ) { // "bad" $this->add_fix_message( 205, array( count( $bad_plugins ) ) ); } else { // "cantfix" $selected_plugins_still_active = array_intersect_key( $bad_plugins, $selected_plugins_still_active ); $selected_plugins_still_active = array_map( 'strip_tags', $selected_plugins_still_active ); $selected_plugins_still_active = array_map( 'esc_html', $selected_plugins_still_active ); $this->add_fix_message( 106, array( count( $selected_plugins_still_active ), $selected_plugins_still_active ) ); } } } /** * Get an array containing ALL the forms that would fix the scan if it requires user action. * * @since 1.0 * * @return (array) An array of HTML templates (form contents most of the time). */ protected function get_fix_action_template_parts() { $plugins = $this->get_installed_plugins_to_remove(); $out = array( 'delete-bad-old-plugins' => static::get_messages( 1 ), 'deactivate-bad-old-plugins' => static::get_messages( 1 ), ); if ( $plugins['to_delete'] ) { $form = '<h4 id="secupress-fix-bad-old-plugins">' . __( 'Checked plugins will be deleted:', 'secupress' ) . '</h4>'; $form .= '<fieldset aria-labelledby="secupress-fix-bad-old-plugins" class="secupress-boxed-group">'; foreach ( $plugins['to_delete'] as $plugin_file => $plugin_name ) { $is_symlinked = secupress_is_plugin_symlinked( $plugin_file ); $plugin_name = esc_html( strip_tags( $plugin_name ) ); $form .= '<input type="checkbox" id="secupress-fix-delete-bad-old-plugins-' . sanitize_html_class( $plugin_file ) . '" name="secupress-fix-delete-bad-old-plugins[]" value="' . esc_attr( $plugin_file ) . '" ' . ( $is_symlinked ? 'disabled="disabled"' : 'checked="checked"' ) . '/> '; $form .= '<label for="secupress-fix-delete-bad-old-plugins-' . sanitize_html_class( $plugin_file ) . '">'; if ( $is_symlinked ) { $form .= '<del>' . $plugin_name . '</del> <span class="description">(' . __( 'symlinked', 'secupress' ) . ')</span>'; } else { $form .= $plugin_name; } $form .= '</label><br/>'; } $form .= '</fieldset>'; $out['delete-bad-old-plugins'] = $form; } if ( $plugins['to_deactivate'] ) { if ( $this->is_for_current_site() ) { $form = '<h4 id="secupress-fix-bad-old-plugins-deactiv">' . __( 'Checked plugins will be deactivated:', 'secupress' ) . '</h4>'; } else { $form = '<h4 id="secupress-fix-bad-old-plugins-deactiv">' . __( 'Checked plugins will be deactivated by Administrators:', 'secupress' ) . '</h4>'; $form .= '<span class="description">' . _n( 'The following plugin is activated in some of your sites and must be deactivated first. Administrators will be asked to do so.', 'The following plugins are activated in some of your sites and must be deactivated first. Administrators will be asked to do so.', count( $plugins['to_deactivate'] ), 'secupress' ) . '</span>'; } $form .= '<fieldset aria-labelledby="secupress-fix-bad-old-plugins-deactiv" class="secupress-boxed-group">'; foreach ( $plugins['to_deactivate'] as $plugin_file => $plugin_name ) { $is_symlinked = secupress_is_plugin_symlinked( $plugin_file ); $plugin_name = esc_html( strip_tags( $plugin_name ) ); $form .= '<input type="checkbox" id="secupress-fix-deactivate-bad-old-plugins-' . sanitize_html_class( $plugin_file ) . '" name="secupress-fix-deactivate-bad-old-plugins[]" value="' . esc_attr( $plugin_file ) . '" ' . ( $is_symlinked ? 'disabled="disabled"' : 'checked="checked"' ) . '/> '; $form .= '<label for="secupress-fix-deactivate-bad-old-plugins-' . sanitize_html_class( $plugin_file ) . '">'; if ( $is_symlinked ) { $form .= '<del>' . $plugin_name . '</del> <span class="description">(' . __( 'symlinked', 'secupress' ) . ')</span>'; } else { $form .= "<strong>$plugin_name</strong>"; } $form .= '</label><br/>'; } $form .= '</fieldset>'; $out['deactivate-bad-old-plugins'] = $form; } return $out; } /** Tools. ================================================================================== */ /** * Get all plugins to delete. * * @since 1.0 * * @return (array). */ final protected function get_installed_plugins_to_remove() { $plugins = array(); // Plugins no longer in directory. $tmp = $this->get_closed_plugins( true ); if ( $tmp ) { $plugins = $tmp; } // Plugins not updated in over 2 years. $tmp = $this->get_old_plugins( true ); if ( $tmp ) { $plugins = array_merge( $plugins, $tmp ); } // Byebye Dolly. $tmp = $this->has_hello_dolly(); if ( $tmp ) { $plugins = array_merge( $plugins, $tmp ); } return $this->separate_deletable_from_deactivable( $plugins ); } /** * Get plugins no longer in directory. * * @since 1.0 * * @param (bool) $for_fix False: for scan. True: for fix. * * @return (array). */ final protected function get_closed_plugins( $for_fix = false ) { return $this->get_installed_bad_plugins( 'closed', $for_fix ); } /** * Get plugins not updated in over 2 years. * * @since 1.0 * * @param (bool) $for_fix False: for scan. True: for fix. * * @return (array|false). */ final protected function get_old_plugins( $for_fix = false ) { return $this->get_installed_bad_plugins( 'old', $for_fix ); } /** * Get an array of installed "bad" plugins. * * @since 1.0 * * @param (string) $plugins_type "removed_plugins" or "notupdated_plugins". * @param (bool) $for_fix False: for scan. True: for fix. * * @return (array|false) An array like `array( path => plugin_name, path => plugin_name )`. */ final protected function get_installed_bad_plugins( $plugins_type, $for_fix = false ) { static $whitelist_error = false; $bad_plugins = secupress_get_bad_plugins( $plugins_type ); if ( ! is_array( $bad_plugins ) ) { return false; } $all_plugins = get_plugins(); $bad_plugins = array_intersect_key( $all_plugins, $bad_plugins ); $bad_plugins = wp_list_pluck( $bad_plugins, 'Name' ); return $bad_plugins; } /** * Dolly are you here? * * @since 1.0 * * @return (array) An array like `array( path => plugin_name )`. */ final protected function has_hello_dolly() { $plugins = []; // Sub-sites don't need to delete Dolly. if ( ! $this->is_for_current_site() && file_exists( WP_PLUGIN_DIR . '/hello.php' ) ) { $plugins['hello.php'] = '<code>Hello Dolly</code>'; } return $plugins; } /** * From a list of plugins, separate them in 2: those that can be deleted and those that can be deactivated first (from a sub-site). * * @since 1.0 * * @param (array) $plugins An array of "bad" plugins. * * @return (array) An array like `array( path => plugin_name )`. */ final protected function separate_deletable_from_deactivable( $plugins ) { if ( ! $plugins ) { return array( 'to_delete' => array(), 'to_deactivate' => array(), 'count' => 0, ); } // Network: plugins activated in sub-sites must be deactivated in each sub-site first. if ( $this->is_network_admin() ) { $active_subsites_plugins_tmp = get_site_option( 'secupress_active_plugins' ); $active_subsites_plugins = array(); if ( $active_subsites_plugins_tmp && is_array( $active_subsites_plugins_tmp ) ) { foreach ( $active_subsites_plugins_tmp as $site_id => $active_subsite_plugins ) { $active_subsites_plugins = array_merge( $active_subsites_plugins, $active_subsite_plugins ); } } // Let's act like Hello Dolly is not enabled in any sub-site, so we won't need Administrators aproval and we'll be able to delete it directly. unset( $active_subsites_plugins_tmp, $active_subsites_plugins['hello.php'] ); $active_subsites_plugins = array_diff_key( $active_subsites_plugins, get_site_option( 'active_sitewide_plugins' ) ); $out = array( // Plugins that are network activated or not activated in any site can be deleted. 'to_delete' => array_diff_key( $plugins, $active_subsites_plugins ), // Plugins activated in subsites. 'to_deactivate' => array_intersect_key( $plugins, $active_subsites_plugins ), ); } // Sub-site: limit to plugins activated in this sub-site. elseif ( $this->is_for_current_site() ) { $site_id = get_current_blog_id(); $subsite_plugins = get_site_option( 'secupress_active_plugins' ); $subsite_plugins = ! empty( $subsite_plugins[ $site_id ] ) ? $subsite_plugins[ $site_id ] : array(); $out = array( // In a sub-site we don't delete any plugin. 'to_delete' => array(), // We only deactivate them. 'to_deactivate' => array_intersect_key( $plugins, $subsite_plugins ), ); } // Not a multisite. else { $out = array( // All plugins can be deleted. 'to_delete' => $plugins, // No need to deactivate anything. 'to_deactivate' => array(), ); } $out['count'] = count( $out['to_delete'] ) + count( $out['to_deactivate'] ); return $out; } } free/classes/scanners/class-secupress-scan-discloses.php 0000644 00000044650 15174670627 0017526 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Discloses scan class. * * @package SecuPress * @subpackage SecuPress_Scan * @since 1.0 */ class SecuPress_Scan_Discloses extends SecuPress_Scan implements SecuPress_Scan_Interface { /** Constants. ============================================================================== */ /** * Class version. * * @var (string) */ const VERSION = '1.2'; /** Properties. ============================================================================= */ /** * The reference to the *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** Init and messages. ====================================================================== */ /** * Init. * * @since 1.0 */ protected function init() { global $is_apache, $is_nginx, $is_iis7; $module_url = esc_url( secupress_admin_url( 'modules', 'sensitive-data' ) ); $this->title = __( 'Check if your site discloses your WordPress version and your server’s PHP version.', 'secupress' ); $this->more = __( 'When an attacker wants to hack into a WordPress site, they will search for all available informations. The goal is to find something useful that will help him penetrate your site. Don’t let them easily find any informations.', 'secupress' ); $this->more_fix = sprintf( __( 'Activate the %1$s protection and/or the %2$s protection from the module %3$s.', 'secupress' ), '<a href="' . $module_url . '#row-content-protect_wp-version">' . __( 'WordPress Version Disclosure', 'secupress' ) . '</a>', '<a href="' . $module_url . '#row-content-protect_php-version">' . __( 'PHP Version Disclosure', 'secupress' ) . '</a>', '<a href="' . esc_url( secupress_admin_url( 'modules', 'sensitive-data' ) ) . '#row-content-protect_bad-url-access">' . __( 'Sensitive Data', 'secupress' ) . '</a>' ); if ( ! $is_apache && ! $is_nginx && ! $is_iis7 ) { $this->more_fix = static::get_messages( 301 ); $this->fixable = false; return; } } /** * Get messages. * * @since 1.0 * * @param (int) $message_id A message ID. * * @return (string|array) A message if a message ID is provided. An array containing all messages otherwise. */ public static function get_messages( $message_id = null ) { $module_url = esc_url( secupress_admin_url( 'modules', 'sensitive-data' ) ); /** Translators: 1 and 2 are the name of protections, 3 is the name of a module. */ $activate_protections_message = sprintf( __( 'But you can activate the %1$s protection and the %2$s protection from the module %3$s.', 'secupress' ), '<a target="_blank" href="' . $module_url . '#row-content-protect_wp-version">' . __( 'WordPress Version Disclosure', 'secupress' ) . '</a>', '<a target="_blank" href="' . $module_url . '#row-content-protect_php-version">' . __( 'PHP Version Disclosure', 'secupress' ) . '</a>', '<strong>' . __( 'Sensitive Data', 'secupress' ) . '</strong>' ); /** Translators: 1 is the name of a protection, 2 is the name of a module. */ $activate_protection_message = sprintf( __( 'But you can activate the %1$s protection from the module %2$s.', 'secupress' ), '<strong>' . __( 'WordPress Version Disclosure', 'secupress' ) . '</strong>', '<a target="_blank" href="' . $module_url . '#row-content-protect_wp-version">' . __( 'WordPress Version Disclosure', 'secupress' ) . '</a>' ); $messages = array( // "good" 0 => __( 'Your site does not reveal either your <strong>WordPress version</strong> or <strong>PHP version</strong>.', 'secupress' ), 1 => __( 'The protection preventing your site to disclose your <strong>PHP version</strong> has been activated.', 'secupress' ), 7 => __( 'The protection preventing your site to disclose your <strong>WordPress version</strong> has been activated.', 'secupress' ), // "warning" 100 => __( 'Unable to determine if your homepage is disclosing your <strong>WordPress version</strong> or <strong>PHP version</strong>.', 'secupress' ) . ' ' . $activate_protections_message, /** Translators: %s is a file name. */ 101 => sprintf( __( 'Unable to determine if the %s file is disclosing your <strong>WordPress version</strong>.', 'secupress' ), '<code>readme.html</code>' ) . ' ' . $activate_protection_message, // "bad" 200 => __( 'The website displays the <strong>PHP version</strong> in the request headers.', 'secupress' ), 201 => __( 'The website displays the <strong>WordPress version</strong> in the homepage source code (%s).', 'secupress' ), /** Translators: %s is a file name. */ 202 => sprintf( __( 'The %s file should not be accessible by anyone to avoid to reveal your <strong>WordPress version</strong>.', 'secupress' ), '<code>readme.html</code>' ), // "cantfix" /** Translators: 1 is a file name, 2 is some code. */ 300 => sprintf( __( 'Your server runs <strong>Nginx</strong>, the <strong>WordPress version</strong> and <strong>PHP version</strong> disclosure cannot be fixed automatically but you can do it yourself by adding the following code to your %1$s file: %2$s', 'secupress' ), '<code>nginx.conf</code>', '%s' ), /** Translators: 1 is a file name, 2 is some code. */ 302 => sprintf( __( 'Your %1$s file is not writable. Please add the following lines at the beginning of the file: %2$s', 'secupress' ), '<code>.htaccess</code>', '%s' ), /** Translators: 1 is a file name, 2 is a folder path (kind of), 3 is some code. */ 303 => sprintf( __( 'Your %1$s file is not writable. Please add the following lines inside the tags hierarchy %2$s (create it if does not exist): %3$s', 'secupress' ), '<code>web.config</code>', '%1$s', '%2$s' ), /** Translators: 1 is a file name, 2 is some code. */ 304 => sprintf( __( 'Your server runs <strong>Nginx</strong>, the <strong>PHP version</strong> disclosure cannot be fixed automatically but you can do it yourself by adding the following code to your %1$s file: %2$s', 'secupress' ), '<code>nginx.conf</code>', '%s' ), /** Translators: 1 is a file name, 2 is some code. */ 305 => sprintf( __( 'Your server runs <strong>Nginx</strong>, the <strong>WordPress version</strong> disclosure cannot be fixed automatically but you can do it yourself by adding the following code to your %1$s file: %2$s', 'secupress' ), '<code>nginx.conf</code>', '%s' ), // DEPRECATED, NOT IN USE ANYMORE. /** Translators: %s is a file name. */ 2 => sprintf( __( 'The %s file is now protected from revealing your <strong>WordPress version</strong>.', 'secupress' ), '<code>readme.html</code>' ), 3 => __( 'The website does not display the <strong>PHP version</strong> in the request headers anymore.', 'secupress' ), 4 => __( 'The generator meta tag should not be displayed anymore.', 'secupress' ), 5 => __( 'The <strong>WordPress version</strong> should now be removed from your styles URLs.', 'secupress' ), 6 => __( 'The <strong>WordPress version</strong> should now be removed from your scripts URLs.', 'secupress' ), 301 => __( 'Your server runs an unrecognized system. The <strong>WordPress version</strong> and <strong>PHP version</strong> disclosure cannot be fixed automatically.', 'secupress' ), ); if ( isset( $message_id ) ) { return isset( $messages[ $message_id ] ) ? $messages[ $message_id ] : __( 'Unknown message', 'secupress' ); } return $messages; } /** Getters. ================================================================================ */ /** * Get the documentation URL. * * @since 1.2.3 * * @return (string) */ public static function get_docs_url() { return __( 'https://docs.secupress.me/article/101-php-and-wordpress-version-disclosure-scan', 'secupress' ); } /** Scan. =================================================================================== */ /** * Scan for flaw(s). * * @since 1.0 * * @return (array) The scan results. */ public function scan() { $activated = $this->filter_scanner( __CLASS__ ); if ( true === $activated ) { $this->add_message( 0 ); return parent::scan(); } global $is_nginx; $wp_version = get_bloginfo( 'version' ); $php_version = phpversion(); $wp_discloses = array(); // Get home page contents. ==========================. $response = wp_remote_get( add_query_arg( secupress_generate_key( 6 ), secupress_generate_key( 8 ), user_trailingslashit( home_url() ) ), $this->get_default_request_args() ); $has_response = ! is_wp_error( $response ) && 200 === wp_remote_retrieve_response_code( $response ); if ( $has_response ) { $powered_by = wp_remote_retrieve_header( $response, 'x-powered-by' ); $powered_by = is_array( $powered_by ) ? reset( $powered_by ) : $powered_by; $body = wp_remote_retrieve_body( $response ); } // WordPress version in generator meta tag. =========. if ( $has_response ) { // Meta tag. preg_match_all( '#<meta[^>]*[name="generator"]?[^>]*content="WordPress ' . $wp_version . '"[^>]*[name="generator"]?[^>]*>#si', $body, $matches ); if ( array_filter( $matches ) ) { // "bad" $wp_discloses[] = 'META'; } } // Style tag src. ===================================. $style_url = home_url( '/fake.css?ver=' . $wp_version ); /** This filter is documented in wp-includes/class.wp-styles.php */ if ( apply_filters( 'style_loader_src', $style_url, 'secupress' ) === $style_url ) { // "bad" $wp_discloses[] = 'CSS'; } // Script tag src. ==================================. $script_url = home_url( '/fake.js?ver=' . $wp_version ); /** This filter is documented in wp-includes/class.wp-scripts.php */ if ( apply_filters( 'script_loader_src', $script_url, 'secupress' ) === $script_url ) { // "bad" $wp_discloses[] = 'JS'; } // Sum up! if ( $wp_discloses ) { // "bad" $this->add_message( 201, array( $wp_discloses ) ); $is_bad = true; } // Readme file. =====================================. $response = wp_remote_get( site_url( 'readme.html' ), $this->get_default_request_args() ); if ( ! is_wp_error( $response ) ) { if ( 200 === wp_remote_retrieve_response_code( $response ) ) { // "bad" $this->add_message( 202 ); } } // PHP version in headers. ==========================. if ( $has_response && false !== strpos( $powered_by, $php_version ) ) { // "bad" $this->add_message( 200 ); } // "good" $this->maybe_set_status( 0 ); return parent::scan(); } /** Fix. ==================================================================================== */ /** * Try to fix the flaw(s). * * @since 1.4.5 * * @return (array) The fix results. */ public function need_manual_fix() { return [ 'fix' => 'fix' ]; } /** * Get an array containing ALL the forms that would fix the scan if it requires user action. * * @since 1.4.5 * * @return (array) An array of HTML templates (form contents most of the time). */ protected function get_fix_action_template_parts() { return [ 'fix' => ' ' ]; } /** * Try to fix the flaw(s) after requiring user action. * * @since 1.4.5 * * @return (array) The fix results. */ public function manual_fix() { if ( $this->has_fix_action_part( 'fix' ) ) { $this->fix(); } // "good" $this->add_fix_message( 1 ); return parent::manual_fix(); } /** * Try to fix the flaw(s). * * @since 1.0 * * @return (array) The fix results. */ public function fix() { global $is_apache, $is_nginx, $is_iis7; $wp_version = get_bloginfo( 'version' ); $php_version = phpversion(); $todo = array(); // Get home page contents. ==========================. $response = wp_remote_get( add_query_arg( secupress_generate_key( 6 ), secupress_generate_key( 8 ), user_trailingslashit( home_url() ) ), $this->get_default_request_args() ); $has_response = ! is_wp_error( $response ) && 200 === wp_remote_retrieve_response_code( $response ); if ( $has_response ) { $powered_by = wp_remote_retrieve_header( $response, 'x-powered-by' ); if ( is_array( $powered_by ) ) { foreach( $powered_by as $p ) { if ( strpos( $p, 'PHP/' ) === 0 ) { $powered_by = $p; break; } } } $body = wp_remote_retrieve_body( $response ); } // WordPress version in generator meta tag. =========. if ( $has_response ) { // Meta tag. preg_match_all( '#<meta[^>]*[name="generator"]?[^>]*content="WordPress ' . $wp_version . '"[^>]*[name="generator"]?[^>]*>#si', $body, $matches ); if ( array_filter( $matches ) ) { $todo['wp_version'] = 1; } } // Style tag src. ===================================. if ( empty( $todo['wp_version'] ) ) { $style_url = home_url( '/fake.css?ver=' . $wp_version ); /** This filter is documented in wp-includes/class.wp-styles.php */ if ( apply_filters( 'style_loader_src', $style_url, 'secupress' ) === $style_url ) { $todo['wp_version'] = 1; } } // Script tag src. ==================================. if ( empty( $todo['wp_version'] ) ) { $script_url = home_url( '/fake.js?ver=' . $wp_version ); /** This filter is documented in wp-includes/class.wp-scripts.php */ if ( apply_filters( 'script_loader_src', $script_url, 'secupress' ) === $script_url ) { $todo['wp_version'] = 1; } } // Readme file. =====================================. if ( empty( $todo['wp_version'] ) ) { $response = wp_remote_get( site_url( 'readme.html' ), $this->get_default_request_args() ); if ( ! is_wp_error( $response ) ) { if ( 200 === wp_remote_retrieve_response_code( $response ) ) { $todo['wp_version'] = 1; } } else { // "warning" $this->add_fix_message( 101 ); } } // PHP version in headers. ==========================. if ( $has_response && false !== strpos( $powered_by, $php_version ) ) { $todo['php_version'] = 1; } if ( $todo ) { if ( $is_apache ) { $this->fix_apache( $todo ); } elseif ( $is_iis7 ) { $this->fix_iis7( $todo ); } elseif ( $is_nginx ) { $this->fix_nginx( $todo ); } } // "good" $this->maybe_set_fix_status( 0 ); return parent::fix(); } /** * Fix for Apache system. * * @since 1.0 * * @param (array) $todo Tasks to do. */ protected function fix_apache( $todo ) { global $wp_settings_errors; $all_rules = array(); // WP version disclosure. if ( isset( $todo['wp_version'] ) ) { secupress_activate_submodule( 'discloses', 'wp-version' ); // Got error? $last_error = is_array( $wp_settings_errors ) && $wp_settings_errors ? end( $wp_settings_errors ) : false; if ( $last_error && 'general' === $last_error['setting'] && 'apache_manual_edit' === $last_error['code'] ) { $all_rules[] = static::get_rules_from_error( $last_error ); array_pop( $wp_settings_errors ); } else { // "good" $this->add_fix_message( 7 ); } } // PHP version disclosure. if ( isset( $todo['php_version'] ) ) { secupress_activate_submodule( 'discloses', 'no-x-powered-by' ); // Got error? $last_error = is_array( $wp_settings_errors ) && $wp_settings_errors ? end( $wp_settings_errors ) : false; if ( $last_error && 'general' === $last_error['setting'] && 'apache_manual_edit' === $last_error['code'] ) { $all_rules[] = static::get_rules_from_error( $last_error ); array_pop( $wp_settings_errors ); } else { // "good" $this->add_fix_message( 1 ); } } if ( $all_rules ) { $all_rules = implode( "\n", $all_rules ); // "cantfix" $this->add_fix_message( 302, array( $all_rules ) ); } } /** * Fix for IIS7 system. * * @since 1.0 * * @param (array) $todo Tasks to do. */ protected function fix_iis7( $todo ) { global $wp_settings_errors; // WP version disclosure. if ( isset( $todo['wp_version'] ) ) { secupress_activate_submodule( 'discloses', 'wp-version' ); // Got error? $last_error = is_array( $wp_settings_errors ) && $wp_settings_errors ? end( $wp_settings_errors ) : false; if ( $last_error && 'general' === $last_error['setting'] && 'iis7_manual_edit' === $last_error['code'] ) { $rules = static::get_rules_from_error( $last_error ); $path = static::get_code_tag_from_error( $last_error, 'secupress-iis7-path' ); // "cantfix" $this->add_fix_message( 303, array( $path, $rules ) ); array_pop( $wp_settings_errors ); } else { // "good" $this->add_fix_message( 7 ); } } // PHP version disclosure. if ( isset( $todo['php_version'] ) ) { secupress_activate_submodule( 'discloses', 'no-x-powered-by' ); // Got error? $last_error = is_array( $wp_settings_errors ) && $wp_settings_errors ? end( $wp_settings_errors ) : false; if ( $last_error && 'general' === $last_error['setting'] && 'iis7_manual_edit' === $last_error['code'] ) { $rules = static::get_rules_from_error( $last_error ); $path = static::get_code_tag_from_error( $last_error, 'secupress-iis7-path' ); // "cantfix" $this->add_fix_message( 303, array( $path, $rules ) ); array_pop( $wp_settings_errors ); } else { // "good" $this->add_fix_message( 1 ); } } } /** * Fix for nginx system. * * @since 1.0 * * @param (array) $todo Tasks to do. */ protected function fix_nginx( $todo ) { global $wp_settings_errors; // WP version disclosure. if ( isset( $todo['wp_version'] ) ) { secupress_activate_submodule( 'discloses', 'wp-version' ); // Get the error. $last_error = is_array( $wp_settings_errors ) && $wp_settings_errors ? end( $wp_settings_errors ) : false; $todo['wp_version'] = '<code>Error</code>'; if ( $last_error && 'general' === $last_error['setting'] && 'nginx_manual_edit' === $last_error['code'] ) { $todo['wp_version'] = static::get_rules_from_error( $last_error ); array_pop( $wp_settings_errors ); } } // PHP version disclosure. if ( isset( $todo['php_version'] ) ) { secupress_activate_submodule( 'discloses', 'no-x-powered-by' ); // Get the error. $last_error = is_array( $wp_settings_errors ) && $wp_settings_errors ? end( $wp_settings_errors ) : false; $todo['php_version'] = '<code>Error</code>'; if ( $last_error && 'general' === $last_error['setting'] && 'nginx_manual_edit' === $last_error['code'] ) { $todo['php_version'] = static::get_rules_from_error( $last_error ); array_pop( $wp_settings_errors ); } } if ( isset( $todo['php_version'], $todo['wp_version'] ) ) { $todo = implode( "\n", $todo ); // "cantfix" $this->add_fix_message( 300, array( $todo ) ); } elseif ( isset( $todo['php_version'] ) ) { // "cantfix" $this->add_fix_message( 304, array_values( $todo ) ); } else { // "cantfix" $this->add_fix_message( 305, array_values( $todo ) ); } } } free/classes/scanners/class-secupress-scan-bad-usernames.php 0000644 00000015167 15174670627 0020265 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Bad Usernames scan class. * * @package SecuPress * @subpackage SecuPress_Scan * @since 1.0 */ class SecuPress_Scan_Bad_Usernames extends SecuPress_Scan implements SecuPress_Scan_Interface { /** Constants. ============================================================================== */ /** * Class version. * * @var (string) */ const VERSION = '1.2'; /** Properties. ============================================================================= */ /** * The reference to the *Singleton* instance of this class. * * @var (object) */ /** Init and messages. ====================================================================== */ /** * Init. * * @since 1.0 */ protected function init() { $activated = secupress_is_submodule_active( 'users-login', 'blacklist-logins' ); $this->fixable = ! $activated; $this->title = __( 'Check if your usernames are correctly set.', 'secupress' ); $this->more = __( 'Some usernames are known to be used for malicious usage, or created by bots, or the same as the nickname.', 'secupress' ); $this->more_fix = sprintf( __( 'Activate the option %1$s in the %2$s module.', 'secupress' ), '<em>' . __( 'Forbid Bad Usernames', 'secupress' ) . '</em>', '<a href="' . esc_url( secupress_admin_url( 'modules', 'users-login' ) ) . '#row-blacklist-logins_activated">' . __( 'Users & Login', 'secupress' ) . '</a>' ); } /** * Get messages. * * @since 1.0 * * @param (int) $message_id A message ID. * * @return (string|array) A message if a message ID is provided. An array containing all messages otherwise. */ public static function get_messages( $message_id = null ) { $messages = array( // "good" 0 => __( 'All the usernames are correct.', 'secupress' ), 1 => __( 'Module activated: The users with a disallowed username will be asked to change it, also users with a nickname same as their login will be automatically renamed.', 'secupress' ), 2 => __( 'Users updated: The users with a login same as their nickname or display_name have been renamed, their login is still the same.', 'secupress' ), // "bad" 200 => _n_noop( '<strong>%1$s user</strong> has a disallowed username: %2$s', '<strong>%1$s users</strong> have a disallowed username: %2$s', 'secupress' ), 201 => _n_noop( '<strong>%1$s user</strong> has the same display name as login: %2$s', '<strong>%1$s users</strong> have the same display name as login: %2$s', 'secupress' ), 202 => _n_noop( '<strong>%1$s user</strong> has the same nickname as login: %2$s', '<strong>%1$s users</strong> have the same nickname as login: %2$s', 'secupress' ), // "cantfix" 300 => __( 'The module is already activated. Let’s give your users some time to change their username.', 'secupress' ), ); if ( isset( $message_id ) ) { return isset( $messages[ $message_id ] ) ? $messages[ $message_id ] : __( 'Unknown message', 'secupress' ); } return $messages; } /** Getters. ================================================================================ */ /** * Get the documentation URL. * * @since 1.2.3 * * @return (string) */ public static function get_docs_url() { return __( 'https://docs.secupress.me/article/133-bad-username-scan', 'secupress' ); } /** Scan. =================================================================================== */ /** * Scan for flaw(s). * * @since 2.2.6 Use REGEX to handle "*" * @author Julio Potier * * @since 1.0 * @author Grégory Viguier * * @return (array) The scan results. */ public function scan() { $activated = $this->filter_scanner( __CLASS__ ); if ( true === $activated ) { $this->add_message( 0 ); return parent::scan(); } global $wpdb; // Blacklisted names. $names = static::get_blacklisted_usernames(); $sql = "SELECT user_login from $wpdb->users WHERE user_login REGEXP '^($names)$'"; // WPCS: unprepared SQL ok. $logins = $wpdb->get_col( $sql ); $ids = count( $logins ); // "bad" if ( $ids ) { $this->slice_and_dice( $logins, 10 ); // 2nd param: 1st item is used for the noop if needed, the rest for sprintf. $this->add_message( 200, array( $ids, $ids, static::wrap_in_tag( $logins, 'strong' ) ) ); if ( secupress_is_submodule_active( 'users-login', 'blacklist-logins' ) ) { $this->add_message( 300 ); } } // "good" $this->maybe_set_status( 0 ); return parent::scan(); } /** Fix. ==================================================================================== */ /** * Try to fix the flaw(s). * * @since 1.4.5 * * @return (array) The fix results. */ public function need_manual_fix() { return [ 'fix' => 'fix' ]; } /** * Get an array containing ALL the forms that would fix the scan if it requires user action. * * @since 1.4.5 * * @return (array) An array of HTML templates (form contents most of the time). */ protected function get_fix_action_template_parts() { return [ 'fix' => ' ' ]; } /** * Try to fix the flaw(s) after requiring user action. * * @since 1.4.5 * * @return (array) The fix results. */ public function manual_fix() { if ( $this->has_fix_action_part( 'fix' ) ) { $this->fix(); } // "good" $this->add_fix_message( 1 ); return parent::manual_fix(); } /** * Try to fix the flaw(s). * * @since 1.0 * * @return (array) The fix results. */ public function fix() { global $wpdb; // Blacklisted names. $names = static::get_blacklisted_usernames(); $ids = $wpdb->get_col( "SELECT ID from $wpdb->users WHERE user_login REGEXP '^($names)$'" ); // WPCS: unprepared SQL ok. if ( $ids ) { $activated = secupress_is_submodule_active( 'users-login', 'blacklist-logins' ); if ( $activated ) { // Well... Can't do better. // "cantfix". $this->add_fix_message( 300 ); } else { // Activate. secupress_activate_submodule( 'users-login', 'blacklist-logins' ); // "good" $this->add_fix_message( 1 ); } } // "good" $this->maybe_set_fix_status( 0 ); return parent::fix(); } /** Tools. ================================================================================== */ /** * Get the blacklisted usernames. * * @since 2.2.6 Use REGEX to handle "*" * @author Julio Potier * * @since 1.0 * @author Grégory Viguier * * @return (string) A comma separated list of blacklisted usernames. */ final protected static function get_blacklisted_usernames() { $names = secupress_get_blacklisted_usernames(); $names = implode( '|', array_map( 'preg_quote', $names ) ); $names = str_replace( '\*', '.*', $names ); return $names; } } free/classes/scanners/class-secupress-scan-bad-vuln-plugins.php 0000644 00000021104 15174670627 0020712 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Bad Vulnerable Plugins scan class. * * @package SecuPress * @subpackage SecuPress_Scan * @since 1.0 */ class SecuPress_Scan_Bad_Vuln_Plugins extends SecuPress_Scan implements SecuPress_Scan_Interface { /** Constants. ============================================================================== */ /** * Class version. * * @var (string) */ const VERSION = '1.2'; /** Properties. ============================================================================= */ /** * The reference to the *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** * Tells if a scanner is fixable by SecuPress. The value "pro" means it's fixable only with the version PRO. * * @var (bool|string) */ protected $fixable = 'pro'; /** Init and messages. ====================================================================== */ /** * Init. * * @since 1.0 */ protected function init() { $this->title = __( 'Check if you are using plugins known to be vulnerable.', 'secupress' ); $this->more = __( 'Never use a plugin with a known vulnerability, you should update or remove it as soon as possible!', 'secupress' ); if ( is_network_admin() ) { $this->more_fix = __( 'Select and delete vulnerable plugins.', 'secupress' ); $this->more_fix .= '<br/>' . __( 'Not fixable on Multisite.', 'secupress' ); $this->fixable = false; } elseif ( ! is_multisite() ) { $this->more_fix = __( 'Delete vulnerable plugins.', 'secupress' ); } else { $this->more_fix = __( 'Deactivate vulnerable plugins.', 'secupress' ); } } /** * Get messages. * * @since 1.0 * * @param (int) $message_id A message ID. * * @return (string|array) A message if a message ID is provided. An array containing all messages otherwise. */ public static function get_messages( $message_id = null ) { $_107 = ! secupress_is_pro() ? sprintf( __( 'The %sPRO version%s will be more accurate.', 'secupress' ), '<a href="' . secupress_admin_url( 'get-pro' ) . '">', '</a>' ) : ' ' . __( 'Scan it now.', 'secupress' ); $messages = array( // "good" 0 => __( 'You don’t use plugins known to be vulnerable.', 'secupress' ), 1 => __( 'You don’t use plugins known to be vulnerable anymore.', 'secupress' ), 2 => __( 'All plugins known to be vulnerable have been deleted.', 'secupress' ), 3 => __( 'All plugins known to be vulnerable have been deleted.', 'secupress' ), 4 => __( 'All plugins known to be vulnerable have been deactivated.', 'secupress' ), // "warning" /** Translators: %s is a file name. */ 100 => __( 'Error, could not read %s.', 'secupress' ), 101 => __( 'No plugins selected for deletion.', 'secupress' ), 102 => _n_noop( 'Selected plugin has been deleted (but some are still there).', 'All selected plugins have been deleted (but some are still there).', 'secupress' ), 103 => _n_noop( 'Sorry, the following plugin could not be deleted: %s.', 'Sorry, the following plugins could not be deleted: %s.', 'secupress' ), 104 => __( 'No plugins selected for deactivation.', 'secupress' ), 105 => _n_noop( 'Selected plugin has been deactivated (but some are still there).', 'All selected plugins have been deactivated (but some are still there).', 'secupress' ), 106 => _n_noop( 'Sorry, the following plugin could not be deactivated: %s.', 'Sorry, the following plugins could not be deactivated: %s.', 'secupress' ), 107 => sprintf( __( 'Your installation may contain vulnerable plugins.%s', 'secupress' ), $_107 ), // "bad" /** Translators: 1 is a number, 2 is a plugin name (or a list of plugin names). */ 200 => _n_noop( '<strong>%1$d plugin</strong> is known to be vulnerable: %2$s.', '<strong>%1$d plugins</strong> are known to be vulnerable: %2$s.', 'secupress' ), /** Translators: %s is a plugin name. */ 202 => __( 'You should delete the plugin %s.', 'secupress' ), 203 => _n_noop( 'Sorry, this plugin could not be deleted.', 'Sorry, those plugins could not be deleted.', 'secupress' ), 204 => _n_noop( 'The following plugin should be deactivated if you don’t need it: %s.', 'The following plugins should be deactivated if you don’t need them: %s.', 'secupress' ), 205 => _n_noop( 'Sorry, this plugin could not be deactivated.', 'Sorry, those plugins could not be deactivated.', 'secupress' ), // "cantfix" /** Translators: %d is a number. */ 300 => _n_noop( '<strong>%d</strong> plugin can be <strong>deleted</strong>.', '<strong>%d</strong> plugins can be <strong>deleted</strong>.', 'secupress' ), /** Translators: %d is a number. */ 301 => _n_noop( '<strong>%d</strong> plugin can be <strong>deactivated</strong>.', '<strong>%d</strong> plugins can be <strong>deactivated</strong>.', 'secupress' ), 302 => __( 'Unable to locate WordPress Plugin directory.', 'secupress' ), /** Translators: %s is the plugin name. */ 303 => sprintf( __( 'A new %s menu item has been activated in the relevant site’s administration area to let Administrators know which plugins to deactivate.', 'secupress' ), '<strong>' . SECUPRESS_PLUGIN_NAME . '</strong>' ), 304 => __( 'No plugins selected.', 'secupress' ), ); if ( isset( $message_id ) ) { return isset( $messages[ $message_id ] ) ? $messages[ $message_id ] : __( 'Unknown message', 'secupress' ); } return $messages; } /** Getters. ================================================================================ */ /** * Get the documentation URL. * * @since 1.2.3 * * @return (string) */ public static function get_docs_url() { return __( 'https://docs.secupress.me/article/121-vulnerable-plugins-check', 'secupress' ); } /** Scan. =================================================================================== */ /** * Scan for flaw(s). * * @since 1.0 * * @return (array) The scan results. */ public function scan() { $activated = $this->filter_scanner( __CLASS__ ); if ( true === $activated ) { $this->add_message( 0 ); return parent::scan(); } if ( ! $this->is_for_current_site() ) { // If we're in a sub-site, don't list the plugins enabled in the network. $to_keep = array(); // Plugins vulnerables. $bad_plugins = $this->get_installed_plugins_vulnerables(); if ( is_numeric( $bad_plugins ) ) { $this->add_message( 107 ); } elseif ( $count = count( $bad_plugins ) ) { // "bad" $this->add_message( 200, array( $count, $count, self::wrap_in_tag( $bad_plugins ) ) ); } } // "good" $this->maybe_set_status( 0 ); return parent::scan(); } /** Fix. ==================================================================================== */ /** * Try to fix the flaw(s). * * @since 1.4.5 * * @return (array) The fix results. */ public function need_manual_fix() { return [ 'fix' => 'fix' ]; } /** * Get an array containing ALL the forms that would fix the scan if it requires user action. * * @since 1.4.5 * * @return (array) An array of HTML templates (form contents most of the time). */ protected function get_fix_action_template_parts() { return [ 'fix' => ' ' ]; } /** * Try to fix the flaw(s) after requiring user action. * * @since 1.4.5 * * @return (array) The fix results. */ public function manual_fix() { if ( $this->has_fix_action_part( 'fix' ) ) { $this->fix(); } // "good" $this->add_fix_message( 1 ); return parent::manual_fix(); } /** * Try to fix the flaw(s). * * @since 1.0 * * @return (array) The fix results. */ public function fix() { secupress_activate_submodule( 'plugins-themes', 'detect-bad-plugins' ); // "good" $this->add_fix_message( 1 ); return parent::fix(); } /** Tools. ================================================================================== */ /** * Get an array of installed plugins that are vulnerable. * * @since 2.1 Returns 1 if not pro * @since 1.0.3 Don't use the whitelist * @since 1.0 * * @param (bool) $for_fix False: for scan. True: for fix. * * @return (array) An array like `array( path => plugin_name, path => plugin_name )`. */ final protected function get_installed_plugins_vulnerables( $for_fix = false ) { static $whitelist_error = false; if ( ! secupress_is_pro() ) { return 1; } $bad_plugins = secupress_get_vulnerable_plugins(); if ( ! $bad_plugins ) { return array(); } $all_plugins = get_plugins(); $all_plugins = array_keys( get_plugins() ); $all_plugins = array_combine( array_map( 'dirname', $all_plugins ), $all_plugins ); $bad_plugins = array_intersect_key( $all_plugins, $bad_plugins ); return $bad_plugins; } } free/classes/scanners/class-secupress-scan-subscription.php 0000644 00000023600 15174670627 0020252 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Subscription scan class. * * @package SecuPress * @subpackage SecuPress_Scan * @since 1.0 */ class SecuPress_Scan_Subscription extends SecuPress_Scan implements SecuPress_Scan_Interface { /** Constants. ============================================================================== */ /** * Class version. * * @var (string) */ const VERSION = '1.0.3'; /** Properties. ============================================================================= */ /** * The reference to the *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** * The minimum role to be available here * * @var (string) */ protected $role_minimum; /** * The minimum role, translated * * @var (string) */ protected $role_minimum_i18n; /** Init and messages. ====================================================================== */ /** * Init. * * @since 1.0 */ protected function init() { global $wp_roles; $this->role_minimum = apply_filters( 'secupress.scan.' . __CLASS__ . '.role_minimum', 'subscriber' ); $this->role_minimum_i18n = isset( $wp_roles->role_names[ $this->role_minimum ] ) ? secupress_translate_user_role( $wp_roles->role_names[ $this->role_minimum ] ) : _x( 'None', 'user role', 'secupress' ); $this->title = __( 'Check if the subscription settings are set correctly.', 'secupress' ); if ( ! is_multisite() || is_network_admin() ) { $this->more = sprintf( __( 'If user registrations are open, the default user role should be %s. Moreover, your registration page should be protected from bots.', 'secupress' ), $this->role_minimum_i18n ); $this->more_fix = sprintf( __( 'Activate the option %1$s in the %2$s module.', 'secupress' ), '<em>' . __( 'Use a Captcha on login page', 'secupress' ) . '</em>', '<a href="' . esc_url( secupress_admin_url( 'modules', 'users-login' ) ) . '#row-captcha_activate">' . __( 'Users & Login', 'secupress' ) . '</a>' ); if ( is_network_admin() ) { $this->more_fix .= '<br/>' . sprintf( __( 'If the default user role is not %1$s in some of your websites, administrators will be asked to set the default user role to %1$s.', 'secupress' ), $this->role_minimum_i18n ); } else { $this->more_fix .= '<br/>' . sprintf( __( 'Set the default user’s role to %s.', 'secupress' ), $this->role_minimum_i18n ); } } else { $this->more = sprintf( __( 'If user registrations are open, the default user role should be %s.', 'secupress' ), $this->role_minimum_i18n ); $this->more_fix = sprintf( __( 'Set the default user’s role to %s.', 'secupress' ), $this->role_minimum_i18n ); } } /** * Get messages. * * @since 1.0 * * @param (int) $message_id A message ID. * * @return (string|array) A message if a message ID is provided. An array containing all messages otherwise. */ public static function get_messages( $message_id = null ) { /** Translators: 1 is the name of a protection, 2 is the name of a module. */ $activate_protection_message = sprintf( __( 'But you can activate the %1$s protection from the module %2$s.', 'secupress' ), '<strong>' . __( 'Use a Captcha on login page', 'secupress' ) . '</strong>', '<a target="_blank" href="' . esc_url( secupress_admin_url( 'modules', 'users-login' ) ) . '#row-captcha_activate">' . __( 'Users & Login', 'secupress' ) . '</a>' ); $messages = array( // "good" 0 => __( 'Your subscription settings are set correctly.', 'secupress' ), 1 => __( 'A captcha module has been activated to block bot registration.', 'secupress' ), 2 => __( 'The user role for new registrations has been set to <strong>%s</strong>.', 'secupress' ), // "warning" 100 => __( 'Unable to determine the status of your subscription settings.', 'secupress' ) . ' ' . $activate_protection_message, /** Translators: %s is the plugin name. */ 101 => sprintf( __( 'You have a big network, %s must work on some data before being able to perform this scan.', 'secupress' ), '<strong>' . SECUPRESS_PLUGIN_NAME . '</strong>' ), // "bad" 200 => __( 'The default role in your installation is <strong>%1$s</strong> and it should be <strong>%2$s</strong>, or registrations should be <strong>closed</strong>.', 'secupress' ), 201 => __( 'The registration page is <strong>not protected</strong> from bots.', 'secupress' ), 202 => _n_noop( 'The default role is not %2$s in %1$s of your sites.', 'The default role is not %2$s in %1$s of your sites.', 'secupress' ), // "cantfix" /** Translators: %s is the plugin name. */ 300 => sprintf( __( 'The default role cannot be fixed from here. A new %s menu item has been activated in the relevant site’s administration area.', 'secupress' ), '<strong>' . SECUPRESS_PLUGIN_NAME . '</strong>' ), ); if ( isset( $message_id ) ) { return isset( $messages[ $message_id ] ) ? $messages[ $message_id ] : __( 'Unknown message', 'secupress' ); } return $messages; } /** Getters. ================================================================================ */ /** * Get the documentation URL. * * @since 1.2.3 * * @return (string) */ public static function get_docs_url() { return __( 'https://docs.secupress.me/article/134-membership-settings-scan', 'secupress' ); } /** Scan. =================================================================================== */ /** * Scan for flaw(s). * * @since 1.0 * * @return (array) The scan results. */ public function scan() { $activated = $this->filter_scanner( __CLASS__ ); if ( true === $activated ) { $this->add_message( 0 ); return parent::scan(); } global $wp_roles; // Subscriptions are closed. if ( ! secupress_users_can_register() ) { // "good" $this->add_message( 0 ); return parent::scan(); } if ( ! static::are_centralized_blog_options_filled() ) { // "warning" $this->add_message( 101 ); return parent::scan(); } // Default role. if ( $this->is_network_admin() ) { $roles = get_site_option( 'secupress_default_role' ); $blogs = array(); foreach ( $roles as $blog_id => $role ) { if ( 'administrator' === $role ) { $blogs[] = $blog_id; } } if ( $count = count( $blogs ) ) { // "bad" $this->add_message( 202, array( $count, $count, $this->role_minimum_i18n ) ); } } else { $role = get_option( 'default_role' ); if ( 'administrator' === $role ) { // "bad" $role = isset( $wp_roles->role_names[ $role ] ) ? secupress_translate_user_role( $wp_roles->role_names[ $role ] ) : _x( 'None', 'user role', 'secupress' ); $this->add_message( 200, array( $role, $this->role_minimum_i18n ) ); } } // Bots. $token = wp_generate_password(); set_transient( 'secupress_scan_subscription_token', $token ); $user_login = 'secupress_' . time(); $request_args = $this->get_default_request_args(); $request_args = array_merge( $request_args, array( 'body' => array( 'user_login' => $user_login, 'user_email' => 'secupress_no_mail_SS@fakemail.' . time(), 'secupress_token' => $token, ), ) ); unset( $request_args['cookies'] ); $response = wp_remote_post( wp_registration_url(), $request_args ); delete_transient( 'secupress_scan_subscription_token' ); if ( ! is_wp_error( $response ) ) { if ( $user_id = username_exists( $user_login ) ) { wp_delete_user( $user_id ); if ( 'failed' === get_transient( 'secupress_registration_test' ) ) { // "bad" $this->add_message( 201 ); } } } delete_transient( 'secupress_registration_test' ); // "good" $this->maybe_set_status( 0 ); return parent::scan(); } /** Fix. ==================================================================================== */ /** * Try to fix the flaw(s). * * @since 1.4.5 * * @return (array) The fix results. */ public function need_manual_fix() { return [ 'fix' => 'fix' ]; } /** * Get an array containing ALL the forms that would fix the scan if it requires user action. * * @since 1.4.5 * * @return (array) An array of HTML templates (form contents most of the time). */ protected function get_fix_action_template_parts() { return [ 'fix' => ' ' ]; } /** * Try to fix the flaw(s) after requiring user action. * * @since 1.4.5 * * @return (array) The fix results. */ public function manual_fix() { if ( $this->has_fix_action_part( 'fix' ) ) { $this->fix(); } if ( is_multisite() ) { $this->add_fix_message( 300 ); } else { $this->add_fix_message( 2, array( $this->role_minimum_i18n ) ); } // "good" $this->add_fix_message( 1 ); return parent::manual_fix(); } /** * Try to fix the flaw(s). * * @since 1.0 * * @return (array) The fix results. */ public function fix() { global $wp_roles; if ( ! secupress_users_can_register() ) { return parent::fix(); } // Default role. if ( $this->is_network_admin() ) { $roles = get_site_option( 'secupress_default_role' ); $is_bad = false; foreach ( $roles as $blog_id => $role ) { if ( 'administrator' === $role ) { $is_bad = true; $role = isset( $wp_roles->role_names[ $role ] ) ? secupress_translate_user_role( $wp_roles->role_names[ $role ] ) : _x( 'None', 'user role', 'secupress' ); $data = array( $role, $this->role_minimum_i18n ); // Add a scan message for each sub-site with wrong role. $this->add_subsite_message( 200, $data, 'scan', $blog_id ); } else { $this->set_empty_data_for_subsite( $blog_id ); } } if ( $is_bad ) { // "cantfix" $this->add_fix_message( 300 ); } } elseif ( 'administrator' === get_option( 'default_role' ) ) { update_option( 'default_role', $this->role_minimum ); // "good" $this->add_fix_message( 2 ); } // Bots: use a captcha. secupress_activate_submodule( 'users-login', 'login-captcha' ); // "good" $this->add_fix_message( 1 ); return parent::fix(); } } free/classes/scanners/class-secupress-scan.php 0000644 00000103217 15174670627 0015533 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Base scan interface. * * @package SecuPress * @since 1.0 */ interface SecuPress_Scan_Interface { /** * Get messages. * * @since 1.0 * * @param (int) $message_id A message ID. */ public static function get_messages( $message_id = null ); /** * Scan for flaw(s). * * @since 1.0 */ public function scan(); /** * Try to fix the flaw(s). * * @since 1.0 */ public function fix(); } /** * Base scan abstract class. * * @package SecuPress * @since 1.0 */ abstract class SecuPress_Scan extends SecuPress_Singleton implements SecuPress_Scan_Interface { /** Constants. ============================================================================== */ /** * Class version. * * @var (string) */ const VERSION = '1.0.2'; /** Properties. ============================================================================= */ /** * The part of the class that extends this one, like SecuPress_Scan_{$class_name_part}. * * @var (string) */ protected $class_name_part; /** * Contains scan results. * * @var (array) */ protected $result = array(); /** * Contains fix results. * * @var (array) */ protected $result_fix = array(); /** * On multisite, some fixes can't be performed from the network admin. * This array will contain a list of site IDs with scan messages. * * @var (array) */ protected $fix_sites; /** * On multisite, if `$for_current_site` is true, then the scan/fix/etc are performed for the current site, not wetwork-widely. * If needed, should be set right after instanciation. * Does nothing on non-multisite installations. * * @var (bool) */ protected $for_current_site = false; /** * Tells if a scanner is fixable by SecuPress. The value "pro" means it's fixable only with the version PRO. * * @var (bool|string) */ protected $fixable = true; /** * Tells if the fix must occur after all other scans and fixes, while no other scan/fix is running. * * @var (bool) */ protected $delayed_fix = false; /** * Scanner title. * * @var (string) */ public $title = ''; /** * Scan description. * * @var (string) */ public $more = ''; /** * Fix description. * * @var (string) */ public $more_fix = ''; /** Init. =================================================================================== */ /** * Init: this method is required by the class `SecuPress_Singleton`. * * @since 1.0 */ protected function _init() { $this->class_name_part = substr( get_called_class(), 15 ); // 15 is 'SecuPress_Scan_' length. $this->init(); } /** * Sub-classes init. * * @since 1.0 */ protected function init() { die( 'Method SecuPress_Scan->init() must be over-ridden in a sub-class.' ); } /** Multisite specifics. ==================================================================== */ /** * Get `$for_current_site`. * * @since 1.0 * * @return (bool) */ final public function is_for_current_site() { return $this->for_current_site; } /** * `is_network_admin()` does not work in an ajax callback. * * @since 1.0 * * @return (bool) */ final public function is_network_admin() { return is_multisite() && ! $this->is_for_current_site(); } /** * Set `$for_current_site`. * * @since 1.0 * * @param (bool) $for_current_site The new value for `$for_current_site`. * * @return $this */ final public function for_current_site( $for_current_site = null ) { $this->for_current_site = (bool) $for_current_site; return $this; } /** Getters. ================================================================================ */ /** * Tells if a scanner is fixable by SecuPress. The value "pro" means it's fixable only with the version PRO. * * @since 1.0 * * @return (bool|string) */ public function is_fixable() { return $this->fixable; } /** * Tells if the fix must occur after all other scans and fixes, while no other scan/fix is running. * * @since 1.0 * * @return (bool) */ public function is_delayed_fix() { return (bool) $this->delayed_fix; } /** * The fix order value, 1 first, then 2... * * @since 2.3.18.1 * * @return (bool) */ public function get_delayed_fix_value() { return (int) $this->delayed_fix; } /** * Get the documentation URL. * * @since 1.0 * * @return (string) */ public static function get_docs_url() { return __( 'https://docs.secupress.me/', 'secupress' ); } /** * Get the timeout in seconds, filterable * * @since 1.0.4 * * @return (int) Timeout in seconds */ public static function get_timeout() { /** * Some scan are doing a request on the homepage or some internal files, we need sometimes to raise the timeout * * @since 1.0.4 * * @param (int) Timeout in seconds */ return apply_filters( 'secupress.remote_timeout', 30 ); } /** Messages for scans and fixes. =========================================================== */ /** * Get messages. * * @since 1.0 * * @param (int) $message_id A message ID. */ public static function get_messages( $message_id = null ) { die( 'Method SecuPress_Scan::get_messages() must be over-ridden in a sub-class.' ); } /** Status and messages for scans. ========================================================== */ /** * Maybe set current scan status, only if it isn't set yet. * If the status was already set, only allow to "upgrade" to a superior status. * * @since 1.0 * * @param (string) $status The status code. * @param (bool) $force Set the status, even if it was already set. * * @return (string|bool) The current status. False on failure. */ final protected function set_status( $status, $force = false ) { if ( $this->is_for_current_site() ) { return $this->set_subsite_status( $status, 'scan', 0, $force ); } $statuses = array( 'cantfix' => 0, 'good' => 1, 'warning' => 2, 'bad' => 3, ); // Unkown status. if ( ! isset( $statuses[ $status ] ) ) { return false; } // No previous status. if ( empty( $this->result['status'] ) || $force ) { $this->result['status'] = $status; return $status; } // Status already set: only allow to "upgrade" to a superior status. if ( $statuses[ $status ] > $statuses[ $this->result['status'] ] ) { $this->result['status'] = $status; } return $this->result['status']; } /** * Add a scan message and automatically set the scan status. * * "good": the scan performed correctly and returned a good result. * "warning": the scan could not perform correctly. * "bad": the scan performed correctly but returned a bad result. * * @since 1.0 * * @param (int) $message_id The message ID. * @param (array) $params The arguments to use with `vsprintf()`. */ final protected function add_message( $message_id, $params = array() ) { if ( $this->is_for_current_site() ) { return $this->add_subsite_message( $message_id, $params ); } $this->result['msgs'] = isset( $this->result['msgs'] ) ? $this->result['msgs'] : array(); $this->result['msgs'][ $message_id ] = $params; $this->set_status( static::get_status_from_message_id( $message_id ) ); } /** * Are scan status and message(s) set? * * @since 1.0 * * @return (bool) */ final protected function has_status() { if ( $this->is_for_current_site() ) { return $this->has_subsite_status(); } return ! empty( $this->result ); } /** * Set a scan status + message only if no status is set yet. * * @since 1.0 * * @param (int) $message_id The message ID. * @param (array) $params The arguments to use with `vsprintf()`. */ final protected function maybe_set_status( $message_id, $params = array() ) { if ( $this->is_for_current_site() ) { return $this->maybe_set_subsite_status( $message_id, $params ); } if ( ! $this->has_status() ) { $this->add_message( $message_id, $params ); } } /** Status and messages for fixes. ========================================================== */ /** * Maybe set current fix status, only if it isn't set yet. * If the status was already set, only allow to "upgrade" to a superior status. * * @since 1.0 * * @param (string) $status The status code. * @param (bool) $force Set the status, even if it was already set. * * @return (string|bool) The current status. False on failure. */ final protected function set_fix_status( $status, $force = false ) { if ( $this->is_for_current_site() ) { return $this->set_subsite_status( $status, 'fix', 0, $force ); } $statuses = array( 'cantfix' => 0, 'good' => 1, 'warning' => 2, 'bad' => 3, ); // Unkown status. if ( ! isset( $statuses[ $status ] ) ) { return false; } // No previous status. if ( empty( $this->result_fix['status'] ) || $force ) { $this->result_fix['status'] = $status; return $status; } // Status already set: only allow to "upgrade" to a superior status. if ( $statuses[ $status ] > $statuses[ $this->result_fix['status'] ] ) { $this->result_fix['status'] = $status; } return $this->result_fix['status']; } /** * Add a fix message and automatically set the fix status. * * "good": the fix performed correctly. * "warning": partial fix. The fix could not perform entirely: some fix(es) worked and some not. * "bad": error. The fix could not perform correctly. * "cantfix": neutral. The flaw cannot be fixed by this plugin. * * @since 1.0 * * @param (int) $message_id The message ID. * @param (array) $params The arguments to use with `vsprintf()`. */ public function add_fix_message( $message_id, $params = array() ) { if ( $this->is_for_current_site() ) { return $this->add_subsite_message( $message_id, $params, 'fix' ); } $this->result_fix['msgs'] = isset( $this->result_fix['msgs'] ) ? $this->result_fix['msgs'] : array(); $this->result_fix['msgs'][ $message_id ] = $params; $this->set_fix_status( static::get_status_from_message_id( $message_id ) ); } /** * Are fix status and message(s) set? * * @since 1.0 * * @return (bool) */ final protected function has_fix_status() { if ( $this->is_for_current_site() ) { return $this->has_subsite_status( 'fix' ); } return ! empty( $this->result_fix ); } /** * Set a fix status + message only if no status is set yet. * * @since 1.0 * * @param (int) $message_id The message ID. * @param (array) $params The arguments to use with `vsprintf()`. */ final protected function maybe_set_fix_status( $message_id, $params = array() ) { if ( $this->is_for_current_site() ) { return $this->maybe_set_subsite_status( $message_id, $params, 'fix' ); } if ( ! $this->has_fix_status() ) { $this->add_fix_message( $message_id, $params ); } } /** * Add a pre fix message to inform the user before a fix (usually because the fix cannot be done). * * "good": the fix performed correctly. * "warning": partial fix. The fix could not perform entirely: some fix(es) worked and some not. * "bad": error. The fix could not perform correctly. * "cantfix": neutral. The flaw cannot be fixed by this plugin. * * @since 1.0 * * @param (int) $message_id The message ID. * @param (array) $params The arguments to use with `vsprintf()`. */ public function add_pre_fix_message( $message_id, $params = array() ) { if ( $this->is_for_current_site() ) { return $this->add_subsite_message( $message_id, $params ); } $this->result['fix_msg'] = isset( $this->result['fix_msg'] ) ? $this->result['fix_msg'] : array(); $this->result['fix_msg'][ $message_id ] = $params; $this->result_fix['msgs'] = isset( $this->result['fix_msg'] ) ? $this->result['fix_msg'] : array(); $this->result_fix['msgs'][ $message_id ] = $params; $this->update_fix(); } /** Messages for subsites. ================================================================== */ /** * On multisite, some fixes can't be performed from the network admin. * Here we store a list of site IDs with some data: * array( * blog_id => array( * 'scan' => array( * 'status' => 'bad|warning|good', * 'msgs' => array( * message_id => array( data_1 ), * message_id => array( data_1, data_2 ), * ), * ), * 'fix' => array( * 'status' => 'bad|warning|good|cantfix', * 'msgs' => array( * message_id => array( data_1 ), * message_id => array( data_1, data_2 ), * ), * ), * ), * ) */ /** * Maybe set current fix status. * * @since 1.0 * * @param (string) $status The status code. * @param (string) $scan_or_fix For a scan or a fix. * @param (int) $site_id The site ID. * @param (bool) $force Set the status, even if it was already set. * * @return (string|bool) The current status. False on failure. */ final protected function set_subsite_status( $status, $scan_or_fix = 'scan', $site_id = 0, $force = false ) { $statuses = array( 'cantfix' => 0, 'good' => 1, 'warning' => 2, 'bad' => 3, ); // Unkown status. if ( ! isset( $statuses[ $status ] ) ) { return false; } $scan_or_fix = 'fix' === $scan_or_fix ? 'fix' : 'scan'; $site_id = $site_id ? $site_id : get_current_blog_id(); $this->set_subsite_defaults( $scan_or_fix, $site_id ); // No previous status. if ( empty( $this->fix_sites[ $site_id ][ $scan_or_fix ]['status'] ) || $force ) { $this->fix_sites[ $site_id ][ $scan_or_fix ]['status'] = $status; } // Status already set: only allow to "upgrade" to a superior status. elseif ( $statuses[ $status ] > $statuses[ $this->fix_sites[ $site_id ][ $scan_or_fix ]['status'] ] ) { $this->fix_sites[ $site_id ][ $scan_or_fix ]['status'] = $status; } return $this->fix_sites[ $site_id ][ $scan_or_fix ]['status']; } /** * Add a message and automatically set the fix status. * * @since 1.0 * * @param (int) $message_id The message ID. * @param (array) $params The arguments to use with `vsprintf()`. * @param (string) $scan_or_fix For a scan or a fix. * @param (int) $site_id The site ID. */ final protected function add_subsite_message( $message_id, $params = array(), $scan_or_fix = 'scan', $site_id = 0 ) { $scan_or_fix = 'fix' === $scan_or_fix ? 'fix' : 'scan'; $site_id = $site_id ? $site_id : get_current_blog_id(); $this->set_subsite_status( static::get_status_from_message_id( $message_id ), $scan_or_fix, $site_id ); $this->fix_sites[ $site_id ][ $scan_or_fix ]['msgs'][ $message_id ] = $params; } /** * Are status and message(s) set? * * @since 1.0 * * @param (string) $scan_or_fix For a scan or a fix. * @param (int) $site_id The site ID. * * @return (bool) */ final protected function has_subsite_status( $scan_or_fix = 'scan', $site_id = 0 ) { $scan_or_fix = 'fix' === $scan_or_fix ? 'fix' : 'scan'; $site_id = $site_id ? $site_id : get_current_blog_id(); return ! empty( $this->fix_sites[ $site_id ][ $scan_or_fix ] ); } /** * Set a status + message only if no status is set yet. * * @since 1.0 * * @param (int) $message_id The message ID. * @param (array) $params The arguments to use with `vsprintf()`. * @param (string) $scan_or_fix For a scan or a fix. * @param (int) $site_id The site ID. */ final protected function maybe_set_subsite_status( $message_id, $params = array(), $scan_or_fix = 'scan', $site_id = 0 ) { if ( ! $this->has_subsite_status( $scan_or_fix, $site_id ) ) { $this->add_subsite_message( $message_id, $params, $scan_or_fix, $site_id ); } } /** * Set defaults. * * @since 1.0 * * @param (string) $scan_or_fix For a scan or a fix. * @param (int) $site_id The site ID. */ final protected function set_subsite_defaults( $scan_or_fix = 'scan', $site_id = 0 ) { $scan_or_fix = 'fix' === $scan_or_fix ? 'fix' : 'scan'; $site_id = $site_id ? $site_id : get_current_blog_id(); $this->set_empty_data_for_subsite( $site_id ); $this->fix_sites[ $site_id ][ $scan_or_fix ] = isset( $this->fix_sites[ $site_id ][ $scan_or_fix ] ) ? $this->fix_sites[ $site_id ][ $scan_or_fix ] : array(); $this->fix_sites[ $site_id ][ $scan_or_fix ]['msgs'] = isset( $this->fix_sites[ $site_id ][ $scan_or_fix ]['msgs'] ) ? $this->fix_sites[ $site_id ][ $scan_or_fix ]['msgs'] : array(); $this->fix_sites[ $site_id ][ $scan_or_fix ]['status'] = isset( $this->fix_sites[ $site_id ][ $scan_or_fix ]['status'] ) ? $this->fix_sites[ $site_id ][ $scan_or_fix ]['status'] : ''; } /** * Remove all previously stored messages for sub-sites. * This will allow to set an empty transient, and then empty the option later. * * @since 1.0 */ final protected function set_empty_data_for_subsites() { $this->fix_sites = array_fill_keys( static::get_blog_ids(), array() ); } /** * Remove all previously stored messages for a particular sub-site. * This will allow to set an empty transient, and then empty the option later for this sub-site. * * @since 1.0 * * @param (int) $site_id The site ID. */ final protected function set_empty_data_for_subsite( $site_id = 0 ) { $site_id = $site_id ? $site_id : get_current_blog_id(); $this->fix_sites = is_array( $this->fix_sites ) ? $this->fix_sites : array(); $this->fix_sites[ $site_id ] = isset( $this->fix_sites[ $site_id ] ) ? $this->fix_sites[ $site_id ] : array(); } /** * Get a sub-site scan/fix results. * * @since 1.0 * * @param (string) $scan_or_fix For a scan or a fix. * @param (int) $site_id The site ID. * * @return (array) */ final protected function get_subsite_result( $scan_or_fix = 'scan', $site_id = 0 ) { $scan_or_fix = 'fix' === $scan_or_fix ? 'fix' : 'scan'; $scan_or_fix_invert = 'fix' === $scan_or_fix ? 'scan' : 'fix'; $site_id = $site_id ? $site_id : get_current_blog_id(); $result = $this->fix_sites[ $site_id ][ $scan_or_fix ]; unset( $this->fix_sites[ $site_id ][ $scan_or_fix ] ); if ( ! empty( $this->fix_sites[ $site_id ][ $scan_or_fix_invert ] ) ) { return $result; } unset( $this->fix_sites[ $site_id ] ); if ( ! empty( $this->fix_sites ) ) { return $result; } $this->fix_sites = null; return $result; } /** Scan and fix. =========================================================================== */ /** * Scan for flaw(s). * * @since 1.0 * * @return (array) The scan results. */ public function scan() { $this->update(); if ( $this->is_for_current_site() ) { $result = $this->get_subsite_result(); } else { $result = $this->result; $this->result = array(); } return $result; } /** * Try to fix the flaw(s). * * @since 1.0 * * @return (array) The fix results. */ public function fix() { $this->update_fix(); if ( $this->is_for_current_site() ) { $result = $this->get_subsite_result( 'fix' ); } else { $result = $this->result_fix; $this->result_fix = array(); } return $result; } /** * Return an array of actions if a manual fix is needed here. False otherwise. * In case a scanner with a manual fix doesn't need to be fixed, return an empty array instead of false: this way, this scanner will never be listed in the automatic fixes (if the scan is not up to date for example). * * @since 1.0 * * @return (bool|array) */ public function need_manual_fix() { return false; } /** * Try to fix the flaw(s) after requiring user action. * * @since 1.0 * * @return (array) The fix results. */ public function manual_fix() { $this->update_fix(); if ( wp_doing_ajax() ) { $fix_actions = $this->need_manual_fix(); if ( $fix_actions ) { // Add the fixes that require user action in the returned data. $form = $this->get_required_fix_action_template_parts( $fix_actions, false ); if ( $this->is_for_current_site() ) { $site_id = get_current_blog_id(); $this->set_subsite_defaults( 'fix', $site_id ); $this->fix_sites[ $site_id ]['fix']['form_contents'] = $form; } else { $this->result_fix['form_contents'] = $form; } } } if ( $this->is_for_current_site() ) { $result = $this->get_subsite_result( 'fix' ); } else { $result = $this->result_fix; $this->result_fix = array(); } return $result; } /** * Get ONLY THE REQUIRED forms that would fix the scan if it requires user action. * * @since 1.0 * * @param (array) $fix_actions An array of fix actions. * @param (bool) $echo True to print the outpout, False to return it. * * @return (string) A string of HTML templates (form contents most of the time). */ final public function get_required_fix_action_template_parts( $fix_actions, $echo = true ) { $templates = array_intersect_key( $this->get_fix_action_template_parts(), $fix_actions ); $templates = implode( '', $templates ); if ( $templates ) { $templates .= $this->get_fix_action_fields( $fix_actions ); } if ( ! $echo ) { return $templates; } echo $templates; } /** * Get an array containing ALL the forms that would fix the scan if it requires user action. * * @since 1.0 * * @return (array) An array of HTML templates (form contents most of the time). */ protected function get_fix_action_template_parts() { return array(); } /** * Tell if a fix action part is needed. * * @since 1.0 * * @param (string) $fix_id A fix action identifier. * * @return (bool) */ final protected function has_fix_action_part( $fix_id ) { $fix_ids = ! empty( $_POST['test-parts'] ) ? ',' . $_POST['test-parts'] . ',' : ''; // WPCS: CSRF ok. return false !== strpos( $fix_ids, ',' . $fix_id . ',' ); } /** * Get the required fields for the user fix form (nonce, referrer, action, etc). * * @since 1.0 * * @param (array) $fix_actions An array of fix actions. * * @return (string) */ final public function get_fix_action_fields( $fix_actions ) { $nonce = 'secupress_manual_fixit_' . $this->class_name_part; $output = "\n"; if ( $this->is_for_current_site() ) { $nonce .= '-' . get_current_blog_id(); $output .= '<input type="hidden" name="for-current-site" value="1" />' . "\n"; $output .= '<input type="hidden" name="site" value="' . get_current_blog_id() . '" />' . "\n"; } $output .= '<input type="hidden" name="action" value="secupress_manual_fixit" />' . "\n"; $output .= '<input type="hidden" name="test" value="' . $this->class_name_part . '" />' . "\n"; $output .= '<input type="hidden" name="test-parts" value="' . implode( ',', $fix_actions ) . '" />' . "\n"; $output .= '<input type="hidden" name="_wpnonce" value="' . wp_create_nonce( $nonce ) . '" />' . "\n"; $output .= '<input type="hidden" name="_wp_http_referer" value="' . esc_attr( wp_unslash( $_SERVER['REQUEST_URI'] ) ) . '" />' . "\n"; return $output; } /** Options. ================================================================================ */ /** * Set options: scan results. * * @since 1.0 * * @return (array) Scan results. */ final public function update() { $name = strtolower( $this->class_name_part ); if ( $this->is_for_current_site() ) { if ( ! isset( $this->fix_sites ) ) { return array(); } $site_id = get_current_blog_id(); $sub_results = SecuPress_Scanner_Results::get_sub_sites_results( $name ); $sub_results = is_array( $sub_results ) ? $sub_results : array(); $sub_results[ $site_id ] = ! empty( $sub_results[ $site_id ] ) ? $sub_results[ $site_id ] : array(); $sub_results[ $site_id ]['scan'] = ! empty( $this->fix_sites[ $site_id ]['scan'] ) ? $this->fix_sites[ $site_id ]['scan'] : array(); SecuPress_Scanner_Results::update_sub_sites_result( $name, $sub_results ); return isset( $this->fix_sites[ $site_id ]['scan'] ) && is_array( $this->fix_sites[ $site_id ]['scan'] ) ? $this->fix_sites[ $site_id ]['scan'] : array(); } SecuPress_Scanner_Results::update_scan_result( $name, $this->result ); return $this->result; } /** * Set options: fix results. * * @since 1.0 * * @return (array) Fix results. */ final public function update_fix() { $name = strtolower( $this->class_name_part ); if ( $this->is_for_current_site() ) { if ( ! isset( $this->fix_sites ) ) { return array(); } $site_id = get_current_blog_id(); $sub_results = SecuPress_Scanner_Results::get_sub_sites_results( $name ); $sub_results = is_array( $sub_results ) ? $sub_results : array(); $sub_results[ $site_id ] = ! empty( $sub_results[ $site_id ] ) ? $sub_results[ $site_id ] : array(); $sub_results[ $site_id ]['fix'] = ! empty( $this->fix_sites[ $site_id ]['fix'] ) ? $this->fix_sites[ $site_id ]['fix'] : array(); SecuPress_Scanner_Results::update_sub_sites_result( $name, $sub_results ); return isset( $this->fix_sites[ $site_id ]['fix'] ) && is_array( $this->fix_sites[ $site_id ]['fix'] ) ? $this->fix_sites[ $site_id ]['fix'] : array(); } if ( isset( $this->fix_sites ) ) { SecuPress_Scanner_Results::update_sub_sites_result( $name, $this->fix_sites ); } SecuPress_Scanner_Results::update_fix_result( $name, $this->result_fix ); return $this->result_fix; } /** Other transients. ======================================================================= */ /** * Auto-scan: schedule an auto-scan that will be executed on page load. * * @since 1.0 */ final public function schedule_autoscan() { if ( $this->is_for_current_site() ) { $transient = secupress_get_transient( 'secupress_autoscans' ); $transient = is_array( $transient ) ? $transient : array(); $transient[ $this->class_name_part ] = $this->class_name_part; secupress_set_transient( 'secupress_autoscans', $transient ); } else { $transient = secupress_get_site_transient( 'secupress_autoscans' ); $transient = is_array( $transient ) ? $transient : array(); $transient[ $this->class_name_part ] = $this->class_name_part; secupress_set_site_transient( 'secupress_autoscans', $transient ); } } /** * Auto-scan: get auto-scans and delete the transient used to store them. * * @since 1.0 * * @return (array) Scan results. */ final public static function get_and_delete_autoscans() { if ( is_multisite() && ! is_network_admin() ) { $transient = secupress_get_transient( 'secupress_autoscans' ); if ( false !== $transient ) { secupress_delete_transient( 'secupress_autoscans' ); } } else { $transient = secupress_get_site_transient( 'secupress_autoscans' ); if ( false !== $transient ) { secupress_delete_site_transient( 'secupress_autoscans' ); } } return is_array( $transient ) ? $transient : array(); } /** Tools. ================================================================================== */ /** * Given an array of items, wrap them in a HTML tag. * * @since 1.0 * * @param (array) $items An array of items. * @param (string) $tag The tag. * * @return (array). */ final public static function wrap_in_tag( $items, $tag = 'code' ) { if ( $items ) { $items = (array) $items; foreach ( $items as $k => $item ) { $items[ $k ] = sprintf( '<%2$s>%1$s</%2$s>', $item, $tag ); } } return $items ? $items : array(); } /** * Slice the items if there are too many. Will add a "XX others" item. * * @since 1.0 * * @param (array) $items An array of items. * @param (int) $max_count The maximum length of the array. */ final public static function slice_and_dice( &$items, $max_count ) { $count = count( $items ) - $max_count; if ( $count > 0 ) { $items = array_slice( $items, 0, $max_count ); array_push( $items, sprintf( _n( '%s other', '%s others', $count ), number_format_i18n( $count ) ) ); } } /** * Get the status code from a message identifier. * * @since 1.0 * * @param (int) $message_id The message ID. */ final protected static function get_status_from_message_id( $message_id ) { if ( $message_id < 100 ) { return 'good'; } if ( $message_id < 200 ) { return 'warning'; } if ( $message_id < 300 ) { return 'bad'; } if ( $message_id < 400 ) { return 'cantfix'; } } /** * Get the list of the blog IDs. * * @since 1.0 * * @return (array) */ final protected static function get_blog_ids() { global $wpdb; static $blogs; if ( isset( $blogs ) ) { return $blogs; } $blogs = $wpdb->get_col( $wpdb->prepare( "SELECT blog_id FROM $wpdb->blogs WHERE site_id = %d", $wpdb->siteid ) ); $blogs = array_map( 'absint', $blogs ); return $blogs; } /** * A shorthand to get the default arguments to use in `wp_remote_get()` and friends. * * @since 1.1.3 * @author Grégory Viguier * * @return (array) An array of default arguments. */ protected function get_default_request_args() { $class_name = get_class( $this ); $request_args = array( 'redirection' => 0, 'timeout' => static::get_timeout(), 'sslverify' => apply_filters( 'https_local_ssl_verify', false ), 'user-agent' => SECUPRESS_PLUGIN_NAME . '/' . SECUPRESS_VERSION, 'cookies' => $_COOKIE, 'headers' => array( 'X-SecuPress-Origin' => $class_name, ), ); /** * Filter the default arguments used in the scanners' internal requests. * * @since 1.1.3 * * @param (array) $request_args The request arguments. * @param (string) $class_name The scan class name. */ return apply_filters( 'secupress.scan.default_request_args', $request_args, $class_name ); } /** * A sandbox for doing crazy things with `.htaccess`. * Create a folder containing a `.htaccess` file with the provided content and a `secupress.html` file. * Then, make a request to the `secupress.html` file to test if a server error is triggered. * * @since 1.0 * * @param (string) $content The content to put in the `.htaccess` file. * * @return (object|bool) Return true if the server does not trigger an error 500, false otherwise. * Return a WP_Error object if the sandbox creation fails or if the HTTP request fails. */ final protected function htaccess_success_in_sandbox( $content ) { /** * Allows to bypass the sandbox * @param (bool) true by default, false to use it. * @param (string) A context. */ if ( false === apply_filters( 'secupress.use_sandbox', true, 'htaccess' ) ) { return true; } $wp_filesystem = secupress_get_filesystem(); $folder_name = 'secupress-sandbox-' . uniqid(); $folder_path = ABSPATH . '/' . $folder_name; // Create folder. if ( ! $wp_filesystem->mkdir( $folder_path ) ) { return new WP_Error( 'dir_creation_failed', __( 'The temporary directory could not be created.', 'secupress' ) ); } // Create `secupress.html` file. if ( ! $wp_filesystem->put_contents( $folder_path . '/secupress.html', 'You are here.', FS_CHMOD_FILE ) ) { $wp_filesystem->delete( $folder_path, true ); return new WP_Error( 'file_creation_failed', __( 'The temporary directory could not be created.', 'secupress' ) ); } // Create `.htaccess` file with our content. if ( ! $wp_filesystem->put_contents( $folder_path . '/.htaccess', $content, FS_CHMOD_FILE ) ) { $wp_filesystem->delete( $folder_path, true ); return new WP_Error( 'htaccess_creation_failed', __( 'The temporary directory could not be created.', 'secupress' ) ); } // Try to reach `secupress.html`. $response = wp_remote_get( site_url( $folder_name . '/secupress.html' ), $this->get_default_request_args() ); // Now we can get rid of the files. $wp_filesystem->delete( $folder_path, true ); // HTTP requests are probably blocked. if ( is_wp_error( $response ) ) { return $response; } // Finally, the answer we were looking for. return 500 !== wp_remote_retrieve_response_code( $response ); } /** * Extract a `<pre/>` content from a message. * * @since 1.0 * * @param (array) $error An array where `message` is the key of our message. * * @return (string) The `<pre/>` tag and its content, or `<code>Error</code>` if no `<pre/>` tag is found. */ final protected static function get_rules_from_error( $error ) { $rules = '<code>Error</code>'; if ( preg_match( '@<pre>.+</pre>@ms', $error['message'], $matches ) ) { $rules = $matches[0]; } return $rules; } /** * Extract a `<code/>` content from a message. * * @since 1.0 * @author Grégory Viguier * * @param (array) $error An array where `message` is the key of our message. * @param (string) $class If not empty, the `<code>` tag with this specific html class will be searched. * * @return (string) The `<code/>` tag and its content, or an empty string if no `<code/>` tag is found. */ final protected static function get_code_tag_from_error( $error, $class = '' ) { $class = $class ? ' class="' . $class . '"' : ''; $tag = ''; if ( preg_match( '@<code' . $class . '>.+</code>@ms', $error['message'], $matches ) ) { $tag = $matches[0]; } return $tag; } /** * Multisite: tell if the "centralized blog options" are fully filled. * * @since 1.0 * @author Grégory Viguier * * @return (bool) True if our network options contain all blog options, or if it's not a multisite. False otherwise. */ final protected static function are_centralized_blog_options_filled() { if ( ! is_multisite() ) { return true; } $plugins = get_site_option( 'secupress_active_plugins' ); return is_array( $plugins ) && empty( $plugins['offset'] ); } /** * Filter every scan to bypass the scan and return "true" * * @param (string) $class The SecuPress class to be filtered. * * @return (bool) "false" by default (not modified), should be "true" to be used * @author Julio Potier **/ final protected function filter_scanner( $class ) { return ! is_null( apply_filters( 'secupress.pre_scan.' . $class, null ) ); } } free/classes/scanners/class-secupress-scan-easy-login.php 0000644 00000012612 15174670627 0017576 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Easy Login scan class. * * @package SecuPress * @subpackage SecuPress_Scan * @since 1.0 */ class SecuPress_Scan_Easy_Login extends SecuPress_Scan implements SecuPress_Scan_Interface { /** Constants. ============================================================================== */ /** * Class version. * * @var (string) */ const VERSION = '1.2'; /** Properties. ============================================================================= */ /** * The reference to the *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** * Tells if a scanner is fixable by SecuPress. The value "pro" means it's fixable only with the version PRO. * * @var (bool|string) */ protected $fixable = 'pro'; /** Init and messages. ====================================================================== */ /** * Init. * * @since 1.0 */ protected function init() { $this->title = __( 'Check if your login page is protected by double authentication.', 'secupress' ); $this->more = __( 'The login vector is often use in web attacks, every hour your website is the target of random bots whom try to log in your site. Adding another layer of login can improve the security.', 'secupress' ); $this->more_fix = sprintf( __( 'Activate the <strong>%1$s</strong> from the module %2$s.', 'secupress' ), __( 'PasswordLess Double Authentication', 'secupress' ), '<a href="' . esc_url( secupress_admin_url( 'modules', 'users-login' ) ) . '#row-double-auth_type">' . __( 'Users & Login', 'secupress' ) . '</a>' ); } /** * Get messages. * * @since 1.0 * * @param (int) $message_id A message ID. * * @return (string|array) A message if a message ID is provided. An array containing all messages otherwise. */ public static function get_messages( $message_id = null ) { $messages = array( // "good" 0 => __( 'The login page is protected by double authentication with %s.', 'secupress' ), 1 => __( 'The <strong>PasswordLess Double Authentication</strong> module has been activated for every role. Users will receive an email to log-in now.', 'secupress' ), 9 => __( 'The login page is protected by double authentication.', 'secupress' ), // "bad" 200 => __( 'Your login system is <strong>not strong enough</strong>, you need a <strong>double authentication system</strong>.', 'secupress' ), 201 => sprintf( __( 'Our module %s could fix this.', 'secupress' ), '<a href="' . esc_url( secupress_admin_url( 'modules', 'users-login' ) ) . '#row-double-auth_type">' . __( 'Two-Factor Authentication', 'secupress' ) . '</a>' ), ); if ( isset( $message_id ) ) { return isset( $messages[ $message_id ] ) ? $messages[ $message_id ] : __( 'Unknown message', 'secupress' ); } return $messages; } /** Getters. ================================================================================ */ /** * Get the documentation URL. * * @since 1.2.3 * * @return (string) */ public static function get_docs_url() { return __( 'https://docs.secupress.me/article/128-two-factor-authentication-scan', 'secupress' ); } /** Scan. =================================================================================== */ /** * Scan for flaw(s). * * @since 1.0 * * @return (array) The scan results. */ public function scan() { $activated = $this->filter_scanner( __CLASS__ ); if ( true === $activated ) { $this->add_message( 9 ); return parent::scan(); } $activated = secupress_is_submodule_active( 'users-login', 'passwordless' ) ? 'PasswordLess' : false; $activated = secupress_is_submodule_active( 'users-login', 'otp-auth' ) ? __( 'Two-Factor Authentication', 'secupress' ) : $activated; /** * Overwrite the activated bool to force a good information * * @since 1.4 * * @param (bool|string) False if not activated, string containing the plugin/system name than activate the feature */ $activated = apply_filters( 'secupress.scan.' . __CLASS__ . '.activated', $activated ); if ( ! $activated ) { // "bad" $this->add_message( 200 ); $this->add_pre_fix_message( 201 ); } else { // "good" $this->add_message( 0, array( '<strong>' . $activated . '</strong>' ) ); } return parent::scan(); } /** Fix. ==================================================================================== */ /** * Try to fix the flaw(s). * * @since 1.4.5 * * @return (array) The fix results. */ public function need_manual_fix() { return [ 'fix' => 'fix' ]; } /** * Get an array containing ALL the forms that would fix the scan if it requires user action. * * @since 1.4.5 * * @return (array) An array of HTML templates (form contents most of the time). */ protected function get_fix_action_template_parts() { return [ 'fix' => ' ' ]; } /** * Try to fix the flaw(s) after requiring user action. * * @since 1.4.5 * * @return (array) The fix results. */ public function manual_fix() { if ( $this->has_fix_action_part( 'fix' ) ) { $this->fix(); } // "good" $this->add_fix_message( 1 ); return parent::manual_fix(); } /** * Try to fix the flaw(s). * * @since 1.0 * * @return (array) The fix results. */ public function fix() { secupress_activate_submodule( 'users-login', 'passwordless' ); // "good" $this->add_fix_message( 1 ); return parent::fix(); } } free/classes/scanners/class-secupress-scan-readme-discloses.php 0000644 00000024664 15174670627 0020764 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * `readme.txt` disclose scan class. * * @package SecuPress * @subpackage SecuPress_Scan * @since 1.0 */ class SecuPress_Scan_Readme_Discloses extends SecuPress_Scan implements SecuPress_Scan_Interface { /** Constants. ============================================================================== */ /** * Class version. * * @var (string) */ const VERSION = '1.2'; /** Properties. ============================================================================= */ /** * The reference to the *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** Init and messages. ====================================================================== */ /** * Init. * * @since 1.0 */ protected function init() { global $is_apache, $is_nginx, $is_iis7; /** Translators: %s is a file name */ $this->title = sprintf( __( 'Check if the %s files from your plugins and themes are protected.', 'secupress' ), '<code>readme.txt</code>' ); $this->more = __( 'When an attacker wants to hack into a WordPress site, they will search for all available informations. The goal is to find something useful that will help him penetrate your site. Don’t let them easily find any informations.', 'secupress' ); $this->more_fix = sprintf( __( 'Activate the %1$s protection from the module %2$s.', 'secupress' ), '<strong>' . __( 'Protect Readme Files', 'secupress' ) . '</strong>', '<a href="' . esc_url( secupress_admin_url( 'modules', 'sensitive-data' ) ) . '#row-content-protect_readmes">' . __( 'Sensitive Data', 'secupress' ) . '</a>' ); if ( ! $is_apache && ! $is_nginx && ! $is_iis7 ) { $this->more_fix = static::get_messages( 301 ); $this->fixable = false; return; } } /** * Get messages. * * @since 1.0 * * @param (int) $message_id A message ID. * * @return (string|array) A message if a message ID is provided. An array containing all messages otherwise. */ public static function get_messages( $message_id = null ) { global $is_apache; $config_file = $is_apache ? '.htaccess' : 'web.config'; /** Translators: 1 is the name of a protection, 2 is the name of a module. */ $activate_protection_message = sprintf( __( 'But you can activate the %1$s protection from the module %2$s.', 'secupress' ), '<strong>' . __( 'Protect Readme Files', 'secupress' ) . '</strong>', '<a target="_blank" href="' . esc_url( secupress_admin_url( 'modules', 'sensitive-data' ) ) . '#row-content-protect_readmes">' . __( 'Sensitive Data', 'secupress' ) . '</a>' ); $messages = array( // "good" /** Translators: %s is a file name. */ 0 => sprintf( __( 'The %s files are protected from revealing sensitive information.', 'secupress' ), '<code>readme.txt</code>' ), /** Translators: 1 and 2 are file names. */ 1 => sprintf( __( 'The rules forbidding access to your %1$s files have been successfully added to your %2$s file.', 'secupress' ), '<code>readme.txt</code>', "<code>$config_file</code>" ), // "warning" /** Translators: %s is a file name. */ 100 => sprintf( __( 'Unable to determine the status of the %s files that may reveal sensitive information.', 'secupress' ), '<code>readme.txt</code>' ) . ' ' . $activate_protection_message, // "bad" /** Translators: 1 and 2 are file names. */ 200 => sprintf( __( 'The %1$s and/or %2$s files should not be accessible to anyone because they are revealing sensitive information.', 'secupress' ), '<code>readme.txt/md/html</code>', '<code>changelog.txt/md/html</code>' ), // "cantfix" /** Translators: 1 and 2 are a file names, 3 is some code. */ 300 => sprintf( __( 'Your server runs <strong>Nginx</strong>, the %1$s files cannot be protected automatically but you can do it yourself by adding the following code to your %2$s file: %3$s', 'secupress' ), '<code>readme.txt</code>', '<code>nginx.conf</code>', '%s' ), /** Translators: %s is a file name. */ 301 => sprintf( __( 'Your server runs an unrecognized system. The %s files cannot be protected automatically.', 'secupress' ), '<code>readme.txt</code>' ), /** Translators: 1 is a file name, 2 is some code. */ 302 => sprintf( __( 'Your %1$s file is not writable. Please add the following lines at the beginning of the file: %2$s', 'secupress' ), "<code>$config_file</code>", '%s' ), /** Translators: 1 is a file name, 2 is a folder path (kind of), 3 is some code. */ 303 => sprintf( __( 'Your %1$s file is not writable. Please add the following lines inside the tags hierarchy %2$s (create it if does not exist): %3$s', 'secupress' ), "<code>$config_file</code>", '%1$s', '%2$s' ), ); if ( isset( $message_id ) ) { return isset( $messages[ $message_id ] ) ? $messages[ $message_id ] : __( 'Unknown message', 'secupress' ); } return $messages; } /** Getters. ================================================================================ */ /** * Get the documentation URL. * * @since 1.2.3 * * @return (string) */ public static function get_docs_url() { return __( 'https://docs.secupress.me/article/102-readme-txt-access-scan', 'secupress' ); } /** Scan. =================================================================================== */ /** * Scan for flaw(s). * * @since 1.0 * * @return (array) The scan results. */ public function scan() { $activated = $this->filter_scanner( __CLASS__ ); if ( true === $activated ) { $this->add_message( 0 ); return parent::scan(); } $protected = $this->are_files_protected(); if ( ! $protected ) { // "bad" $this->add_message( 200 ); if ( ! $this->fixable ) { $this->add_pre_fix_message( 301 ); } } // "good" $this->maybe_set_status( 0 ); return parent::scan(); } /** Fix. ==================================================================================== */ /** * Try to fix the flaw(s). * * @since 1.4.5 * * @return (array) The fix results. */ public function need_manual_fix() { return [ 'fix' => 'fix' ]; } /** * Get an array containing ALL the forms that would fix the scan if it requires user action. * * @since 1.4.5 * * @return (array) An array of HTML templates (form contents most of the time). */ protected function get_fix_action_template_parts() { return [ 'fix' => ' ' ]; } /** * Try to fix the flaw(s) after requiring user action. * * @since 1.4.5 * * @return (array) The fix results. */ public function manual_fix() { if ( $this->has_fix_action_part( 'fix' ) ) { $this->fix(); } // "good" $this->add_fix_message( 1 ); return parent::manual_fix(); } /** * Try to fix the flaw(s). * * @since 1.0 * * @return (array) The fix results. */ public function fix() { global $is_apache, $is_nginx, $is_iis7; $protected = $this->are_files_protected(); if ( is_null( $protected ) ) { // "warning" $this->add_fix_message( 100 ); return parent::fix(); } if ( $protected ) { // "good" $this->add_fix_message( 0 ); return parent::fix(); } if ( $is_apache ) { $this->fix_apache(); } elseif ( $is_iis7 ) { $this->fix_iis7(); } elseif ( $is_nginx ) { $this->fix_nginx(); } // "good" $this->maybe_set_fix_status( 0 ); return parent::fix(); } /** * Fix for Apache system. * * @since 1.0 */ protected function fix_apache() { global $wp_settings_errors; secupress_activate_submodule( 'discloses', 'readmes' ); // Got error? $last_error = is_array( $wp_settings_errors ) && $wp_settings_errors ? end( $wp_settings_errors ) : false; if ( $last_error && 'general' === $last_error['setting'] && 'apache_manual_edit' === $last_error['code'] ) { $rules = static::get_rules_from_error( $last_error ); // "cantfix" $this->add_fix_message( 302, array( $rules ) ); array_pop( $wp_settings_errors ); return; } // "good" $this->add_fix_message( 1 ); } /** * Fix for IIS7 system. * * @since 1.0 */ protected function fix_iis7() { global $wp_settings_errors; secupress_activate_submodule( 'discloses', 'readmes' ); // Got error? $last_error = is_array( $wp_settings_errors ) && $wp_settings_errors ? end( $wp_settings_errors ) : false; if ( $last_error && 'general' === $last_error['setting'] && 'iis7_manual_edit' === $last_error['code'] ) { $rules = static::get_rules_from_error( $last_error ); $path = static::get_code_tag_from_error( $last_error, 'secupress-iis7-path' ); // "cantfix" $this->add_fix_message( 303, array( $path, $rules ) ); array_pop( $wp_settings_errors ); return; } // "good" $this->add_fix_message( 1 ); } /** * Fix for nginx system. * * @since 1.0 */ protected function fix_nginx() { global $wp_settings_errors; secupress_activate_submodule( 'discloses', 'readmes' ); // Get the error. $last_error = is_array( $wp_settings_errors ) && $wp_settings_errors ? end( $wp_settings_errors ) : false; $rules = '<code>Error</code>'; if ( $last_error && 'general' === $last_error['setting'] && 'nginx_manual_edit' === $last_error['code'] ) { $rules = static::get_rules_from_error( $last_error ); array_pop( $wp_settings_errors ); } // "cantfix" $this->add_fix_message( 300, array( $rules ) ); } /** Tools. ================================================================================== */ /** * Tells if the readme files are accessible. * * @since 1.0 * * @return (bool) */ protected function are_files_protected() { // Get all readme/changelog files. $plugins = rtrim( secupress_get_plugins_path(), '\\/' ); $themes = rtrim( secupress_get_themes_path(), '\\/' ); $pattern = '{' . $plugins . ',' . $themes . '}/*/{README,CHANGELOG,readme,changelog}.{TXT,MD,HTML,txt,md,html}'; $files = glob( $pattern, GLOB_BRACE ); // No file? Good, nothing to protect. if ( ! $files ) { // "good". return true; } // Get the first file path, relative to the root of the site. $abspath = wp_normalize_path( ABSPATH ); $file = reset( $files ); if ( isset( $files[1] ) && false !== strpos( $file, '/akismet/' ) ) { // Akismet protects its files. $file = $files[1]; } $file = wp_normalize_path( $file ); $file = ltrim( str_replace( $abspath, '', $file ), '/' ); // Get file contents. $response = wp_remote_get( site_url( $file ), $this->get_default_request_args() ); if ( ! is_wp_error( $response ) && 200 === wp_remote_retrieve_response_code( $response ) ) { // "bad". return false; } // "good". return true; } } free/classes/scanners/class-secupress-scan-sqli.php 0000644 00000011652 15174670627 0016502 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * SQLi scan class. * * @package SecuPress * @subpackage SecuPress_Scan * @since 1.0 */ class SecuPress_Scan_SQLi extends SecuPress_Scan implements SecuPress_Scan_Interface { /** Constants. ============================================================================== */ /** * Class version. * * @var (string) */ const VERSION = '1.2'; /** Properties. ============================================================================= */ /** * The reference to the *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** Init and messages. ====================================================================== */ /** * Init. * * @since 1.0 */ protected function init() { $this->title = __( 'Check if basic SQL Injections are blocked or not.', 'secupress' ); $this->more = __( 'SQL injection is a way to read, modify, delete any content of your database, this is a powerful vulnerability, don’t let anyone play with that.', 'secupress' ); $this->more_fix = sprintf( __( 'Activate the option %1$s in the %2$s module.', 'secupress' ), '<em>' . __( 'Block Bad Content', 'secupress' ) . '</em>', '<a href="' . esc_url( secupress_admin_url( 'modules', 'firewall' ) ) . '#row-bbq-url-content_bad-contents">' . __( 'Firewall', 'secupress' ) . '</a>' ); } /** * Get messages. * * @since 1.0 * * @param (int) $message_id A message ID. * * @return (string|array) A message if a message ID is provided. An array containing all messages otherwise. */ public static function get_messages( $message_id = null ) { /** Translators: 1 is the name of a protection, 2 is the name of a module. */ $activate_protection_message = sprintf( __( 'But you can activate the %1$s protection from the module %2$s.', 'secupress' ), '<strong>' . __( 'Block Bad Content', 'secupress' ) . '</strong>', '<a target="_blank" href="' . esc_url( secupress_admin_url( 'modules', 'firewall' ) ) . '#row-bbq-url-content_bad-contents">' . __( 'Firewall', 'secupress' ) . '</a>' ); $messages = array( // "good" 0 => __( 'You are currently blocking simple SQL Injection.', 'secupress' ), 1 => __( 'Protection activated', 'secupress' ), // "warning" 100 => __( 'Unable to determine if your homepage is blocking SQL Injection.', 'secupress' ) . ' ' . $activate_protection_message, // "bad" 200 => __( 'Your website should block <strong>SQL Injection</strong>.', 'secupress' ), ); if ( isset( $message_id ) ) { return isset( $messages[ $message_id ] ) ? $messages[ $message_id ] : __( 'Unknown message', 'secupress' ); } return $messages; } /** Getters. ================================================================================ */ /** * Get the documentation URL. * * @since 1.2.3 * * @return (string) */ public static function get_docs_url() { return __( 'https://docs.secupress.me/article/109-basic-sql-injection-scan', 'secupress' ); } /** Scan. =================================================================================== */ /** * Scan for flaw(s). * * @since 1.0 * * @return (array) The scan results. */ public function scan() { $activated = $this->filter_scanner( __CLASS__ ); if ( true === $activated ) { $this->add_message( 0 ); return parent::scan(); } $response = wp_remote_get( add_query_arg( secupress_generate_key( 6 ), 'UNION%20SELECT%20FOO', user_trailingslashit( home_url() ) ), $this->get_default_request_args() ); if ( ! is_wp_error( $response ) ) { if ( 200 === wp_remote_retrieve_response_code( $response ) ) { // "bad" $this->add_message( 200 ); } else { // "good" $this->add_message( 0 ); } } // Good. $this->maybe_set_status( 0 ); return parent::scan(); } /** Fix. ==================================================================================== */ /** * Try to fix the flaw(s). * * @since 1.4.5 * * @return (array) The fix results. */ public function need_manual_fix() { return [ 'fix' => 'fix' ]; } /** * Get an array containing ALL the forms that would fix the scan if it requires user action. * * @since 1.4.5 * * @return (array) An array of HTML templates (form contents most of the time). */ protected function get_fix_action_template_parts() { return [ 'fix' => ' ' ]; } /** * Try to fix the flaw(s) after requiring user action. * * @since 1.4.5 * * @return (array) The fix results. */ public function manual_fix() { if ( $this->has_fix_action_part( 'fix' ) ) { $this->fix(); } // "good" $this->add_fix_message( 1 ); return parent::manual_fix(); } /** * Try to fix the flaw(s). * * @since 1.0 * * @return (array) The fix results. */ public function fix() { // Activate. secupress_activate_submodule( 'firewall', 'bad-url-contents' ); // "good" $this->add_fix_message( 1 ); return parent::fix(); } } free/classes/scanners/class-secupress-scan-themes-update.php 0000644 00000015636 15174670627 0020305 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Themes Update scan class. * * @package SecuPress * @subpackage SecuPress_Scan * @since 1.0 */ class SecuPress_Scan_Themes_Update extends SecuPress_Scan implements SecuPress_Scan_Interface { /** Constants. ============================================================================== */ /** * Class version. * * @var (string) */ const VERSION = '1.2'; /** Properties. ============================================================================= */ /** * The reference to the *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** * Tells if the fix must occur after all other scans and fixes, while no other scan/fix is running. * * @var (bool) */ protected $delayed_fix = true; /** Init and messages. ====================================================================== */ /** * Init. * * @since 1.0 */ protected function init() { $this->title = __( 'Check if your themes are up to date.', 'secupress' ); $this->more = __( 'It is very important to keep your WordPress installation up to date. If you cannot update because of a theme, contact its author and submit your issue.', 'secupress' ); $this->more_fix = __( 'Update all your themes that are not up to date.', 'secupress' ); } /** * Get messages. * * @since 1.0 * * @param (int) $message_id A message ID. * * @return (string|array) A message if a message ID is provided. An array containing all messages otherwise. */ public static function get_messages( $message_id = null ) { $messages = array( // "good" 0 => __( 'Your themes are up to date.', 'secupress' ), // "warning" 100 => _n_noop( '<strong>%d symlinked theme</strong> is not up to date, and cannot be updated automatically.', '<strong>%d symlinked themes</strong> are not up to date, and cannot be updated automatically.', 'secupress' ), // "bad" 200 => _n_noop( '<strong>%1$d theme</strong> is not up to date: %2$s.', '<strong>%1$d themes</strong> are not up to date: %2$s.', 'secupress' ), // "cantfix" 300 => __( 'Some themes could not be updated correctly.', 'secupress' ), 301 => _n_noop( '<strong>%d symlinked theme</strong> is not up to date, and cannot be updated automatically.', '<strong>%d symlinked themes</strong> are not up to date, and cannot be updated automatically.', 'secupress' ), ); if ( isset( $message_id ) ) { return isset( $messages[ $message_id ] ) ? $messages[ $message_id ] : __( 'Unknown message', 'secupress' ); } return $messages; } /** Getters. ================================================================================ */ /** * Get the documentation URL. * * @since 1.2.3 * * @return (string) */ public static function get_docs_url() { return __( 'https://docs.secupress.me/article/119-theme-update-scan', 'secupress' ); } /** Scan. =================================================================================== */ /** * Scan for flaw(s). * * @since 1.0 * * @return (array) The scan results. */ public function scan() { $activated = $this->filter_scanner( __CLASS__ ); if ( true === $activated ) { $this->add_message( 0 ); return parent::scan(); } ob_start(); wp_update_themes(); $themes = get_site_transient( 'update_themes' ); $themes = ! empty( $themes->response ) && is_array( $themes->response ) ? array_keys( $themes->response ) : array(); $symlinked_themes = array(); if ( $themes ) { $symlinked_themes = array_filter( $themes, 'secupress_is_theme_symlinked' ); $themes = array_diff( $themes, $symlinked_themes ); $themes = array_flip( $themes ); $themes = array_intersect_key( wp_get_themes(), $themes ); $themes = wp_list_pluck( $themes, 'Name' ); } ob_flush(); if ( $count = count( $themes ) ) { // "bad" $this->add_message( 200, array( $count, $count, self::wrap_in_tag( $themes ) ) ); } if ( $count = count( $symlinked_themes ) ) { // "warning" $this->add_message( 100, array( $count, $count ) ); } // "good" $this->maybe_set_status( 0 ); return parent::scan(); } /** Fix. ==================================================================================== */ /** * Try to fix the flaw(s). * * @since 1.4.5 * * @return (array) The fix results. */ public function need_manual_fix() { return [ 'fix' => 'fix' ]; } /** * Get an array containing ALL the forms that would fix the scan if it requires user action. * * @since 1.4.5 * * @return (array) An array of HTML templates (form contents most of the time). */ protected function get_fix_action_template_parts() { return [ 'fix' => ' ' ]; } /** * Try to fix the flaw(s) after requiring user action. * * @since 1.4.5 * * @return (array) The fix results. */ public function manual_fix() { if ( $this->has_fix_action_part( 'fix' ) ) { $this->fix(); } // "good" $this->add_fix_message( 1 ); return parent::manual_fix(); } /** * Try to fix the flaw(s). * * @since 1.0 * * @return (array) The fix results. */ public function fix() { $themes = get_site_transient( 'update_themes' ); $themes = ! empty( $themes->response ) && is_array( $themes->response ) ? array_keys( $themes->response ) : array(); if ( $themes ) { $symlinked_themes = array_filter( $themes, 'secupress_is_theme_symlinked' ); $themes = array_diff( $themes, $symlinked_themes ); } if ( $themes ) { ob_start(); secupress_time_limit( 0 ); // Remove the WP upgrade process for translation since it will output data, use our own based on core but using a silent upgrade. remove_action( 'upgrader_process_complete', array( 'Language_Pack_Upgrader', 'async_upgrade' ), 20 ); add_action( 'upgrader_process_complete', 'secupress_async_upgrades', 20 ); include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' ); $nonce = 'bulk-update-themes'; $url = implode( ',', $themes ); $url = 'update.php?action=update-selected-themes&themes=' . urlencode( $url ); $skin = new Automatic_Upgrader_Skin( array( 'nonce' => $nonce, 'url' => $url ) ); $upgrader = new Theme_Upgrader( $skin ); $upgrader->bulk_upgrade( $themes ); ob_end_clean(); } // Test if we succeeded. $themes = get_site_transient( 'update_themes' ); $themes = ! empty( $themes->response ) && is_array( $themes->response ) ? array_keys( $themes->response ) : array(); if ( ! $themes ) { // "good" $this->add_fix_message( 0 ); } else { $symlinked_themes = array_filter( $themes, 'secupress_is_theme_symlinked' ); $themes = array_diff( $themes, $symlinked_themes ); if ( $count = count( $symlinked_themes ) ) { // "cantfix" $this->add_fix_message( 301, array( $count, $count ) ); } else { // "cantfix" $this->add_fix_message( 300 ); } } return parent::fix(); } } free/classes/scanners/class-secupress-scan-wporg.php 0000644 00000010244 15174670627 0016664 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * WPOrg scan class. * * @package SecuPress * @subpackage SecuPress_Scan * @since 1.0 */ class SecuPress_Scan_WPOrg extends SecuPress_Scan implements SecuPress_Scan_Interface { /** Constants. ============================================================================== */ /** * Class version. * * @var (string) */ const VERSION = '1.0'; /** Properties. ============================================================================= */ /** * The reference to the *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** * Tells if a scanner is fixable by SecuPress. The value "pro" means it's fixable only with the version PRO. * * @var (bool|string) */ protected $fixable = false; /** Init and messages. ====================================================================== */ /** * Init. * * @since 1.0 */ protected function init() { $this->title = __( 'Check if your installation can communicate with WordPress.org.', 'secupress' ); $this->more = __( 'Communicating with the WordPress servers is used to check for new versions, and to both install and update WordPress core, themes or plugins.' ); $this->more_fix = static::get_messages( 300 ); } /** * Get messages. * * @since 1.0 * * @param (int) $message_id A message ID. * * @return (string|array) A message if a message ID is provided. An array containing all messages otherwise. */ public static function get_messages( $message_id = null ) { /** * Filter the .org API text incase of... * @since 2.2.6 * @param (string) $dot_org */ $dot_org = apply_filters( 'secupress.dot_org.text', 'WordPress.org' ); /** * Filter the .org API support link incase of... * @since 2.2.6 * @param (string) $support */ $support = apply_filters( 'secupress.dot_org.support_link', __( 'https://wordpress.org/support', 'secupress' ) ); $messages = array( // "good" 0 => sprintf( __( 'Your site can communicate with %s.', 'secupress' ), $dot_org ), // "bad" 200 => sprintf( __( 'Your site could not reach WordPress.org.', 'secupress' ), $dot_org ), // "cantfix" 300 => sprintf( '<p><a href="%s" target="_blank" rel="noopener noreferrer">%s <span class="screen-reader-text">%s</span><span aria-hidden="true" class="dashicons dashicons-external"></span></a></p>', /* translators: Localized Support reference. */ esc_url( $support ), __( 'Get help resolving this issue.', 'secupress' ), /* translators: accessibility text */ __( '(opens in a new tab)' ) ) ); if ( isset( $message_id ) ) { return isset( $messages[ $message_id ] ) ? $messages[ $message_id ] : __( 'Unknown message', 'secupress' ); } return $messages; } /** Getters. ================================================================================ */ /** * Get the documentation URL. * * @since 1.2.3 * * @return (string) */ public static function get_docs_url() { return __( 'https://docs.secupress.me/article/177-communicate-with-wordpress-org-or-secupress-me', 'secupress' ); } /** Scan. =================================================================================== */ /** * Scan for flaw(s). * * @since 1.0 * * @return (array) The scan results. */ public function scan() { $activated = $this->filter_scanner( __CLASS__ ); if ( true === $activated ) { $this->add_message( 0 ); return parent::scan(); } /** * Filter the .org API incase of... * @since 2.2.6 * @param (string) $dot_org_url */ $dot_org_url = apply_filters( 'secupress.dot_org.url', 'https://api.wordpress.org' ); $wp_dotorg = wp_remote_get( $dot_org_url, $this->get_default_request_args() ); if ( ! is_wp_error( $wp_dotorg ) ) { $this->add_message( 0 ); } else { $this->add_message( 200 ); } return parent::scan(); } /** Fix. ==================================================================================== */ /** * Try to fix the flaw(s). * * @since 1.0 * * @return (array) The fix results. */ public function fix() { $this->add_fix_message( 300 ); return parent::fix(); } } free/classes/scanners/class-secupress-scan-chmods.php 0000644 00000025133 15174670627 0017006 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Chmods scan class. * * @package SecuPress * @subpackage SecuPress_Scan * @since 1.0 */ class SecuPress_Scan_Chmods extends SecuPress_Scan implements SecuPress_Scan_Interface { /** * Reminder: * * `$perm = fileperms( $file );` * * WHAT | TYPE | FILE | FOLDER | * ----------------------------------------------+--------+--------+--------| * `$perm` | int | 33188 | 16877 | * `substr( decoct( $perm ), -4 )` | string | '0644' | '0755' | * `substr( sprintf( '%o', $perm ), -4 )` | string | '0644' | '0755' | * `$perm & 0777` | int | 420 | 493 | * `decoct( $perm & 0777 )` | string | '644' | '755' | * `substr( sprintf( '%o', $perm & 0777 ), -4 )` | string | '644' | '755' | */ /** Constants. ============================================================================== */ /** * Class version. * * @var (string) */ const VERSION = '2.0'; /** Properties. ============================================================================= */ /** * Targeted chmod for folders. * * @var (int) Integer value of 0755. */ protected $folder_chmod = 493; /** * Targeted chmod for files. * * @var (int) Integer value of 0644. */ protected $file_chmod = 420; /** * The reference to the *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** Init and messages. ====================================================================== */ /** * Init. * * @since 1.0 */ protected function init() { $this->title = __( 'Check if your files and folders have the correct write permissions (chmod).', 'secupress' ); $this->more = __( 'CHMOD is the way to give read/write/execute rights to a file or a folder. This test will check some strategic files and folders.', 'secupress' ); $this->more_fix = __( 'Change the files permissions to the recommended one for each.', 'secupress' ); } /** * Get messages. * * @since 1.0 * * @param (int) $message_id A message ID. * * @return (string|array) A message if a message ID is provided. An array containing all messages otherwise. */ public static function get_messages( $message_id = null ) { $messages = array( // "good" 0 => __( 'All file permissions are good.', 'secupress' ), 1 => __( 'All file permissions are fixed.', 'secupress' ), // "warning" 100 => __( 'Unable to determine the file permissions of %s.', 'secupress' ), // "bad" 202 => _nx_noop( 'File permissions for the following folder <strong>should be %1$s</strong>: %2$s.', 'File permissions for the following folders <strong>should be %1$s</strong>: %2$s.', '1: chmod required, 2: folder path(s)', 'secupress' ), 203 => _nx_noop( 'File permissions for the following file <strong>should be %1$s</strong>: %2$s.', 'File permissions for the following files <strong>should be %1$s</strong>: %2$s.', '1: chmod required, 2: folder path(s)', 'secupress' ), 204 => _n_noop( 'Unable to apply new permissions to the file or folder %s.', 'Unable to apply new permissions to the files or folders %s.', 'secupress' ), ); if ( isset( $message_id ) ) { return isset( $messages[ $message_id ] ) ? $messages[ $message_id ] : __( 'Unknown message', 'secupress' ); } return $messages; } /** Getters. ================================================================================ */ /** * Get the documentation URL. * * @since 1.2.3 * * @return (string) */ public static function get_docs_url() { return __( 'https://docs.secupress.me/article/125-file-permission-scan', 'secupress' ); } /** Scan. =================================================================================== */ /** * Scan for flaw(s). * * @since 1.0 * * @return (array) The scan results. */ public function scan() { $activated = $this->filter_scanner( __CLASS__ ); if ( true === $activated ) { $this->add_message( 0 ); return parent::scan(); } $warnings = array(); $folders = array(); $files = array(); $to_test = $this->get_files(); $abspath = realpath( ABSPATH ); clearstatcache(); // Folders. foreach ( $to_test['folders'] as $file_path ) { // Current folder perm. $file_path = realpath( $file_path ); $current_chmod = fileperms( $file_path ) & 0777; $file_relative = ltrim( str_replace( $abspath, '', $file_path ), '\\' ); $file_relative = '' === $file_relative ? '/' : $file_relative; if ( ! $current_chmod ) { // "warning": unable to determine folder perm. $warnings[] = sprintf( '<code>%s</code>', $file_relative ); } elseif ( $current_chmod > $this->folder_chmod ) { // "bad". $folders[] = sprintf( '<code>%s</code> (<code>%s</code>)', $file_relative, static::to_octal( $current_chmod ) ); } } // Files. if ( $to_test['files'] ) { foreach ( $to_test['files'] as $file_path ) { // Current file perm. $file_path = realpath( $file_path ); $current_chmod = fileperms( $file_path ) & 0777; $file_relative = ltrim( str_replace( $abspath, '', $file_path ), '\\' ); $file_relative = '' === $file_relative ? '/' : $file_relative; if ( ! $current_chmod ) { // "warning": unable to determine file perm. $warnings[] = sprintf( '<code>%s</code>', $file_relative ); } elseif ( $current_chmod > $this->file_chmod ) { // "bad". $files[] = sprintf( '<code>%s</code> (<code>%s</code>)', $file_relative, static::to_octal( $current_chmod ) ); } } } if ( ! empty( $folders ) ) { // "bad" $this->add_message( 202, array( count( $folders ), sprintf( '<code>%s</code>', static::to_octal( $this->folder_chmod ) ), $folders ) ); } if ( ! empty( $files ) ) { // "bad" $this->add_message( 203, array( count( $files ), sprintf( '<code>%s</code>', static::to_octal( $this->file_chmod ) ), $files ) ); } if ( ! empty( $warnings ) ) { // "warning" $this->add_message( 100, array( $warnings ) ); } // "good" $this->maybe_set_status( 0 ); return parent::scan(); } /** Fix. ==================================================================================== */ /** * Try to fix the flaw(s). * * @since 1.4.5 * * @return (array) The fix results. */ public function need_manual_fix() { return [ 'fix' => 'fix' ]; } /** * Get an array containing ALL the forms that would fix the scan if it requires user action. * * @since 1.4.5 * * @return (array) An array of HTML templates (form contents most of the time). */ protected function get_fix_action_template_parts() { return [ 'fix' => ' ' ]; } /** * Try to fix the flaw(s) after requiring user action. * * @since 1.4.5 * * @return (array) The fix results. */ public function manual_fix() { if ( $this->has_fix_action_part( 'fix' ) ) { $this->fix(); } // "good" $this->add_fix_message( 1 ); return parent::manual_fix(); } /** * Try to fix the flaw(s). * * @since 1.0 * * @return (array) The fix results. */ public function fix() { $warnings = array(); $files = array(); $failed = array(); $to_test = $this->get_files(); $abspath = realpath( ABSPATH ); $filesystem = secupress_get_filesystem(); clearstatcache(); // Folders. foreach ( $to_test['folders'] as $file_path ) { // Current folder perm. $current_chmod = fileperms( $file_path ) & 0777; if ( ! $current_chmod || $current_chmod > $this->folder_chmod ) { // Apply new folder perm. $filesystem->chmod( $file_path, $this->folder_chmod ); $files[ $file_path ] = $this->folder_chmod; } } // Files. if ( $to_test['files'] ) { foreach ( $to_test['files'] as $file_path ) { // Current file perm. $current_chmod = @fileperms( $file_path ) & 0777; if ( ! $current_chmod || $current_chmod > $this->file_chmod ) { // Apply new file perm. @$filesystem->chmod( $file_path, $this->file_chmod ); $files[ $file_path ] = $this->file_chmod; } } } // Check if it worked. clearstatcache(); if ( ! $files ) { // "good" (there was nothing to fix). $this->add_fix_message( 0 ); return parent::fix(); } if ( $files ) { foreach ( $files as $file_path => $target_chmod ) { // Current file perm. $file_path = realpath( $file_path ); $current_chmod = fileperms( $file_path ) & 0777; $file_relative = ltrim( str_replace( $abspath, '', $file_path ), '\\' ); $file_relative = '' === $file_relative ? '/' : $file_relative; if ( ! $current_chmod ) { // "warning": unable to determine file perm. $warnings[] = sprintf( '<code>%s</code>', $file_relative ); } elseif ( $current_chmod > $target_chmod ) { // "bad": unable to apply the file perm. $failed[] = sprintf( '<code>%s</code> (<code>%s</code>)', $file_relative, static::to_octal( $target_chmod ) ); } } } if ( $failed ) { // "bad" $this->add_fix_message( 204, array( count( $failed ), $failed ) ); } if ( $warnings ) { // "warning" $this->add_fix_message( 100, array( $warnings ) ); } // "good" $this->maybe_set_fix_status( 1 ); return parent::fix(); } /** Tools. ================================================================================== */ /** * Get files and folders to test, categorized by type. * * @since 2.0 remove web.config+ better .htaccess path * @since 1.2.2 * * @return (array) An array of arrays of paths. */ protected function get_files() { $upload_dir = wp_upload_dir(); $files = array( 'folders' => array( ABSPATH, ABSPATH . 'wp-admin', ABSPATH . WPINC, WP_CONTENT_DIR, get_theme_root(), WP_PLUGIN_DIR, $upload_dir['basedir'], ), 'files' => array(), ); if ( file_exists( WPMU_PLUGIN_DIR ) ) { $files['folders'][] = WPMU_PLUGIN_DIR; } $files['folders'] = array_map( 'trailingslashit', $files['folders'] ); $wpconfig_filepath = secupress_find_wpconfig_path(); if ( file_exists( $wpconfig_filepath ) ) { $files['files'][] = $wpconfig_filepath; } $htaccess_path = trailingslashit( secupress_get_home_path() ); $htaccess_file = $htaccess_path . '.htaccess'; if ( file_exists( $htaccess_file ) ) { $files['files'][] = $htaccess_file; } return $files; } /** * Transform an "octal" integer to a "readable" string like "0644". * * @since 1.2.2 * * @param (int) $int An "octal" integer. * * @return (string). */ protected static function to_octal( $int ) { return substr( '0' . decoct( $int ), -4 ); } } free/classes/scanners/class-secupress-scan-inactive-plugins-themes.php 0000644 00000052115 15174670627 0022275 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Inactive Plugins Themes scan class. * * @package SecuPress * @subpackage SecuPress_Scan * @since 1.0 */ class SecuPress_Scan_Inactive_Plugins_Themes extends SecuPress_Scan implements SecuPress_Scan_Interface { /** Constants. ============================================================================== */ /** * Class version. * * @var (string) */ const VERSION = '1.0.1'; /** Properties. ============================================================================= */ /** * The reference to the *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** Init and messages. ====================================================================== */ /** * Init. * * @since 1.0 */ protected function init() { $this->title = __( 'Check if you have some deactivated plugins or themes.', 'secupress' ); $this->more = __( 'Even deactivated plugins or themes can potentially be exploited to some vulnerabilities. Don’t take the risk to keep them on your website.', 'secupress' ); $this->more_fix = __( 'Delete every inactive plugin and theme you have.', 'secupress' ); } /** * Get messages. * * @since 1.0 * * @param (int) $message_id A message ID. * * @return (string|array) A message if a message ID is provided. An array containing all messages otherwise. */ public static function get_messages( $message_id = null ) { $messages = array( // "good" 0 => __( 'You don’t have any deactivated plugins or themes.', 'secupress' ), 1 => __( 'All inactive plugins have been deleted.', 'secupress' ), 2 => __( 'All inactive themes have been deleted.', 'secupress' ), // "warning" 100 => __( 'No plugins selected.', 'secupress' ), 101 => __( 'All selected plugins have been deleted (but some are still there).', 'secupress' ), 102 => _n_noop( 'Sorry, the following plugin could not be deleted: %s.', 'Sorry, the following plugins could not be deleted: %s.', 'secupress' ), 103 => __( 'No themes selected.', 'secupress' ), 104 => __( 'All selected themes have been deleted (but some are still there).', 'secupress' ), 105 => _n_noop( 'Sorry, the following theme could not be deleted: %s.', 'Sorry, the following themes could not be deleted: %s.', 'secupress' ), /** Translators: %s is the plugin name. */ 106 => sprintf( __( 'You have a big network, %s must work on some data before being able to perform this scan.', 'secupress' ), '<strong>' . SECUPRESS_PLUGIN_NAME . '</strong>' ), // "bad" 200 => _n_noop( '<strong>%1$d deactivated plugin</strong>, if you don’t need it, delete it: %2$s.', '<strong>%1$d deactivated plugins</strong>, if you don’t need them, delete them: %2$s.', 'secupress' ), 201 => _n_noop( '<strong>%1$d deactivated theme</strong>, if you don’t need it, delete it: %2$s.', '<strong>%1$d deactivated themes</strong>, if you don’t need them, delete them: %2$s.', 'secupress' ), 202 => _n_noop( 'Sorry, this plugin could not be deleted.', 'Sorry, those plugins could not be deleted.', 'secupress' ), 203 => _n_noop( 'Sorry, this theme could not be deleted.', 'Sorry, those themes could not be deleted.', 'secupress' ), // "cantfix" 300 => _n_noop( '%d plugin is deactivated.', '%d plugins are deactivated.', 'secupress' ), 301 => _n_noop( '%d theme is deactivated.', '%d themes are deactivated.', 'secupress' ), 302 => __( 'Unable to locate WordPress Plugin directory.', 'secupress' ), 303 => __( 'Unable to locate WordPress theme directory.', 'secupress' ), 304 => __( 'No plugins nor themes selected.', 'secupress' ), ); if ( isset( $message_id ) ) { return isset( $messages[ $message_id ] ) ? $messages[ $message_id ] : __( 'Unknown message', 'secupress' ); } return $messages; } /** Getters. ================================================================================ */ /** * Get the documentation URL. * * @since 1.2.3 * * @return (string) */ public static function get_docs_url() { return __( 'https://docs.secupress.me/article/118-deactivated-plugins-and-themes-scan', 'secupress' ); } /** Scan. =================================================================================== */ /** * Scan for flaw(s). * * @since 1.0 * * @return (array) The scan results. */ public function scan() { $activated = $this->filter_scanner( __CLASS__ ); if ( true === $activated ) { $this->add_message( 0 ); return parent::scan(); } if ( ! static::are_centralized_blog_options_filled() ) { // "warning" $this->add_message( 106 ); return parent::scan(); } $lists = static::get_inactive_plugins_and_themes(); // Inactive plugins. if ( $count = count( $lists['plugins'] ) ) { // "bad" $lists['plugins'] = wp_list_pluck( $lists['plugins'], 'Name' ); // Do not translate 'Name'. $lists['plugins'] = self::wrap_in_tag( $lists['plugins'], 'code' ); $this->slice_and_dice( $lists['plugins'], 8 ); $this->add_message( 200, array( $count, $count, $lists['plugins'] ) ); } // Inactive themes. if ( $count = count( $lists['themes'] ) ) { // "bad" foreach ( $lists['themes'] as $key => $theme ) { $lists['themes'][ $key ] = '<code>' . $theme->display( 'Name', false, true ) . '</code>'; } $this->slice_and_dice( $lists['themes'], 8 ); $this->add_message( 201, array( $count, $count, $lists['themes'] ) ); } // "good" $this->maybe_set_status( 0 ); return parent::scan(); } /** Fix. ==================================================================================== */ /** * Try to fix the flaw(s). * * @since 1.0 * * @return (array) The fix results. */ public function fix() { // "good" $this->add_fix_message( 0 ); return parent::fix(); } /** Manual fix. ============================================================================= */ /** * Return an array of actions if a manual fix is needed here. * * @since 1.0 * * @return (array) */ public function need_manual_fix() { $lists = static::get_inactive_plugins_and_themes(); $actions = array(); // Inactive plugins. if ( $lists['plugins'] ) { $actions['delete-inactive-plugins'] = 'delete-inactive-plugins'; } // Inactive themes. if ( $lists['themes'] ) { $actions['delete-inactive-themes'] = 'delete-inactive-themes'; } return $actions; } /** * Try to fix the flaw(s) after requiring user action. * * @since 1.0 * * @return (array) The fix results. */ public function manual_fix() { $inactive = static::get_inactive_plugins_and_themes(); ob_start(); // PLUGINS. if ( $this->has_fix_action_part( 'delete-inactive-plugins' ) ) { $plugins = $this->manual_fix_plugins( $inactive ); } // THEMES. if ( $this->has_fix_action_part( 'delete-inactive-themes' ) ) { $themes = $this->manual_fix_themes( $inactive ); } ob_end_clean(); if ( ! empty( $plugins ) && ! empty( $themes ) ) { // "cantfix": nothing selected in both lists. $this->add_fix_message( 304 ); } elseif ( ! empty( $plugins ) ) { // "warning": no plugins selected. $this->add_fix_message( $delete ); } elseif ( ! empty( $themes ) ) { // "warning": no themes selected. $this->add_fix_message( $themes ); } // "good" $this->maybe_set_fix_status( 0 ); return parent::manual_fix(); } /** * Manual fix for plugins. * * @since 1.0 * * @param (array) $inactive Array containing an array of inactive plugins and an array of inactive themes. Values must be sanitized before. */ protected function manual_fix_plugins( $inactive ) { // Get the list of plugins to uninstall. $selected_plugins = ! empty( $_POST['secupress-fix-delete-inactive-plugins'] ) && is_array( $_POST['secupress-fix-delete-inactive-plugins'] ) ? array_filter( $_POST['secupress-fix-delete-inactive-plugins'] ) : array(); // WPCS: CSRF ok. $selected_plugins = $selected_plugins ? array_fill_keys( $selected_plugins, 1 ) : array(); $selected_plugins = $selected_plugins ? array_intersect_key( $inactive['plugins'], $selected_plugins ) : array(); // Sanitize submitted values. if ( ! $selected_plugins ) { if ( $this->has_fix_action_part( 'delete-inactive-themes' ) ) { /* * warning: no plugins selected. * No `add_fix_message()`, we need to change the status from warning to cantfix if both lists have no selection. */ return 100; } // "cantfix": no plugins selected. return $this->add_fix_message( 304 ); } // Get the base plugin folder. $wp_filesystem = secupress_get_filesystem(); $plugins_dir = $wp_filesystem->wp_plugins_dir(); if ( empty( $plugins_dir ) ) { // "cantfix": plugins dir not located. return $this->add_fix_message( 302 ); } $plugins_dir = trailingslashit( $plugins_dir ); $plugin_translations = wp_get_installed_translations( 'plugins' ); $deleted_plugins = array(); $count_inactive = count( $inactive['plugins'] ); $count_selected = count( $selected_plugins ); foreach ( $selected_plugins as $plugin_file => $plugin_data ) { // Run Uninstall hook. if ( is_uninstallable_plugin( $plugin_file ) ) { uninstall_plugin( $plugin_file ); } /** This action is documented in wp-admin/includes/plugin.php */ do_action( 'delete_plugin', $plugin_file ); $this_plugin_dir = trailingslashit( dirname( $plugins_dir . $plugin_file ) ); // If plugin is in its own directory, recursively delete the directory. if ( strpos( $plugin_file, '/' ) && $this_plugin_dir !== $plugins_dir ) { // base check on if plugin includes directory separator AND that its not the root plugin folder. $deleted = $wp_filesystem->delete( $this_plugin_dir, true ); } else { $deleted = $wp_filesystem->delete( $plugins_dir . $plugin_file ); } /** This action is documented in wp-admin/includes/plugin.php */ do_action( 'deleted_plugin', $plugin_file, $deleted ); if ( $deleted ) { $deleted_plugins[ $plugin_file ] = 1; // Remove language files, silently. $plugin_slug = dirname( $plugin_file ); if ( '.' !== $plugin_slug && ! empty( $plugin_translations[ $plugin_slug ] ) ) { $translations = $plugin_translations[ $plugin_slug ]; foreach ( $translations as $translation => $data ) { $wp_filesystem->delete( WP_LANG_DIR . '/plugins/' . $plugin_slug . '-' . $translation . '.po' ); $wp_filesystem->delete( WP_LANG_DIR . '/plugins/' . $plugin_slug . '-' . $translation . '.mo' ); } } } } $count_deleted = count( $deleted_plugins ); // Everything's deleted, no plugins left. if ( $count_deleted === $count_inactive ) { // "good" $this->add_fix_message( 1 ); } // All selected plugins deleted. elseif ( $count_deleted === $count_selected ) { // "partial": some plugins still need to be deleted. $this->add_fix_message( 101 ); } // No plugins deleted. elseif ( ! $count_deleted ) { // "bad" $this->add_fix_message( 202, array( $count_inactive ) ); } // Some plugins could not be deleted. else { // "cantfix" $not_removed = array_diff_key( $selected_plugins, $deleted_plugins ); $not_removed = wp_list_pluck( $not_removed, 'Name' ); $this->add_fix_message( 102, array( count( $not_removed ), $not_removed ) ); } // Force refresh of plugin update information and cache. if ( $deleted_plugins ) { if ( $current = get_site_transient( 'update_plugins' ) ) { $current->response = array_diff_key( $current->response, $deleted_plugins ); $current->no_update = array_diff_key( $current->no_update, $deleted_plugins ); set_site_transient( 'update_plugins', $current ); } wp_cache_delete( 'plugins', 'plugins' ); } } /** * Manual fix for themes. * * @since 1.0 * * @param (array) $inactive Array containing an array of inactive plugins and an array of inactive themes. Values must be sanitized before. */ protected function manual_fix_themes( $inactive ) { // Get the list of themes to uninstall. $selected_themes = ! empty( $_POST['secupress-fix-delete-inactive-themes'] ) && is_array( $_POST['secupress-fix-delete-inactive-themes'] ) ? array_filter( $_POST['secupress-fix-delete-inactive-themes'] ) : array(); // WPCS: CSRF ok. $selected_themes = $selected_themes ? array_fill_keys( $selected_themes, 1 ) : array(); $selected_themes = $selected_themes ? array_intersect_key( $inactive['themes'], $selected_themes ) : array(); // Sanitize submitted values. if ( ! $selected_themes ) { if ( $this->has_fix_action_part( 'delete-inactive-plugins' ) ) { /* * warning: no themes selected. * No `add_fix_message()`, we need to change the status from warning to cantfix if both lists have no selection. */ return 103; } // "cantfix": no themes selected. return $this->add_fix_message( 304 ); } // Get the base theme folder. $wp_filesystem = secupress_get_filesystem(); $themes_dir = $wp_filesystem->wp_themes_dir(); if ( empty( $themes_dir ) ) { // "cantfix": themes dir not located. return $this->add_fix_message( 303 ); } $themes_dir = trailingslashit( $themes_dir ); $deleted_themes = array(); $count_inactive = count( $inactive['themes'] ); $count_selected = count( $selected_themes ); foreach ( $selected_themes as $theme_file => $theme_data ) { $this_theme_dir = trailingslashit( $themes_dir . $theme_file ); if ( $wp_filesystem->delete( $this_theme_dir, true ) ) { $deleted_themes[ $theme_file ] = 1; } } $count_deleted = count( $deleted_themes ); // Everything's deleted, no themes left. if ( $count_deleted === $count_inactive ) { // "good" $this->add_fix_message( 2 ); } // All selected themes deleted. elseif ( $count_deleted === $count_selected ) { // "partial": some themes still need to be deleted. $this->add_fix_message( 104 ); } // No themes deleted. elseif ( ! $count_deleted ) { // "bad" $this->add_fix_message( 203, array( $count_inactive ) ); } // Some themes could not be deleted. else { // "cantfix" $not_removed = array_diff_key( $selected_themes, $deleted_themes ); if ( $not_removed ) { foreach ( $not_removed as $key => $theme ) { $not_removed[ $key ] = $theme->display( 'Name', false, true ); } } $this->add_fix_message( 105, array( count( $not_removed ), $not_removed ) ); } // Force refresh of theme update information. delete_site_transient( 'update_themes' ); // Force refresh of themes list. search_theme_directories( true ); } /** * Get an array containing ALL the forms that would fix the scan if it requires user action. * * @since 1.0 * * @return (array) An array of HTML templates (form contents most of the time). */ protected function get_fix_action_template_parts() { $forms = array(); $lists = static::get_inactive_plugins_and_themes(); if ( $lists['plugins'] ) { $form = '<h4 id="secupress-fix-inactive-plugins">' . __( 'Checked plugins will be deleted:', 'secupress' ) . '</h4>'; $form .= '<fieldset aria-labelledby="secupress-fix-inactive-plugins" class="secupress-boxed-group">' . "\n"; foreach ( $lists['plugins'] as $plugin_file => $plugin_data ) { $is_symlinked = secupress_is_plugin_symlinked( $plugin_file ); $plugin_name = esc_html( strip_tags( $plugin_data['Name'] ) ); $form .= '<input type="checkbox" id="secupress-fix-delete-inactive-plugins-' . sanitize_html_class( $plugin_file ) . '" name="secupress-fix-delete-inactive-plugins[]" value="' . esc_attr( $plugin_file ) . '" ' . ( $is_symlinked ? 'disabled="disabled"' : 'checked="checked"' ) . '/> '; $form .= '<label for="secupress-fix-delete-inactive-plugins-' . sanitize_html_class( $plugin_file ) . '">'; if ( $is_symlinked ) { $form .= '<del>' . $plugin_data['Name'] . '</del> <span class="description">(' . __( 'symlinked', 'secupress' ) . ')</span>'; } else { $form .= $plugin_data['Name']; } $form .= "</label><br/>\n"; } $form .= "</fieldset>\n"; } else { $form = __( 'No inactive plugins', 'secupress' ); } $forms['delete-inactive-plugins'] = $form; if ( $lists['themes'] ) { $form = '<h4 id="secupress-fix-inactive-themes">' . __( 'Checked themes will be deleted:', 'secupress' ) . '</h4>'; $form .= '<fieldset aria-labelledby="secupress-fix-inactive-themes" class="secupress-boxed-group">' . "\n"; // Add the default themes back. if ( $lists['default_themes'] ) { $lists['themes'] = array_merge( $lists['themes'], $lists['default_themes'] ); WP_Theme::sort_by_name( $lists['themes'] ); } foreach ( $lists['themes'] as $theme_file => $theme_data ) { $is_symlinked = ! empty( $lists['default_themes'][ $theme_file ] ) ? true : secupress_is_theme_symlinked( $theme_file ); $form .= '<input type="checkbox" id="secupress-fix-delete-inactive-themes-' . sanitize_html_class( $theme_file ) . '" name="secupress-fix-delete-inactive-themes[]" value="' . esc_attr( $theme_file ) . '" ' . ( $is_symlinked ? 'disabled="disabled"' : 'checked="checked"' ) . '/> '; $form .= '<label for="secupress-fix-delete-inactive-themes-' . sanitize_html_class( $theme_file ) . '">'; $theme_name = $theme_data->display( 'Name', false, true ); if ( ! empty( $lists['default_themes'][ $theme_file ] ) ) { $form .= '<del>' . $theme_name . '</del> <span class="description">(' . __( 'default theme', 'secupress' ) . ')</span>'; } elseif ( $is_symlinked ) { $form .= '<del>' . $theme_name . '</del> <span class="description">(' . __( 'symlinked', 'secupress' ) . ')</span>'; } else { $form .= $theme_name; } $form .= "</label><br/>\n"; } $form .= "</fieldset>\n"; } else { $form = __( 'No inactive themes', 'secupress' ); } $forms['delete-inactive-themes'] = $form; return $forms; } /** Tools. ================================================================================== */ /** * Get the default theme and (maybe) its child theme. * * @since 1.0 * * @return (array) An array of theme slugs. */ protected static function get_default_themes() { static $themes; if ( isset( $themes ) ) { return $themes; } $themes = array(); $default = wp_get_theme( WP_DEFAULT_THEME ); if ( ! $default->exists() ) { $default = WP_Theme::get_core_default_theme(); if ( false === $default ) { return array(); } } $stylesheet = $default->get_stylesheet(); $template = $default->get_template(); $themes[ $stylesheet ] = $stylesheet; if ( $template !== $stylesheet ) { $default = wp_get_theme( $template ); if ( $default->exists() ) { $themes[ $template ] = $template; } } return $themes; } /** * Get the inactive plugins and themes. * * @since 1.0 * * @return (array) Array containing an array of inactive plugins, an array of inactive themes, and an array of default theme(s). */ protected static function get_inactive_plugins_and_themes() { $out = array(); if ( is_multisite() ) { // For multisite we need to get active plugins and themes for each blog. Here, we'll fetch both. $plugins = get_site_option( 'secupress_active_plugins' ); $themes = get_site_option( 'secupress_active_themes' ); $active = array( 'plugins' => array(), 'themes' => array() ); foreach ( $plugins as $site_id => $site_plugins ) { if ( $site_plugins ) { $active['plugins'] = array_merge( $active['plugins'], $site_plugins ); } } foreach ( $themes as $site_id => $theme ) { $active['themes'][ $theme ] = $theme; } } // INACTIVE PLUGINS. $out['plugins'] = get_plugins(); if ( is_multisite() ) { $network_active_plugins = get_site_option( 'active_sitewide_plugins', array() ); $network_active_plugins = is_array( $network_active_plugins ) ? $network_active_plugins : array(); $active_plugins = array_merge( $active['plugins'], $network_active_plugins ); } else { $active_plugins = get_option( 'active_plugins', array() ); $active_plugins = is_array( $active_plugins ) ? $active_plugins : array(); $active_plugins = array_fill_keys( $active_plugins, 1 ); } $out['plugins'] = array_diff_key( $out['plugins'], $active_plugins ); // INACTIVE THEMES. $out['themes'] = wp_get_themes(); if ( is_multisite() ) { $active_themes = $active['themes']; } else { $active_themes = array(); $this_blog_theme = get_stylesheet(); if ( $this_blog_theme ) { $active_themes[ $this_blog_theme ] = $this_blog_theme; } } // We may have child themes, we need to add their parent to the "active themes" list. if ( $active_themes ) { foreach ( $active_themes as $stylesheet ) { if ( isset( $out['themes'][ $stylesheet ] ) && $out['themes'][ $stylesheet ]->parent() ) { $parent_stylesheet = $out['themes'][ $stylesheet ]->parent()->get_stylesheet(); $active_themes[ $parent_stylesheet ] = $parent_stylesheet; } } $out['themes'] = array_diff_key( $out['themes'], $active_themes ); } // Don't list the default themes. $default_themes = static::get_default_themes(); if ( $default_themes ) { // Keep track of those that are inactive. $out['default_themes'] = array_intersect_key( $out['themes'], $default_themes ); $out['themes'] = array_diff_key( $out['themes'], $default_themes ); } else { $out['default_themes'] = array(); } return $out; } } free/classes/scanners/class-secupress-scan-woocommerce-discloses.php 0000644 00000016134 15174670627 0022037 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * WooCommerce version disclose scan class. * * @package SecuPress * @subpackage SecuPress_Scan * @since 1.0 */ class SecuPress_Scan_Woocommerce_Discloses extends SecuPress_Scan implements SecuPress_Scan_Interface { /** Constants. ============================================================================== */ /** * Class version. * * @var (string) */ const VERSION = '1.2'; /** Properties. ============================================================================= */ /** * The reference to the *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** Init and messages. ====================================================================== */ /** * Init. * * @since 1.0 */ protected function init() { /** Translators: %s is a plugin name. */ $this->title = sprintf( __( 'Check if the %s plugin discloses its version.', 'secupress' ), 'WooCommerce' ); $this->more = __( 'When an attacker wants to hack into a WordPress site, they will search for all available informations. The goal is to find something useful that will help him penetrate your site. Don’t let them easily find any informations.', 'secupress' ); $this->more_fix = sprintf( /** Translators: 1 is a plugin name, 2 is the name of a protection, 3 is the name of a module. */ __( 'Hide the %1$s version to prevent giving too much information to attackers. The %2$s protection from the module %3$s will be activated.', 'secupress' ), 'WooCommerce', '<strong>' . __( 'Plugin Version Disclosure', 'secupress' ) . '</strong>', '<a href="' . esc_url( secupress_admin_url( 'modules', 'sensitive-data' ) ) . '#row-content-protect_plugin-version-discloses">' . __( 'Sensitive Data', 'secupress' ) . '</a>' ); } /** * Get messages. * * @since 1.0 * * @param (int) $message_id A message ID. * * @return (string|array) A message if a message ID is provided. An array containing all messages otherwise. */ public static function get_messages( $message_id = null ) { /** Translators: 1 is the name of a protection, 2 is the name of a module. */ $activate_protection_message = sprintf( __( 'But you can activate the %1$s protection from the module %2$s.', 'secupress' ), '<strong>' . __( 'Plugin Version Disclosure', 'secupress' ) . '</strong>', '<a target="_blank" href="' . esc_url( secupress_admin_url( 'modules', 'sensitive-data' ) ) . '#row-content-protect_plugin-version-discloses">' . __( 'Sensitive Data', 'secupress' ) . '</a>' ); $messages = array( // "good" /** Translators: %s is a plugin name. */ 0 => sprintf( __( 'The %s plugin does not reveal sensitive information.', 'secupress' ), '<strong>WooCommerce</strong>' ), // "warning" /** Translators: %s is a plugin name. */ 100 => sprintf( __( 'Unable to determine if %s is disclosing its version on your homepage.', 'secupress' ), '<strong>WooCommerce</strong>' ) . ' ' . $activate_protection_message, // "bad" /** Translators: 1 is a plugin name, 2 is some related info. */ 200 => sprintf( __( 'The %1$s plugin displays its version in the source code of your homepage (%2$s).', 'secupress' ), '<strong>WooCommerce</strong>', '%s' ), // DEPRECATED, NOT IN USE ANYMORE. 1 => __( 'The generator meta tag should not be displayed anymore.', 'secupress' ), /** Translators: %s is a plugin name. */ 2 => sprintf( __( 'The %s’s version should be removed from your styles URLs now.', 'secupress' ), 'WooCommerce' ), /** Translators: %s is a plugin name. */ 3 => sprintf( __( 'The %s’s version should be removed from your scripts URLs now.', 'secupress' ), 'WooCommerce' ), ); if ( isset( $message_id ) ) { return isset( $messages[ $message_id ] ) ? $messages[ $message_id ] : __( 'Unknown message', 'secupress' ); } return $messages; } /** Getters. ================================================================================ */ /** * Get the documentation URL. * * @since 1.2.3 * * @return (string) */ public static function get_docs_url() { return __( 'https://docs.secupress.me/article/105-woocommerce-version-number-disclosure-scan', 'secupress' ); } /** Scan. =================================================================================== */ /** * Scan for flaw(s). * * @since 1.0 * * @return (array) The scan results. */ public function scan() { $activated = $this->filter_scanner( __CLASS__ ); if ( true === $activated ) { $this->add_message( 0 ); return parent::scan(); } $discloses = array(); // Get home page contents. $response = wp_remote_get( add_query_arg( secupress_generate_key( 6 ), secupress_generate_key( 8 ), user_trailingslashit( home_url() ) ), $this->get_default_request_args() ); $has_response = ! is_wp_error( $response ) && 200 === wp_remote_retrieve_response_code( $response ); // Generator meta tag. if ( $has_response ) { $body = wp_remote_retrieve_body( $response ); // WooCommerce version in meta tag. preg_match_all( '#<meta name="generator" content="WooCommerce [^"]*' . esc_attr( WC_VERSION ) . '[^"]*"[^>]*>#s', $body, $matches ); if ( array_filter( $matches ) ) { // "bad" $discloses[] = 'META'; } } // What about style tag src? $style_url = home_url( '/fake.css?ver=' . WC_VERSION ); /** This filter is documented in wp-includes/class.wp-styles.php */ if ( apply_filters( 'style_loader_src', $style_url, 'secupress' ) === $style_url ) { // "bad" $discloses[] = 'CSS'; } // What about script tag src? $script_url = home_url( '/fake.js?ver=' . WC_VERSION ); /** This filter is documented in wp-includes/class.wp-scripts.php */ if ( apply_filters( 'script_loader_src', $script_url, 'secupress' ) === $script_url ) { // "bad" $discloses[] = 'JS'; } // Sum up! if ( $discloses ) { // "bad" $this->add_message( 200, array( $discloses ) ); } // "good" $this->maybe_set_status( 0 ); return parent::scan(); } /** Fix. ==================================================================================== */ /** * Try to fix the flaw(s). * * @since 1.4.5 * * @return (array) The fix results. */ public function need_manual_fix() { return [ 'fix' => 'fix' ]; } /** * Get an array containing ALL the forms that would fix the scan if it requires user action. * * @since 1.4.5 * * @return (array) An array of HTML templates (form contents most of the time). */ protected function get_fix_action_template_parts() { return [ 'fix' => ' ' ]; } /** * Try to fix the flaw(s) after requiring user action. * * @since 1.4.5 * * @return (array) The fix results. */ public function manual_fix() { if ( $this->has_fix_action_part( 'fix' ) ) { $this->fix(); } // "good" $this->add_fix_message( 1 ); return parent::manual_fix(); } /** * Try to fix the flaw(s). * * @since 1.0 * * @return (array) The fix results. */ public function fix() { // Activate. secupress_activate_submodule( 'discloses', 'woocommerce-version' ); // "good" $this->add_fix_message( 0 ); return parent::fix(); } } free/classes/scanners/class-secupress-scan-phpversion.php 0000644 00000010511 15174670627 0017720 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * PhpVersion scan class. * * @package SecuPress * @subpackage SecuPress_Scan * @since 1.0 */ class SecuPress_Scan_PhpVersion extends SecuPress_Scan implements SecuPress_Scan_Interface { /** Constants. ============================================================================== */ /** * Class version. * * @var (string) */ const VERSION = '2.0'; /** Properties. ============================================================================= */ /** * The reference to the *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** * Tells if a scanner is fixable by SecuPress. The value "pro" means it's fixable only with the version PRO. * * @var (bool|string) */ protected $fixable = false; /** Init and messages. ====================================================================== */ /** * Init. * * @since 1.0 */ protected function init() { $this->title = __( 'Check if your installation is using a supported version of PHP.', 'secupress' ); $this->more = __( 'Every year, old PHP version are not supported anymore, even for security patches so it’s important to stay updated.', 'secupress' ); $this->more_fix = static::get_messages( 300 ); } /** * Get messages. * * @since 1.0 * * @param (int) $message_id A message ID. * * @return (string|array) A message if a message ID is provided. An array containing all messages otherwise. */ public static function get_messages( $message_id = null ) { $versions = secupress_get_php_versions(); $messages = array( // "good" 0 => sprintf( __( 'You are using <strong>PHP v%s</strong>.', 'secupress' ), $versions['current'] ), 1 => sprintf( __( 'You are using <strong>PHP v%s</strong>, do not go lower!', 'secupress' ), $versions['current'] ), // last 2 => sprintf( __( 'You are using <strong>PHP v%s</strong>, this is correctly supported.', 'secupress' ), $versions['current'] ), // mini 3 => sprintf( __( 'You are using <strong>PHP v%s</strong>, the last one? Perfect!', 'secupress' ), $versions['current'] ), // best // "warning" 100 => __( 'Unable to determine version of PHP.', 'secupress' ), // "bad" 200 => sprintf( __( 'You are using <strong>PHP v%1$s</strong>, but the oldest major supported version is <strong>PHP v%2$s</strong>, and the last one is <strong>PHP v%3$s</strong>.', 'secupress' ), $versions['current'], $versions['mini'], $versions['best'] ), // "cantfix" 300 => __( 'Cannot be fixed automatically. You have to contact your host provider to ask him to <strong>upgrade your version of PHP</strong>.', 'secupress' ), ); if ( isset( $message_id ) ) { return isset( $messages[ $message_id ] ) ? $messages[ $message_id ] : __( 'Unknown message', 'secupress' ); } return $messages; } /** Getters. ================================================================================ */ /** * Get the documentation URL. * * @since 1.2.3 * * @return (string) */ public static function get_docs_url() { return __( 'https://docs.secupress.me/article/114-php-version-scan', 'secupress' ); } /** Scan. =================================================================================== */ /** * Scan for flaw(s). * * @since 1.0 * * @return (array) The scan results. */ public function scan() { $activated = $this->filter_scanner( __CLASS__ ); if ( true === $activated ) { $this->add_message( 0 ); return parent::scan(); } $versions = secupress_get_php_versions(); if ( version_compare( $versions['current'], $versions['mini'] ) < 0 ) { // minimum supported, they need more! $this->add_message( 200 ); $this->add_pre_fix_message( 300 ); } elseif ( version_compare( $versions['current'], $versions['last'] ) < 0 ) { // last supported by security $this->add_message( 1 ); } elseif ( version_compare( $versions['current'], $versions['best'] ) < 0 ) { // best supported $this->add_message( 2 ); } else { $this->add_message( 3 ); } return parent::scan(); } /** Fix. ==================================================================================== */ /** * Try to fix the flaw(s). * * @since 1.0 * * @return (array) The fix results. */ public function fix() { $this->add_fix_message( 300 ); return parent::fix(); } } free/classes/scanners/class-secupress-scan-https.php 0000644 00000015513 15174670627 0016674 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * HTTPS scan class. * * @package SecuPress * @subpackage SecuPress_Scan * @since 1.0 */ class SecuPress_Scan_HTTPS extends SecuPress_Scan implements SecuPress_Scan_Interface { /** Constants. ============================================================================== */ /** * Class version. * * @var (string) */ const VERSION = '2.0'; /** Properties. ============================================================================= */ /** * The reference to the *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** * Tells if a scanner is fixable by SecuPress. The value "pro" means it's fixable only with the version PRO. * * @var (bool|string) */ protected $fixable = true; /** Init and messages. ====================================================================== */ /** * Init. * * @since 1.0 */ protected function init() { $this->title = __( 'Check if your website is using an active HTTPS connection.', 'secupress' ); $this->more = __( 'An HTTPS connection is needed for many features on the web today, it also gains the trust of your visitors by helping to protecting their online privacy.', 'secupress' ); $this->more_fix = static::get_messages( 301 ); if ( false === $this->need_fix() ) { // "bad" $this->more_fix = static::get_messages( 300 ); $this->fixable = false; } if ( 0 === $this->need_fix() ) { // "plugin active" $this->more_fix = static::get_messages( 302 ); } if ( -1 === $this->need_fix() ) { // "capa" $this->more_fix = static::get_messages( 303 ); } } /** * Get messages. * * @since 1.0 * * @param (int) $message_id A message ID. * * @return (string|array) A message if a message ID is provided. An array containing all messages otherwise. */ public static function get_messages( $message_id = null ) { $messages = array( // "good" 0 => __( 'Your website is using an active HTTPS/SSL connection.', 'secupress' ), // "bad" 200 => __( 'Your site does not totally use HTTPS/SSL: %s', 'secupress' ), 201 => __( 'Your site does not use HTTPS/SSL. Error: %s', 'secupress' ), // 202 => __( 'Your website seems to run under maintenance mode, relaunch the HTTPS scanner later when you set it off.', 'secupress' ), // "cantfix" 300 => __( 'Cannot be fixed automatically. You have to contact you host provider to ask him to <strong>upgrade your site with HTTPS/SSL</strong>.', 'secupress' ), 301 => sprintf( __( 'Update your HOME url and SITE url with %s.', 'secupress' ), secupress_code_me( 'https://' ) ), 302 => __( 'The module <strong>WordPress Core > Locations</strong> is activated, deactivate it to fix this.', 'secupress' ), 303 => __( 'Sorry, you are not allowed to update this site to HTTPS.', 'secupress' ), ); if ( isset( $message_id ) ) { return isset( $messages[ $message_id ] ) ? $messages[ $message_id ] : __( 'Unknown message', 'secupress' ); } return $messages; } /** Getters. ================================================================================ */ /** * Get the documentation URL. * * @since 1.2.3 * * @return (string) */ public static function get_docs_url() { return __( 'https://docs.secupress.me/article/171-connection-https-ssl-scan', 'secupress' ); } /** Scan. =================================================================================== */ /** * Scan for flaw(s). * * @since 2.3.16 Revamp * @since 1.0 * * @return (array) The scan results. */ public function scan() { $activated = $this->filter_scanner( __CLASS__ ); if ( true === $activated ) { $this->add_message( 0 ); return parent::scan(); } if ( ! secupress_is_https_supported() ) { // very bad $response = get_transient( 'secupress_is_https_supported' ); if ( is_wp_error( $response ) && isset( $response->errors ) ) { $this->add_message( 201, [ implode( ', ', reset( $response->errors ) ) ] ); } } elseif ( ! secupress_site_is_using_https() ) { $bad = []; $bad[] = secupress_site_is_using_https( 'home' ) ? '' : __( 'your front-end site is not using HTTPS', 'secupress' ); $bad[] = secupress_site_is_using_https( 'site' ) ? '' : __( 'your back-end site is not using HTTPS', 'secupress' ); $bad = array_filter( $bad ); $bad = ucfirst( wp_sprintf_l( '%l', $bad ) ) . '.'; // bad $this->add_message( 200, [ $bad ] ); } // "good" $this->maybe_set_status( 0 ); return parent::scan(); } /** Fix. ==================================================================================== */ /** * Tell if we need to rename the table prefix. * * @since 2.0 secupress_site_is_using_https() secupress_is_https_supported() * @author Julio Potier * @since 1.1.1 * @author Grégory Viguier * * @return (bool) */ protected function need_fix() { if ( ! current_user_can( 'update_https' ) ) { return -1; } if ( ! secupress_is_https_supported() ) { return false; } if ( secupress_is_submodule_active( 'wordpress-core', 'wp-config-constant-locations' ) ) { return 0; } } /** * Try to fix the flaw(s). * * @since 2.0 * * @return (array) The fix results. */ public function need_manual_fix() { return [ 'fix' => 'fix' ]; } /** * Get an array containing ALL the forms that would fix the scan if it requires user action. * * @since 2.0 * * @return (array) An array of HTML templates (form contents most of the time). */ protected function get_fix_action_template_parts() { return [ 'fix' => ' ' ]; } /** * Try to fix the flaw(s). * * @since 2.0 secupress_update_urls_to_https() * * @return (array) The fix results. */ public function fix() { if ( false === $this->need_fix() ) { // "bad" $this->add_fix_message( 300 ); return parent::fix(); } if ( 0 === $this->need_fix() ) { // "plugin active" $this->add_fix_message( 302 ); return parent::fix(); } if ( -1 === $this->need_fix() ) { // "capa" $this->add_fix_message( 303 ); return parent::fix(); } secupress_update_urls_to_https(); $this->add_fix_message( 0 ); return parent::fix(); } /** * Try to fix the flaw(s) after requiring user action. * * @since 2.0 * * @return (array) The fix results. */ public function manual_fix() { // Make the tests again, we want to be sure to not run this script unnecessarily. if ( false === $this->need_fix() ) { // "bad" $this->add_fix_message( 300 ); return parent::manual_fix(); } if ( 0 === $this->need_fix() ) { // "plugin active" $this->add_fix_message( 302 ); return parent::manual_fix(); } if ( -1 === $this->need_fix() ) { // "capa" $this->add_fix_message( 303 ); return parent::manual_fix(); } secupress_update_urls_to_https(); $this->add_fix_message( 0 ); return parent::manual_fix(); } } free/classes/scanners/class-secupress-scan-php-disclosure.php 0000644 00000021505 15174670627 0020471 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * PHP Disclosure scan class. * * @package SecuPress * @subpackage SecuPress_Scan * @since 1.0 */ class SecuPress_Scan_PHP_Disclosure extends SecuPress_Scan implements SecuPress_Scan_Interface { /** Constants. ============================================================================== */ /** * Class version. * * @var (string) */ const VERSION = '1.2'; /** Properties. ============================================================================= */ /** * The reference to the *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** Init and messages. ====================================================================== */ /** * Init. * * @since 1.0 */ protected function init() { global $is_apache, $is_nginx, $is_iis7; $this->title = __( 'Check if your server lists the PHP modules <em>(known as «PHP Easter Egg»)</em>.', 'secupress' ); $this->more = __( 'PHP contains a flaw that discloses sensitive information about installed modules, resulting in a loss of confidentiality.', 'secupress' ); $this->more_fix = sprintf( __( 'Activate the %1$s protection from the module %2$s.', 'secupress' ), '<strong>' . __( 'PHP Disclosure', 'secupress' ) . '</strong>', '<a href="' . esc_url( secupress_admin_url( 'modules', 'sensitive-data' ) ) . '#row-content-protect_php-disclosure">' . __( 'Sensitive Data', 'secupress' ) . '</a>' ); if ( ! $is_apache && ! $is_nginx && ! $is_iis7 ) { $this->more_fix = static::get_messages( 301 ); $this->fixable = false; } } /** * Get messages. * * @since 1.0 * * @param (int) $message_id A message ID. * * @return (string|array) A message if a message ID is provided. An array containing all messages otherwise. */ public static function get_messages( $message_id = null ) { global $is_apache; $config_file = $is_apache ? '.htaccess' : 'web.config'; /** Translators: 1 is the name of a protection, 2 is the name of a module. */ $activate_protection_message = sprintf( __( 'But you can activate the %1$s protection from the module %2$s.', 'secupress' ), '<strong>' . __( 'PHP Disclosure', 'secupress' ) . '</strong>', '<a target="_blank" href="' . esc_url( secupress_admin_url( 'modules', 'sensitive-data' ) ) . '#row-content-protect_php-disclosure">' . __( 'Sensitive Data', 'secupress' ) . '</a>' ); $messages = array( // "good" 0 => __( 'Your site does not reveal the PHP modules.', 'secupress' ), /** Translators: %s is a file name. */ 1 => sprintf( __( 'The rules forbidding access to the <strong>PHP Easter Egg</strong> have been successfully added to your %s file.', 'secupress' ), "<code>$config_file</code>" ), // "warning" 100 => __( 'Unable to determine if your homepage is disclosing the PHP modules.', 'secupress' ) . ' ' . $activate_protection_message, // "bad" /** Translators: %s is a URL. */ 200 => sprintf( __( '%s should not be accessible to anyone.', 'secupress' ), '<code>' . esc_url( user_trailingslashit( home_url() ) . '?=PHPB8B5F2A0-3C92-11d3-A3A9-4C7B08C10000' ) . '</code>' ), // "cantfix" /** Translators: 1 is a file name, 2 is some code. */ 300 => sprintf( __( 'Your server runs <strong>Nginx</strong>, PHP modules disclosure cannot be protected automatically but you can do it yourself by adding the following code to your %1$s file: %2$s', 'secupress' ), '<code>nginx.conf</code>', '%s' ), 301 => __( 'Your server runs an unrecognized system. PHP modules disclosure cannot be protected automatically.', 'secupress' ), /** Translators: 1 is a file name, 2 is some code. */ 302 => sprintf( __( 'Your %1$s file is not writable. Please add the following lines at the beginning of the file: %2$s', 'secupress' ), "<code>$config_file</code>", '%s' ), /** Translators: 1 is a file name, 2 is a folder path (kind of), 3 is some code. */ 303 => sprintf( __( 'Your %1$s file is not writable. Please add the following lines inside the tags hierarchy %2$s (create it if does not exist): %3$s', 'secupress' ), "<code>$config_file</code>", '%1$s', '%2$s' ), ); if ( isset( $message_id ) ) { return isset( $messages[ $message_id ] ) ? $messages[ $message_id ] : __( 'Unknown message', 'secupress' ); } return $messages; } /** Getters. ================================================================================ */ /** * Get the documentation URL. * * @since 1.2.3 * * @return (string) */ public static function get_docs_url() { return __( 'https://docs.secupress.me/article/103-php-modules-disclosure-scan', 'secupress' ); } /** Scan. =================================================================================== */ /** * Scan for flaw(s). * * @since 1.0 * * @return (array) The scan results. */ public function scan() { $activated = $this->filter_scanner( __CLASS__ ); if ( true === $activated ) { $this->add_message( 0 ); return parent::scan(); } // - http://osvdb.org/12184 $response = wp_remote_get( user_trailingslashit( home_url() ) . '?=PHPB8B5F2A0-3C92-11d3-A3A9-4C7B08C10000', $this->get_default_request_args() ); if ( ! is_wp_error( $response ) && 200 === wp_remote_retrieve_response_code( $response ) ) { $response_body = wp_remote_retrieve_body( $response ); if ( strpos( $response_body, '<h1>PHP Credits</h1>' ) > 0 && strpos( $response_body, '<title>phpinfo()</title>' ) > 0 ) { // "bad" $this->add_message( 200 ); if ( ! $this->fixable ) { $this->add_pre_fix_message( 301 ); } } } // "good" $this->maybe_set_status( 0 ); return parent::scan(); } /** Fix. ==================================================================================== */ /** * Try to fix the flaw(s). * * @since 1.4.5 * * @return (array) The fix results. */ public function need_manual_fix() { return [ 'fix' => 'fix' ]; } /** * Get an array containing ALL the forms that would fix the scan if it requires user action. * * @since 1.4.5 * * @return (array) An array of HTML templates (form contents most of the time). */ protected function get_fix_action_template_parts() { return [ 'fix' => ' ' ]; } /** * Try to fix the flaw(s) after requiring user action. * * @since 1.4.5 * * @return (array) The fix results. */ public function manual_fix() { if ( $this->has_fix_action_part( 'fix' ) ) { $this->fix(); } // "good" $this->add_fix_message( 1 ); return parent::manual_fix(); } /** * Try to fix the flaw(s). * * @since 1.0 * * @return (array) The fix results. */ public function fix() { global $is_apache, $is_nginx, $is_iis7; if ( $is_apache ) { $this->fix_apache(); } elseif ( $is_iis7 ) { $this->fix_iis7(); } elseif ( $is_nginx ) { $this->fix_nginx(); } // "good" $this->maybe_set_fix_status( 0 ); return parent::fix(); } /** * Fix for Apache system. * * @since 1.0 */ protected function fix_apache() { global $wp_settings_errors; secupress_activate_submodule( 'sensitive-data', 'php-easter-egg' ); // Got error? $last_error = is_array( $wp_settings_errors ) && $wp_settings_errors ? end( $wp_settings_errors ) : false; if ( $last_error && 'general' === $last_error['setting'] && 'apache_manual_edit' === $last_error['code'] ) { $rules = static::get_rules_from_error( $last_error ); // "cantfix" $this->add_fix_message( 302, array( $rules ) ); array_pop( $wp_settings_errors ); return; } // "good" $this->add_fix_message( 1 ); } /** * Fix for IIS7 system. * * @since 1.0 */ protected function fix_iis7() { global $wp_settings_errors; secupress_activate_submodule( 'sensitive-data', 'php-easter-egg' ); // Got error? $last_error = is_array( $wp_settings_errors ) && $wp_settings_errors ? end( $wp_settings_errors ) : false; if ( $last_error && 'general' === $last_error['setting'] && 'iis7_manual_edit' === $last_error['code'] ) { $rules = static::get_rules_from_error( $last_error ); $path = static::get_code_tag_from_error( $last_error, 'secupress-iis7-path' ); // "cantfix" $this->add_fix_message( 303, array( $path, $rules ) ); array_pop( $wp_settings_errors ); return; } // "good" $this->add_fix_message( 1 ); } /** * Fix for nginx system. * * @since 1.0 */ protected function fix_nginx() { global $wp_settings_errors; secupress_activate_submodule( 'sensitive-data', 'php-easter-egg' ); // Get the error. $last_error = is_array( $wp_settings_errors ) && $wp_settings_errors ? end( $wp_settings_errors ) : false; $rules = '<code>Error</code>'; if ( $last_error && 'general' === $last_error['setting'] && 'nginx_manual_edit' === $last_error['code'] ) { $rules = static::get_rules_from_error( $last_error ); array_pop( $wp_settings_errors ); } // "cantfix" $this->add_fix_message( 300, array( $rules ) ); } } free/classes/scanners/class-secupress-scan-bad-file-extensions.php 0000644 00000025011 15174670627 0021364 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Bad File Extensions scan class. * * @package SecuPress * @subpackage SecuPress_Scan * @since 1.0 */ class SecuPress_Scan_Bad_File_Extensions extends SecuPress_Scan implements SecuPress_Scan_Interface { /** Constants. ============================================================================== */ /** * Class version. * * @var (string) */ const VERSION = '1.2'; /** Properties. ============================================================================= */ /** * The reference to the *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** * The test file path. * * @var (bool|string) */ protected $file_path = false; /** * The test file URL. * * @var (bool|string) */ protected $file_url = false; /** Init and messages. ====================================================================== */ /** * Init. * * @since 1.0 */ protected function init() { global $is_apache, $is_nginx, $is_iis7; $this->title = __( 'Check if there are files using bad extensions are accessible in the uploads folder.', 'secupress' ); $this->more = __( 'The uploads folder should only contain files like images, pdf, or zip archives. Other files should not be accessible by their URL.', 'secupress' ); $this->more_fix = sprintf( __( 'Activate the %1$s protection from the module %2$s.', 'secupress' ), '<strong>' . __( 'Bad File Extensions', 'secupress' ) . '</strong>', '<a href="' . esc_url( secupress_admin_url( 'modules', 'sensitive-data' ) ) . '#row-content-protect_bad-url-access">' . __( 'Sensitive Data', 'secupress' ) . '</a>' ); if ( ! $is_apache && ! $is_nginx && ! $is_iis7 ) { $this->more_fix = static::get_messages( 301 ); $this->fixable = false; return; } } /** * Get messages. * * @since 1.0 * * @param (int) $message_id A message ID. * * @return (string|array) A message if a message ID is provided. An array containing all messages otherwise. */ public static function get_messages( $message_id = null ) { global $is_apache; $config_file = $is_apache ? '.htaccess' : 'web.config'; /** Translators: 1 is the name of a protection, 2 is the name of a module. */ $activate_protection_message = sprintf( __( 'But you can activate the %1$s protection from the module %2$s.', 'secupress' ), '<strong>' . __( 'Bad File Extensions', 'secupress' ) . '</strong>', '<a target="_blank" href="' . esc_url( secupress_admin_url( 'modules', 'sensitive-data' ) ) . '#row-content-protect_bad-url-access">' . __( 'Sensitive Data', 'secupress' ) . '</a>' ); $messages = array( // "good" 0 => __( 'Files that use bad extensions are protected.', 'secupress' ), /** Translators: %s is a file name. */ 1 => sprintf( __( 'The rules forbidding access to files that use bad extensions have been successfully added to your %s file.', 'secupress' ), "<code>$config_file</code>" ), // "warning" 100 => __( 'Unable to determine the status of the bad file extensions test file.', 'secupress' ) . ' ' . $activate_protection_message, // "bad" 200 => __( 'Could not create a bad extension test file in the uploads folder.', 'secupress' ) . ' ' . $activate_protection_message, 201 => __( 'Whether or not you have files using bad extensions in the uploads folder, those files are accessible directly.', 'secupress' ), // "cantfix" /** Translators: 1 is a file name, 2 is some code. */ 300 => sprintf( __( 'Your server runs <strong>Nginx</strong>, the files that use bad extensions cannot be protected automatically but you can do it yourself by adding the following code to your %1$s file: %2$s', 'secupress' ), '<code>nginx.conf</code>', '%s' ), 301 => __( 'Your server runs an unrecognized system. The files that use bad extensions cannot be protected automatically.', 'secupress' ), /** Translators: 1 is a file name, 2 is some code. */ 302 => sprintf( __( 'Your %1$s file is not writable. Please add the following lines at the beginning of the file: %2$s', 'secupress' ), "<code>$config_file</code>", '%s' ), /** Translators: 1 is a file name, 2 is a folder path (kind of), 3 is some code. */ 303 => sprintf( __( 'Your %1$s file is not writable. Please add the following lines inside the tags hierarchy %2$s (create it if does not exist): %3$s', 'secupress' ), "<code>$config_file</code>", '%1$s', '%2$s' ), ); if ( isset( $message_id ) ) { return isset( $messages[ $message_id ] ) ? $messages[ $message_id ] : __( 'Unknown message', 'secupress' ); } return $messages; } /** Getters. ================================================================================ */ /** * Get the documentation URL. * * @since 1.2.3 * * @return (string) */ public static function get_docs_url() { return __( 'https://docs.secupress.me/article/124-bad-file-extension-scan', 'secupress' ); } /** Scan. =================================================================================== */ /** * Scan for flaw(s). * * @since 1.0 * * @return (array) The scan results. */ public function scan() { $activated = $this->filter_scanner( __CLASS__ ); if ( true === $activated ) { $this->add_message( 0 ); return parent::scan(); } // Create the temporary file. $this->create_file(); if ( ! $this->file_url ) { // "bad" $this->add_message( 200 ); return parent::scan(); } $response = wp_remote_get( $this->file_url, $this->get_default_request_args() ); if ( is_wp_error( $response ) ) { // "good" $this->add_message( 0 ); } elseif ( 200 === wp_remote_retrieve_response_code( $response ) ) { // "bad" $this->add_message( 201 ); } // Delete the temporary file. $this->delete_file(); // "good" $this->maybe_set_status( 0 ); return parent::scan(); } /** Fix. ==================================================================================== */ /** * Try to fix the flaw(s). * * @since 1.4.5 * * @return (array) The fix results. */ public function need_manual_fix() { return [ 'fix' => 'fix' ]; } /** * Get an array containing ALL the forms that would fix the scan if it requires user action. * * @since 1.4.5 * * @return (array) An array of HTML templates (form contents most of the time). */ protected function get_fix_action_template_parts() { return [ 'fix' => ' ' ]; } /** * Try to fix the flaw(s) after requiring user action. * * @since 1.4.5 * * @return (array) The fix results. */ public function manual_fix() { if ( $this->has_fix_action_part( 'fix' ) ) { $this->fix(); } // "good" $this->add_fix_message( 1 ); return parent::manual_fix(); } /** * Try to fix the flaw(s). * * @since 1.0 * * @return (array) The fix results. */ public function fix() { global $is_apache, $is_nginx, $is_iis7; if ( $is_apache ) { $this->fix_apache(); } elseif ( $is_iis7 ) { $this->fix_iis7(); } elseif ( $is_nginx ) { $this->fix_nginx(); } else { $this->add_fix_message( 301 ); } return parent::fix(); } /** * Fix for Apache system. * * @since 1.0 */ protected function fix_apache() { global $wp_settings_errors; secupress_activate_submodule( 'sensitive-data', 'bad-file-extensions' ); // Got error? $last_error = is_array( $wp_settings_errors ) && $wp_settings_errors ? end( $wp_settings_errors ) : false; if ( $last_error && 'general' === $last_error['setting'] && 'apache_manual_edit' === $last_error['code'] ) { $rules = static::get_rules_from_error( $last_error ); // "cantfix" $this->add_fix_message( 302, array( $rules ) ); array_pop( $wp_settings_errors ); return; } // "good" $this->add_fix_message( 1 ); } /** * Fix for IIS7 system. * * @since 1.0 */ protected function fix_iis7() { global $wp_settings_errors; secupress_activate_submodule( 'sensitive-data', 'bad-file-extensions' ); // Got error? $last_error = is_array( $wp_settings_errors ) && $wp_settings_errors ? end( $wp_settings_errors ) : false; if ( $last_error && 'general' === $last_error['setting'] && 'iis7_manual_edit' === $last_error['code'] ) { $rules = static::get_rules_from_error( $last_error ); $path = static::get_code_tag_from_error( $last_error, 'secupress-iis7-path' ); // "cantfix" $this->add_fix_message( 303, array( $path, $rules ) ); array_pop( $wp_settings_errors ); return; } // "good" $this->add_fix_message( 1 ); } /** * Fix for nginx system. * * @since 1.0 */ protected function fix_nginx() { global $wp_settings_errors; secupress_activate_submodule( 'sensitive-data', 'bad-file-extensions' ); // Get the error. $last_error = is_array( $wp_settings_errors ) && $wp_settings_errors ? end( $wp_settings_errors ) : false; $rules = '<code>Error</code>'; if ( $last_error && 'general' === $last_error['setting'] && 'nginx_manual_edit' === $last_error['code'] ) { $rules = static::get_rules_from_error( $last_error ); array_pop( $wp_settings_errors ); } // "cantfix" $this->add_fix_message( 300, array( $rules ) ); } /** Tools. ================================================================================== */ /** * Create a test file in the uploads folder. Also set the test file path and URL. * * @since 2.3.17 Use a forbidden ext directly from the list * @since 1.0 */ protected function create_file() { $wp_filesystem = secupress_get_filesystem(); $uploads = wp_upload_dir( null, false ); $basedir = wp_normalize_path( $uploads['basedir'] ); $extensions = secupress_bad_file_extensions_get_forbidden_extensions(); // Get the file name. $file_ext = mt_rand( 0, count( $extensions ) - 1 ); $file_ext = $extensions[ $file_ext ]; $file_name = 'secupress-temporary-file-' . secupress_generate_hash( 'file_name', 2, 6 ) . '.' . $file_ext; $file_path = $basedir . '/' . $file_name; // Create the file. if ( file_exists( $file_path ) ) { $wp_filesystem->delete( $file_path ); } if ( ! file_exists( $basedir ) ) { $wp_filesystem->mkdir( $basedir, FS_CHMOD_DIR ); } if ( file_exists( $file_path ) || ! file_exists( $basedir ) ) { return; } $created = $wp_filesystem->put_contents( $file_path, 'Temporary file', FS_CHMOD_FILE ); if ( $created ) { $this->file_path = $file_path; $this->file_url = trailingslashit( $uploads['baseurl'] ) . $file_name; } } /** * Delete a file. * * @since 1.0 */ protected function delete_file() { secupress_get_filesystem()->delete( $this->file_path ); $this->file_path = false; $this->file_url = false; } } free/classes/scanners/class-secupress-scan-plugins-update.php 0000644 00000015751 15174670627 0020477 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Plugins Update scan class. * * @package SecuPress * @subpackage SecuPress_Scan * @since 1.0 */ class SecuPress_Scan_Plugins_Update extends SecuPress_Scan implements SecuPress_Scan_Interface { /** Constants. ============================================================================== */ /** * Class version. * * @var (string) */ const VERSION = '1.2'; /** Properties. ============================================================================= */ /** * The reference to the *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** * Tells if the fix must occur after all other scans and fixes, while no other scan/fix is running. * * @var (bool) */ protected $delayed_fix = true; /** Init and messages. ====================================================================== */ /** * Init. * * @since 1.0 */ protected function init() { $this->title = __( 'Check if your plugins are up to date.', 'secupress' ); $this->more = __( 'It is very important to keep your WordPress installation up to date. If you cannot update because of a plugin, contact its author and submit your issue.', 'secupress' ); $this->more_fix = __( 'Update all your plugins that are not up to date.', 'secupress' ); } /** * Get messages. * * @since 1.0 * * @param (int) $message_id A message ID. * * @return (string|array) A message if a message ID is provided. An array containing all messages otherwise. */ public static function get_messages( $message_id = null ) { $messages = array( // "good" 0 => __( 'Your plugins are up to date.', 'secupress' ), // "warning" 100 => _n_noop( '<strong>%d symlinked plugin</strong> is not up to date, and cannot be updated automatically.', '<strong>%d symlinked plugins</strong> are not up to date, and cannot be updated automatically.', 'secupress' ), // "bad" 200 => _n_noop( '<strong>%1$d plugin</strong> is not up to date: %2$s.', '<strong>%1$d plugins</strong> are not up to date: %2$s.', 'secupress' ), // "cantfix" 300 => __( 'Some plugins could not be updated correctly.', 'secupress' ), 301 => _n_noop( '<strong>%d symlinked plugin</strong> is not up to date, and cannot be updated automatically.', '<strong>%d symlinked plugins</strong> are not up to date, and cannot be updated automatically.', 'secupress' ), ); if ( isset( $message_id ) ) { return isset( $messages[ $message_id ] ) ? $messages[ $message_id ] : __( 'Unknown message', 'secupress' ); } return $messages; } /** Getters. ================================================================================ */ /** * Get the documentation URL. * * @since 1.2.3 * * @return (string) */ public static function get_docs_url() { return __( 'https://docs.secupress.me/article/120-plugin-update-scan', 'secupress' ); } /** Scan. =================================================================================== */ /** * Scan for flaw(s). * * @since 1.0 * * @return (array) The scan results. */ public function scan() { $activated = $this->filter_scanner( __CLASS__ ); if ( true === $activated ) { $this->add_message( 0 ); return parent::scan(); } ob_start(); wp_update_plugins(); $plugins = get_site_transient( 'update_plugins' ); $plugins = ! empty( $plugins->response ) && is_array( $plugins->response ) ? array_keys( $plugins->response ) : array(); $symlinked_plugins = array(); if ( $plugins ) { $symlinked_plugins = array_filter( $plugins, 'secupress_is_plugin_symlinked' ); $plugins = array_diff( $plugins, $symlinked_plugins ); $plugins = array_flip( $plugins ); $plugins = array_intersect_key( get_plugins(), $plugins ); $plugins = wp_list_pluck( $plugins, 'Name' ); } ob_flush(); if ( $count = count( $plugins ) ) { // "bad" $this->add_message( 200, array( $count, $count, self::wrap_in_tag( $plugins ) ) ); } if ( $count = count( $symlinked_plugins ) ) { // "warning" $this->add_message( 100, array( $count, $count ) ); } // "good" $this->maybe_set_status( 0 ); return parent::scan(); } /** Fix. ==================================================================================== */ /** * Try to fix the flaw(s). * * @since 1.4.5 * * @return (array) The fix results. */ public function need_manual_fix() { return [ 'fix' => 'fix' ]; } /** * Get an array containing ALL the forms that would fix the scan if it requires user action. * * @since 1.4.5 * * @return (array) An array of HTML templates (form contents most of the time). */ protected function get_fix_action_template_parts() { return [ 'fix' => ' ' ]; } /** * Try to fix the flaw(s) after requiring user action. * * @since 1.4.5 * * @return (array) The fix results. */ public function manual_fix() { if ( $this->has_fix_action_part( 'fix' ) ) { $this->fix(); } // "good" $this->add_fix_message( 1 ); return parent::manual_fix(); } /** * Try to fix the flaw(s). * * @since 1.0 * * @return (array) The fix results. */ public function fix() { // Plugins. $plugins = get_site_transient( 'update_plugins' ); $plugins = ! empty( $plugins->response ) && is_array( $plugins->response ) ? array_keys( $plugins->response ) : array(); if ( $plugins ) { $symlinked_plugins = array_filter( $plugins, 'secupress_is_plugin_symlinked' ); $plugins = array_diff( $plugins, $symlinked_plugins ); } if ( $plugins ) { ob_start(); secupress_time_limit( 0 ); // Remove the WP upgrade process for translation since it will output data, use our own based on core but using a silent upgrade. remove_action( 'upgrader_process_complete', array( 'Language_Pack_Upgrader', 'async_upgrade' ), 20 ); add_action( 'upgrader_process_complete', 'secupress_async_upgrades', 20 ); include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' ); $nonce = 'bulk-update-plugins'; $url = implode( ',', $plugins ); $url = 'update.php?action=update-selected&plugins=' . urlencode( $url ); $skin = new Automatic_Upgrader_Skin( array( 'nonce' => $nonce, 'url' => $url ) ); $upgrader = new Plugin_Upgrader( $skin ); $upgrader->bulk_upgrade( $plugins ); ob_end_clean(); } // Test if we succeeded. $plugins = get_site_transient( 'update_plugins' ); $plugins = ! empty( $plugins->response ) && is_array( $plugins->response ) ? array_keys( $plugins->response ) : array(); if ( ! $plugins ) { // "good" $this->add_fix_message( 0 ); } else { $symlinked_plugins = array_filter( $plugins, 'secupress_is_plugin_symlinked' ); $plugins = array_diff( $plugins, $symlinked_plugins ); if ( $count = count( $symlinked_plugins ) ) { // "cantfix" $this->add_fix_message( 301, array( $count, $count ) ); } else { // "cantfix" $this->add_fix_message( 300 ); } } return parent::fix(); } } free/classes/scanners/class-secupress-scan-auto-update.php 0000644 00000020163 15174670627 0017757 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Auto Update scan class. * * @package SecuPress * @subpackage SecuPress_Scan * @since 1.0 */ class SecuPress_Scan_Auto_Update extends SecuPress_Scan implements SecuPress_Scan_Interface { /** Constants. ============================================================================== */ /** * Class version. * * @var (string) */ const VERSION = '1.2'; /** Properties. ============================================================================= */ /** * The reference to the *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** Init and messages. ====================================================================== */ /** * Init. * * @since 1.0 */ protected function init() { $this->title = __( 'Check if your WordPress core can perform auto-updates for minor versions.', 'secupress' ); $this->more = __( 'When a minor update is released, WordPress can install it automatically. By doing so you are always up to date when a security flaw is discovered in the WordPress Core.', 'secupress' ); $this->more_fix = sprintf( __( 'Activate the option %1$s in the %2$s module.', 'secupress' ), '<em>' . __( 'Minor updates', 'secupress' ) . '</em>', '<a href="' . esc_url( secupress_admin_url( 'modules', 'wordpress-core' ) ) . '#row-auto-update_minor">' . __( 'WordPress Core', 'secupress' ) . '</a>' ); } /** * Get messages. * * @since 1.0 * * @param (int) $message_id A message ID. * * @return (string|array) A message if a message ID is provided. An array containing all messages otherwise. */ public static function get_messages( $message_id = null ) { $messages = array( // "good" 0 => __( 'Your installation <strong>can auto-update</strong> itself.', 'secupress' ), 1 => __( 'Protection activated', 'secupress' ), // "bad" 200 => __( 'Your installation <strong>cannot auto-update</strong> itself.', 'secupress' ), 207 => _n_noop( /** Translators: 1 is a value, 2 is a PHP constant name (or a list of names). */ 'The following constant should not be set to %1$s: %2$s.', 'The following constants should not be set to %1$s: %2$s.', 'secupress' ), 208 => _n_noop( /** Translators: 1 is a value, 2 is a PHP constant name (or a list of names). */ 'The following constant should not be set to %1$s: %2$s.', 'The following constants should not be set to %1$s: %2$s.', 'secupress' ), 209 => _n_noop( /** Translators: 1 is a value, 2 is a filter name (or a list of names). */ 'The following filter should not be used or set to return %1$s: %2$s.', 'The following filters should not be used or set to return %1$s: %2$s.', 'secupress' ), 210 => _n_noop( /** Translators: 1 is a value, 2 is a filter name (or a list of names). */ 'The following filter should not be used or set to return %1$s: %2$s.', 'The following filters should not be used or set to return %1$s: %2$s.', 'secupress' ), // "cantfix" /** Translators: 1 is a file name, 2 is some code. */ 300 => sprintf( __( 'The %1$s file is not writable. Please remove the following code from the file: %2$s', 'secupress' ), '<code>' . secupress_get_wpconfig_filename() . '</code>', '%s' ), 301 => _n_noop( /** Translators: 1 is the plugin name, 2 is a file name, 3 is some code. */ '%1$s could not remove a constant definition from the %2$s file. Please remove the following line from the file: %3$s', '%1$s could not remove some constant definitions from the %2$s file. Please remove the following lines from the file: %3$s', 'secupress' ), ); if ( isset( $message_id ) ) { return isset( $messages[ $message_id ] ) ? $messages[ $message_id ] : __( 'Unknown message', 'secupress' ); } return $messages; } /** Getters. ================================================================================ */ /** * Get the documentation URL. * * @since 1.2.3 * * @return (string) */ public static function get_docs_url() { return __( 'https://docs.secupress.me/article/98-automatic-updates-scan', 'secupress' ); } /** Scan. =================================================================================== */ /** * Scan for flaw(s). * * @since 1.0 * * @return (array) The scan results. */ public function scan() { $activated = $this->filter_scanner( __CLASS__ ); if ( true === $activated ) { $this->add_message( 0 ); return parent::scan(); } $constants_false = array(); $constants_true = array(); $constants = array( 'DISALLOW_FILE_MODS' => true, 'AUTOMATIC_UPDATER_DISABLED' => true, 'WP_AUTO_UPDATE_CORE' => false, ); foreach ( $constants as $constant => $val ) { if ( defined( $constant ) && (bool) constant( $constant ) === $val ) { if ( $val ) { $constants_true[] = "<code>$constant</code>"; } else { $constants_false[] = "<code>$constant</code>"; } } } $filters_false = array(); $filters_true = array(); $filters = array( 'automatic_updater_disabled' => true, 'allow_minor_auto_core_updates' => false, ); foreach ( $filters as $filter => $val ) { /** This filter is documented wp-admin/includes/class-wp-upgrader.php */ if ( apply_filters( $filter, ! $val ) === $val ) { if ( $val ) { $filters_true[] = "<code>$filter</code>"; } else { $filters_false[] = "<code>$filter</code>"; } } } if ( $constants_false || $constants_true || $filters_false || $filters_true ) { $this->add_message( 200 ); if ( $constants_false ) { $this->add_message( 207, array( count( $constants_false ), '<code>false</code>', $constants_false ) ); } if ( $constants_true ) { $this->add_message( 208, array( count( $constants_true ), '<code>true</code>', $constants_true ) ); } if ( $filters_false ) { $this->add_message( 209, array( count( $filters_false ), '<code>false</code>', $filters_false ) ); } if ( $filters_true ) { $this->add_message( 210, array( count( $filters_true ), '<code>true</code>', $filters_true ) ); } } else { $this->add_message( 0 ); } // "good" $this->maybe_set_status( 0 ); return parent::scan(); } /** Fix. ==================================================================================== */ /** * Try to fix the flaw(s). * * @since 1.4.5 * * @return (array) The fix results. */ public function need_manual_fix() { return [ 'fix' => 'fix' ]; } /** * Get an array containing ALL the forms that would fix the scan if it requires user action. * * @since 1.4.5 * * @return (array) An array of HTML templates (form contents most of the time). */ protected function get_fix_action_template_parts() { return [ 'fix' => ' ' ]; } /** * Try to fix the flaw(s) after requiring user action. * * @since 1.4.5 * * @return (array) The fix results. */ public function manual_fix() { if ( $this->has_fix_action_part( 'fix' ) ) { $this->fix(); } // "good" $this->add_fix_message( 1 ); return parent::manual_fix(); } /** * Try to fix the flaw(s). * * @since 1.0 * * @return (array) The fix results. */ public function fix() { global $wp_settings_errors; secupress_activate_submodule( 'wordpress-core', 'minor-updates' ); // Get the error. $last_error = is_array( $wp_settings_errors ) && $wp_settings_errors ? end( $wp_settings_errors ) : false; if ( $last_error && 'general' === $last_error['setting'] ) { if ( 'wp_config_not_writable' === $last_error['code'] ) { $rules = static::get_rules_from_error( $last_error ); // "cantfix" $this->add_fix_message( 300, array( $rules ) ); array_pop( $wp_settings_errors ); } elseif ( 'constant_not_commented' === $last_error['code'] ) { $rules = static::get_rules_from_error( $last_error ); $count = substr_count( $rules, "\n" ) + 1; // "cantfix" $this->add_fix_message( 301, array( $count, SECUPRESS_PLUGIN_NAME, '<code>' . secupress_get_wpconfig_filename() . '</code>', $rules ) ); array_pop( $wp_settings_errors ); } } $this->maybe_set_fix_status( 1 ); return parent::fix(); } } free/classes/scanners/class-secupress-scan-passwords-strength.php 0000644 00000014117 15174670627 0021412 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Passwords Strength scan class. * * @package SecuPress * @subpackage SecuPress_Scan * @since 1.0 */ class SecuPress_Scan_Passwords_Strength extends SecuPress_Scan implements SecuPress_Scan_Interface { /** Constants. ============================================================================== */ /** * Class version. * * @var (string) */ const VERSION = '1.0.1'; /** Properties. ============================================================================= */ /** * The reference to the *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** * Tells if a scanner is fixable by SecuPress. The value "pro" means it's fixable only with the version PRO. * * @var (bool|string) */ protected $fixable = false; /** Init and messages. ====================================================================== */ /** * Init. * * @since 1.0 */ protected function init() { if ( defined( 'FTP_PASS' ) ) { $this->title = __( 'Test the strength of WordPress database and FTP passwords.', 'secupress' ); $this->more = __( 'The passwords of the database and FTP have to be strong to avoid a possible brute-force attack.', 'secupress' ); } else { $this->title = __( 'Test the strength of WordPress database password.', 'secupress' ); $this->more = __( 'The password of the database has to be strong to avoid a possible brute-force attack.', 'secupress' ); } $this->more_fix = static::get_messages( 300 ); } /** * Get messages. * * @since 1.0 * * @param (int) $message_id A message ID. * * @return (string|array) A message if a message ID is provided. An array containing all messages otherwise. */ public static function get_messages( $message_id = null ) { $messages = array( // "good" 0 => __( 'Database password seems strong enough.', 'secupress' ), 1 => __( 'Database and FTP passwords seems strong enough.', 'secupress' ), // "bad" 200 => __( 'Your Database password is <strong>empty</strong>, this is not secure!', 'secupress' ), 201 => __( 'Your Database password is known to be <strong>too common</strong>, this is not secure', 'secupress' ), 202 => _n_noop( 'Your Database password is only <strong>%d character length</strong>, this is not secure', 'Your Database password is only <strong>%d characters length</strong>, this is not secure', 'secupress' ), 203 => __( 'Your Database password is not <strong>complex</strong> enough, this is not secure', 'secupress' ), 210 => __( 'Your FTP password is <strong>empty</strong>, this is not secure!', 'secupress' ), 211 => __( 'Your FTP password is known to be <strong>too common</strong>, this is not secure', 'secupress' ), 212 => _n_noop( 'Your FTP password is only <strong>%d character length</strong>, this is not secure', 'Your FTP password is only <strong>%d characters length</strong>, this is not secure', 'secupress' ), 213 => __( 'Your FTP password is not <strong>complex</strong> enough, this is not secure', 'secupress' ), // "cantfix" 300 => __( 'This cannot be fixed automatically, you have to manually change your database and FTP password in your server administration.', 'secupress' ), 301 => __( 'This cannot be fixed automatically, you have to manually change your database password in your server administration.', 'secupress' ), 302 => __( 'This cannot be fixed automatically, you have to manually change your FTP password in your server administration.', 'secupress' ), ); if ( isset( $message_id ) ) { return isset( $messages[ $message_id ] ) ? $messages[ $message_id ] : __( 'Unknown message', 'secupress' ); } return $messages; } /** Getters. ================================================================================ */ /** * Get the documentation URL. * * @since 1.2.3 * * @return (string) */ public static function get_docs_url() { return __( 'https://docs.secupress.me/article/131-ftp-and-database-passwords-scan', 'secupress' ); } /** Scan. =================================================================================== */ /** * Scan for flaw(s). * * @since 1.0 * * @return (array) The scan results. */ public function scan() { $activated = $this->filter_scanner( __CLASS__ ); if ( true === $activated ) { $this->add_message( 0 ); return parent::scan(); } $has_ftp = defined( 'FTP_PASS' ); // DB_PASSWORD. if ( '' === DB_PASSWORD ) { // "bad" $this->add_message( 200 ); $this->add_pre_fix_message( 301 ); } elseif ( self::dictionary_attack( DB_PASSWORD ) ) { // "bad" $this->add_message( 201 ); $this->add_pre_fix_message( 301 ); } elseif ( ( $len = strlen( DB_PASSWORD ) ) <= 6 ) { // "bad" $this->add_message( 202 ); $this->add_pre_fix_message( 301 ); } elseif ( count( count_chars( DB_PASSWORD, 1 ) ) < 5 ) { // "bad" $this->add_message( 203 ); $this->add_pre_fix_message( 301 ); } // FTP_PASS. if ( $has_ftp ) { if ( '' === FTP_PASS ) { // "bad" $this->add_message( 210 ); $this->add_pre_fix_message( 302 ); } elseif ( self::dictionary_attack( FTP_PASS ) ) { // "bad" $this->add_message( 211 ); $this->add_pre_fix_message( 302 ); } elseif ( ( $len = strlen( FTP_PASS ) ) <= 6 ) { // "bad" $this->add_message( 212 ); $this->add_pre_fix_message( 302 ); } elseif ( count( count_chars( FTP_PASS, 1 ) ) < 5 ) { // "bad" $this->add_message( 213 ); $this->add_pre_fix_message( 302 ); } } // "good" $this->maybe_set_status( $has_ftp ? 1 : 0 ); return parent::scan(); } /** Tools. ================================================================================== */ /** * Test if a password is in our dictionary. * * @since 1.0 * * @param (string) $password The password to test. * * @return (bool) */ public static function dictionary_attack( #[\SensitiveParameter] $password ) { $dictionary = file( SECUPRESS_INC_PATH . 'data/10kmostcommon.data', FILE_IGNORE_NEW_LINES ); return $dictionary ? in_array( $password, $dictionary, true ) : null; } } free/classes/scanners/class-secupress-scan-wp-config.php 0000644 00000030730 15174670627 0017421 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * `wp-config.php` scan class. * * @package SecuPress * @subpackage SecuPress_Scan * @since 1.0 */ class SecuPress_Scan_WP_Config extends SecuPress_Scan implements SecuPress_Scan_Interface { /** Constants. ============================================================================== */ /** * Class version. * * @var (string) */ const VERSION = '2.2.6'; /** Properties. ============================================================================= */ /** * The reference to the *Singleton* instance of this class. * * @var (object) */ protected static $_instance; protected $delayed_fix = 2; /** * Constants to test, with values to test against. * * @var (array) * @since 2.2.6 CONCATENATE_SCRIPTS */ protected $constants = array( 'ALLOW_UNFILTERED_UPLOADS' => false, 'DIEONDBERROR' => false, 'DISALLOW_FILE_EDIT' => 1, 'RELOCATE' => false, 'WP_ALLOW_REPAIR' => false, 'WP_DEBUG' => false, 'WP_DEBUG_DISPLAY' => false, 'CONCATENATE_SCRIPTS' => false, ); /** Init and messages. ====================================================================== */ /** * Init. * * @since 1.0 */ protected function init() { /** Translators: %s is a file name. */ $this->title = sprintf( __( 'Check your %s file, especially the PHP constants.', 'secupress' ), '<code>' . secupress_get_wpconfig_filename() . '</code>' ); /** Translators: %s is a file name. */ $this->more = sprintf( __( 'You can use the %s file to improve the security of your website.', 'secupress' ), '<code>' . secupress_get_wpconfig_filename() . '</code>' ); /** Translators: %s is a file name. */ $this->more_fix = sprintf( __( 'Set some PHP constants in your %s file to improve the security of your website.', 'secupress' ), '<code>' . secupress_get_wpconfig_filename() . '</code>' ); } /** * Get messages. * * @since 1.0 * * @param (int) $message_id A message ID. * * @return (string|array) A message if a message ID is provided. An array containing all messages otherwise. */ public static function get_messages( $message_id = null ) { $messages = array( // "good" /** Translators: %s is a file name. */ 0 => sprintf( __( 'Your %s file is correct.', 'secupress' ), secupress_code_me( secupress_get_wpconfig_filename() ) ), /** Translators: %s is a constant name. */ 1 => sprintf( __( 'A <a href="https://codex.wordpress.org/Must_Use_Plugins" hreflang="en">must-use plugin</a> has been added in order to change the default value for %s.', 'secupress' ), '<code>COOKIEHASH</code>' ), // "warning" 100 => __( 'This fix is <strong>pending</strong>, please reload the page to apply it now.', 'secupress' ), // "bad" /** Translators: %s is a constant name. */ 201 => sprintf( __( 'The PHP constant %s is defined with the default value, it should be modified.', 'secupress' ), '<code>COOKIEHASH</code>' ), /** Translators: 1 is a file name, 2 is a constant name. */ 202 => sprintf( __( 'In your %1$s file, the PHP constant %2$s should be set.', 'secupress' ), secupress_code_me( secupress_get_wpconfig_filename() ), '%s' ), 207 => _n_noop( /** Translators: 1 is a file name, 2 is a constant name. */ 'In your %1$s file, the PHP constant %2$s should not be set.', 'In your %1$s file, the PHP constants %2$s should not be set.', 'secupress' ), 208 => _n_noop( /** Translators: 1 is a file name, 2 is a constant name. */ 'In your %1$s file, the PHP constant %2$s should not be empty.', 'In your %1$s file, the PHP constants %2$s should not be empty.', 'secupress' ), 209 => _n_noop( /** Translators: 1 is a file name, 2 is a constant name, 3 is a value. */ 'In your %1$s file, the PHP constant %2$s should be set to %3$s.', 'In your %1$s file, the PHP constants %2$s should be set to %3$s.', 'secupress' ), 210 => _n_noop( /** Translators: 1 is a file name, 2 is a constant name, 3 is a value. */ 'In your %1$s file, the PHP constant %2$s should be set to %3$s.', 'In your %1$s file, the PHP constants %2$s should be set to %3$s.', 'secupress' ), // 209 and 210 are identical. 211 => _n_noop( /** Translators: 1 is a file name, 2 is a constant name, 3 is a value. */ 'In your %1$s file, the PHP constant %2$s should be set to %3$s or less.', 'In your %1$s file, the PHP constants %2$s should be set to %3$s or less.', 'secupress' ), 212 => _n_noop( /** Translators: 1 is a file name, 2 is a constant name, 3 is a value. */ 'In your %1$s file, the PHP constant %2$s should be set to %3$s or less.', 'In your %1$s file, the PHP constants %2$s should be set to %3$s or less.', 'secupress' ), // 211 and 212 are identical. // "cantfix" /** Translators: %s is a list of constant names. */ 300 => __( 'Some PHP constants could not be set correctly: %s.', 'secupress' ), /** Translators: %s is a constant name. */ 301 => sprintf( __( 'Impossible to create a <a href="https://codex.wordpress.org/Must_Use_Plugins">must-use plugin</a> but the default value for %s needs to be changed.', 'secupress' ), '<code>COOKIEHASH</code>' ), 302 => sprintf( __( 'The %s file is not writable, the constants could not be changed.', 'secupress' ), secupress_code_me( secupress_get_wpconfig_filename() ) ), ); if ( isset( $message_id ) ) { return isset( $messages[ $message_id ] ) ? $messages[ $message_id ] : __( 'Unknown message', 'secupress' ); } return $messages; } /** Getters. ================================================================================ */ /** * Get the documentation URL. * * @since 1.2.3 * * @return (string) */ public static function get_docs_url() { return __( 'https://docs.secupress.me/article/93-wp-config-php-file-constants-scan', 'secupress' ); } /** Scan. =================================================================================== */ /** * Scan for flaw(s). * * @since 1.0 * * @return (array) The scan results. */ public function scan() { $activated = $this->filter_scanner( __CLASS__ ); if ( true === $activated ) { $this->add_message( 0 ); return parent::scan(); } SecuPress_Scanner_Results::update_fix_result( 'wp_config', false ); // COOKIEHASH. $check = defined( 'COOKIEHASH' ) && COOKIEHASH === md5( get_site_option( 'siteurl' ) ); if ( $check ) { // "bad" $this->add_message( 201 ); } // Other constants. $results = []; foreach ( $this->constants as $constant => $compare ) { $check = defined( $constant ) ? constant( $constant ) : null; switch ( $compare ) { case 1: if ( is_null( $check ) || ! $check ) { $results[209] = isset( $results[209] ) ? $results[209] : []; $results[209]['true'] = isset( $results[209]['true'] ) ? $results[209]['true'] : []; $results[209]['true'][] = '<code>' . $constant . '</code>'; } break; case false: if ( is_null( $check ) || $check ) { $results[210] = isset( $results[210] ) ? $results[210] : []; $results[210]['false'] = isset( $results[210]['false'] ) ? $results[210]['false'] : []; $results[210]['false'][] = '<code>' . $constant . '</code>'; } break; } } if ( $results ) { foreach ( $results as $message_id => $maybe_constants ) { $first = reset( $maybe_constants ); if ( is_array( $first ) ) { foreach ( $maybe_constants as $compare => $constants ) { // "bad" $this->add_message( $message_id, array( count( $constants ), secupress_code_me( secupress_get_wpconfig_filename() ), $constants, secupress_code_me( $compare ) ) ); } } else { // "bad" $this->add_message( $message_id, array( count( $maybe_constants ), secupress_code_me( secupress_get_wpconfig_filename() ), $maybe_constants ) ); } } } // "good" $this->maybe_set_status( 0 ); return parent::scan(); } /** Fix. ==================================================================================== */ /** * Try to fix the flaw(s). * * @since 1.4.5 * * @return (array) The fix results. */ public function need_manual_fix() { return [ 'fix' => 'fix' ]; } /** * Get an array containing ALL the forms that would fix the scan if it requires user action. * * @since 1.4.5 * * @return (array) An array of HTML templates (form contents most of the time). */ protected function get_fix_action_template_parts() { return [ 'fix' => ' ' ]; } /** * Try to fix the flaw(s) after requiring user action. * * @since 1.4.5 * * @return (array) The fix results. */ public function manual_fix() { if ( $this->has_fix_action_part( 'fix' ) ) { $this->fix(); } // "good" $check = defined( 'COOKIEHASH' ) && COOKIEHASH === md5( get_site_option( 'siteurl' ) ); if ( $check ) { $this->add_fix_message( 1 ); } $this->add_fix_message( 0 ); return parent::manual_fix(); } /** * Try to fix the flaw(s). * * @since 1.0 * * @return (array) The fix results. */ public function fix() { global $current_user; if ( secupress_get_site_transient( 'secupress-cookiehash-muplugin-failed' ) && secupress_delete_site_transient( 'secupress-cookiehash-muplugin-failed' ) ) { // MU Plugin creation failed. $this->add_fix_message( 301 ); return parent::fix(); } if ( secupress_get_site_transient( 'secupress-cookiehash-muplugin-succeeded' ) && secupress_delete_site_transient( 'secupress-cookiehash-muplugin-succeeded' ) ) { // MU Plugin creation succeeded. $this->add_fix_message( 1 ); return parent::fix(); } // COOKIEHASH. $check = defined( 'COOKIEHASH' ) && COOKIEHASH === md5( get_site_option( 'siteurl' ) ); if ( $check ) { // "bad" $this->maybe_fix_by_plugin( 'COOKIEHASH' ); } // Other constants. $wpconfig_filepath = secupress_is_wpconfig_writable(); if ( ! $wpconfig_filepath ) { // "bad" $this->add_fix_message( 302 ); return parent::fix(); } $new_content = ''; $results = []; $not_fixed = []; foreach ( $this->constants as $constant => $compare ) { $check = defined( $constant ) ? constant( $constant ) : null; $replaced = false; switch ( $compare ) { case 1: if ( true !== $check && is_wp_error( $this->maybe_fix_by_plugin( $constant ) ) ) { $not_fixed[] = sprintf( '<code>%s</code>', $constant ); } break; case false: if ( false !== $check && is_wp_error( $this->maybe_fix_by_plugin( $constant ) ) ) { $not_fixed[] = sprintf( '<code>%s</code>', $constant ); } break; } } if ( $not_fixed ) { $this->add_fix_message( 300, array( $not_fixed ) ); } $this->maybe_set_fix_status( 0 ); return parent::fix(); } /** Tools. ================================================================================== */ /** * Tell if the fix for the given constant is a plugin (and activate it). * * @since 1.1.4 * * @param (string) $constant A constant name. * * @return (bool|object) False if no plugin, true if the plugin was successfully activated, a `WP_Error` object if the plugin returned an error. */ public function maybe_fix_by_plugin( $constant ) { global $wp_settings_errors; $has_plugin = array( 'ALLOW_UNFILTERED_UPLOADS' => 'unfiltered-uploads', 'COOKIEHASH' => 'cookiehash', 'DIEONDBERROR' => 'dieondberror', 'DISALLOW_FILE_EDIT' => 'file-edit', 'RELOCATE' => 'locations', 'WP_ALLOW_REPAIR' => 'repair', 'WP_DEBUG' => 'debugging', 'WP_DEBUG_DISPLAY' => 'debugging', 'WP_HOME' => 'locations', 'WP_SITEURL' => 'locations', 'CONCATENATE_SCRIPTS' => 'script-concat', ); if ( empty( $has_plugin[ $constant ] ) ) { return false; } secupress_deactivate_submodule_silently( 'wordpress-core', 'wp-config-constant-' . $has_plugin[ $constant ] ); secupress_activate_submodule( 'wordpress-core', 'wp-config-constant-' . $has_plugin[ $constant ] ); $last_error = is_array( $wp_settings_errors ) && $wp_settings_errors ? end( $wp_settings_errors ) : false; $error_codes = array( 'wp_config_not_writable' => 1, 'constant_not_removed' => 1, 'constant_not_added' => 1 ); if ( $last_error && 'general' === $last_error['setting'] && isset( $error_codes[ $last_error['code'] ] ) ) { array_pop( $wp_settings_errors ); return new WP_Error( $last_error['code'], $last_error['message'] ); } return true; } } free/classes/scanners/class-secupress-scan-core-update.php 0000644 00000014166 15174670627 0017745 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Core Update scan class. * * @package SecuPress * @subpackage SecuPress_Scan * @since 1.0 */ class SecuPress_Scan_Core_Update extends SecuPress_Scan implements SecuPress_Scan_Interface { /** Constants. ============================================================================== */ /** * Class version. * * @var (string) */ const VERSION = '1.2'; /** Properties. ============================================================================= */ /** * The reference to the *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** * Tells if the fix must occur after all other scans and fixes, while no other scan/fix is running. * * @var (bool) */ protected $delayed_fix = true; /** Init and messages. ====================================================================== */ /** * Init. * * @since 1.0 */ protected function init() { $this->title = __( 'Check if your WordPress core is up to date.', 'secupress' ); $this->more = __( 'It’s very important to keep your WordPress installation up to date. If you cannot update for any reason, contact your hosting provider as soon as possible.', 'secupress' ); $this->more_fix = __( 'Update your WordPress installation right away.', 'secupress' ); } /** * Get messages. * * @since 1.0 * * @param (int) $message_id A message ID. * * @return (string|array) A message if a message ID is provided. An array containing all messages otherwise. */ public static function get_messages( $message_id = null ) { $messages = array( // "good" 0 => __( 'WordPress core is up to date.', 'secupress' ), 1 => __( 'WordPress has been updated to version <strong>%s</strong>.', 'secupress' ), 2 => '%s', // Already translated. // "bad". 200 => __( 'WordPress core is <strong>not up to date</strong>.', 'secupress' ), // "cantfix" 300 => '%s', // Already translated. 301 => __( 'You have the latest version of WordPress.', 'secupress' ), ); if ( isset( $message_id ) ) { return isset( $messages[ $message_id ] ) ? $messages[ $message_id ] : __( 'Unknown message', 'secupress' ); } return $messages; } /** Getters. ================================================================================ */ /** * Get the documentation URL. * * @since 1.2.3 * * @return (string) */ public static function get_docs_url() { return __( 'https://docs.secupress.me/article/95-wordpress-core-update-scan', 'secupress' ); } /** Scan. =================================================================================== */ /** * Scan for flaw(s). * * @since 1.0 * * @return (array) The scan results. */ public function scan() { $activated = $this->filter_scanner( __CLASS__ ); if ( true === $activated ) { $this->add_message( 0 ); return parent::scan(); } ob_start(); if ( ! function_exists( 'get_preferred_from_update_core' ) ) { require_once( ABSPATH . 'wp-admin/includes/update.php' ); } wp_version_check(); $core_update = get_preferred_from_update_core(); $core_update = isset( $core_update->response ) && 'upgrade' === $core_update->response; ob_flush(); if ( $core_update ) { // "bad" $this->add_message( 200 ); } // "good" $this->maybe_set_status( 0 ); return parent::scan(); } /** Fix. ==================================================================================== */ /** * Try to fix the flaw(s). * * @since 1.4.5 * * @return (array) The fix results. */ public function need_manual_fix() { return [ 'fix' => 'fix' ]; } /** * Get an array containing ALL the forms that would fix the scan if it requires user action. * * @since 1.4.5 * * @return (array) An array of HTML templates (form contents most of the time). */ protected function get_fix_action_template_parts() { return [ 'fix' => ' ' ]; } /** * Try to fix the flaw(s) after requiring user action. * * @since 1.4.5 * * @return (array) The fix results. */ public function manual_fix() { if ( $this->has_fix_action_part( 'fix' ) ) { $this->fix(); } // "good" $this->add_fix_message( 1 ); return parent::manual_fix(); } /** * Try to fix the flaw(s). * * @since 1.0 * * @return (array) The fix results. */ public function fix() { ob_start(); secupress_time_limit( 0 ); $core = get_preferred_from_update_core(); $version = isset( $core->version ) ? $core->version : false; $locale = isset( $core->locale ) ? $core->locale : 'en_US'; $result = false; if ( $version ) { include_once( ABSPATH . 'wp-admin/includes/class-wp-upgrader.php' ); $url = 'update-core.php?action=do-core-upgrade'; $nonce = 'upgrade-core'; $url_nonce = wp_nonce_url( $url, $nonce ); $update = find_core_update( $version, $locale ); if ( $update ) { $allow_relaxed_file_ownership = isset( $update->new_files ) && ! $update->new_files; $credentials = request_filesystem_credentials( $url_nonce, '', false, ABSPATH, array( 'version', 'locale' ), $allow_relaxed_file_ownership ); if ( WP_Filesystem( $credentials, ABSPATH, $allow_relaxed_file_ownership ) ) { // Remove the WP upgrade process for translation since it will output data, use our own based on core but using a silent upgrade. remove_action( 'upgrader_process_complete', array( 'Language_Pack_Upgrader', 'async_upgrade' ), 20 ); add_action( 'upgrader_process_complete', 'secupress_async_upgrades', 20 ); $skin = new Automatic_Upgrader_Skin( compact( 'nonce', 'url' ) ); $upgrader = new Core_Upgrader( $skin ); $result = $upgrader->upgrade( $update, array( 'allow_relaxed_file_ownership' => $allow_relaxed_file_ownership, ) ); } } } ob_end_clean(); if ( is_string( $result ) ) { $this->add_fix_message( 1, array( $result ) ); } elseif ( false === $result ) { $this->add_fix_message( 301 ); } else { $errors = reset( $result->errors ); $code = isset( $errors['up_to_date'] ) ? 2 : 300; $this->add_fix_message( $code, array( reset( $errors ) ) ); } return parent::fix(); } } free/classes/scanners/class-secupress-scan-login-errors-disclose.php 0000644 00000011626 15174670627 0021760 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Login Errors Disclose scan class. * * @package SecuPress * @subpackage SecuPress_Scan * @since 1.0 */ class SecuPress_Scan_Login_Errors_Disclose extends SecuPress_Scan implements SecuPress_Scan_Interface { /** Constants. ============================================================================== */ /** * Class version. * * @var (string) */ const VERSION = '1.2'; /** Properties. ============================================================================= */ /** * The reference to the *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** Init and messages. ====================================================================== */ /** * Init. * * @since 1.0 */ protected function init() { $this->title = __( 'Check if your WordPress lists the some login errors.', 'secupress' ); $this->more = __( 'Error messages displayed on the login page are a useful information for an attacker: they should not be displayed, or at least, should be less specific.', 'secupress' ); $this->more_fix = __( 'Hide errors on login page to avoid being read by attackers.', 'secupress' ); } /** * Get messages. * * @since 1.0 * * @param (int) $message_id A message ID. * * @return (string|array) A message if a message ID is provided. An array containing all messages otherwise. */ public static function get_messages( $message_id = null ) { $messages = array( // "good" 0 => __( 'You are currently not displaying <strong>login errors</strong>.', 'secupress' ), 1 => __( 'Protection activated', 'secupress' ), // "bad" 200 => __( '<strong>Login errors</strong> should not be displayed.', 'secupress' ), ); if ( isset( $message_id ) ) { return isset( $messages[ $message_id ] ) ? $messages[ $message_id ] : __( 'Unknown message', 'secupress' ); } return $messages; } /** Getters. ================================================================================ */ /** * Get the documentation URL. * * @since 1.2.3 * * @return (string) */ public static function get_docs_url() { return __( 'https://docs.secupress.me/article/129-login-error-message-scan', 'secupress' ); } /** Scan. =================================================================================== */ /** * Scan for flaw(s). * * @since 1.0 * * @return (array) The scan results. */ public function scan() { $activated = $this->filter_scanner( __CLASS__ ); if ( true === $activated ) { $this->add_message( 0 ); return parent::scan(); } $messages = secupress_login_errors_disclose_get_messages( false ); $messages = ' ' . implode( "<br />\n ", $messages ) . "<br />\n"; $wp_error = new WP_Error(); $wp_error->add( 'invalid_username', $messages ); /** This filter is documented in wp-login.php */ $messages = apply_filters( 'login_errors', $wp_error ); while ( is_array( $messages ) ) { $messages = reset( $messages ); } $pattern = secupress_login_errors_disclose_get_messages(); $pattern = '@\s(' . implode( '|', $pattern ) . ')<br />\n@'; if ( is_array( $messages ) && preg_match( $pattern, $messages ) ) { // "bad" $this->add_message( 200 ); } else { // "good" $this->add_message( 0 ); } return parent::scan(); } /** Fix. ==================================================================================== */ /** * Try to fix the flaw(s). * * @since 1.4.5 * * @return (array) The fix results. */ public function need_manual_fix() { return [ 'fix' => 'fix' ]; } /** * Get an array containing ALL the forms that would fix the scan if it requires user action. * * @since 1.4.5 * * @return (array) An array of HTML templates (form contents most of the time). */ protected function get_fix_action_template_parts() { return [ 'fix' => ' ' ]; } /** * Try to fix the flaw(s) after requiring user action. * * @since 1.4.5 * * @return (array) The fix results. */ public function manual_fix() { if ( $this->has_fix_action_part( 'fix' ) ) { $this->fix(); } // "good" $this->add_fix_message( 1 ); return parent::manual_fix(); } /** * Try to fix the flaw(s). * * @since 1.0 * * @return (array) The fix results. */ public function fix() { $messages = secupress_login_errors_disclose_get_messages( false ); $messages = ' ' . implode( "<br />\n ", $messages ) . "<br />\n"; /** This filter is documented in wp-login.php */ $messages = apply_filters( 'login_errors', $messages ); $pattern = secupress_login_errors_disclose_get_messages(); $pattern = '@\s(' . implode( '|', $pattern ) . ')<br />\n@'; if ( preg_match( $pattern, $messages ) ) { secupress_activate_submodule( 'discloses', 'login-errors-disclose' ); // "good" $this->add_fix_message( 1 ); } else { // "good" $this->add_fix_message( 0 ); } return parent::fix(); } } free/classes/scanners/class-secupress-scan-bad-url-access.php 0000644 00000022340 15174670627 0020313 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Bad URL Access scan class. * * @package SecuPress * @subpackage SecuPress_Scan * @since 1.0 */ class SecuPress_Scan_Bad_URL_Access extends SecuPress_Scan implements SecuPress_Scan_Interface { /** Constants. ============================================================================== */ /** * Class version. * * @var (string) */ const VERSION = '1.2'; /** Properties. ============================================================================= */ /** * The reference to the *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** Init and messages. ====================================================================== */ /** * Init. * * @since 1.0 */ protected function init() { global $is_apache, $is_nginx, $is_iis7; $this->title = __( 'Check if some of your WordPress URLs disclose your site’s internal path.', 'secupress' ); $this->more = __( 'When an attacker wants to hack into a WordPress site, they will search for all available informations. The goal is to find something useful that will help him penetrate your site. Don’t let them easily find any informations using useful URLs.', 'secupress' ); $this->more_fix = sprintf( __( 'Activate the %1$s protection from the module %2$s.', 'secupress' ), '<strong>' . __( 'Bad URL Access', 'secupress' ) . '</strong>', '<a href="' . esc_url( secupress_admin_url( 'modules', 'sensitive-data' ) ) . '#row-content-protect_bad-url-access">' . __( 'Sensitive Data', 'secupress' ) . '</a>' ); if ( ! $is_apache && ! $is_nginx && ! $is_iis7 ) { $this->more_fix = static::get_messages( 301 ); $this->fixable = false; return; } } /** * Get messages. * * @since 1.0 * * @param (int) $message_id A message ID. * * @return (string|array) A message if a message ID is provided. An array containing all messages otherwise. */ public static function get_messages( $message_id = null ) { global $is_apache; $config_file = $is_apache ? '.htaccess' : 'web.config'; $messages = array( // "good" 0 => __( 'Your site does not reveal your site’s internal path.', 'secupress' ), /** Translators: %s is a file name. */ 1 => sprintf( __( 'Rules preventing disclosure of your site’s internal path disclosure have been added to your %s file.', 'secupress' ), "<code>$config_file</code>" ), // "warning" 100 => _n_noop( /** Translators: %s is a URL or a list of URLs. */ 'Unable to determine if %s is accessible.', 'Unable to determine if %s are accessible.', 'secupress' ), 101 => sprintf( /** Translators: 1 is the name of a protection, 2 is the name of a module. */ __( 'But you can activate the %1$s protection from the module %2$s.', 'secupress' ), '<strong>' . __( 'Bad URL Access', 'secupress' ) . '</strong>', '<a target="_blank" href="' . esc_url( secupress_admin_url( 'modules', 'sensitive-data' ) ) . '#row-content-protect_bad-url-access">' . __( 'Sensitive Data', 'secupress' ) . '</a>' ), // "bad" /** Translators: %s is a URL, or a list of URLs. */ 200 => _n_noop( 'This URL should not be accessible by anyone: %s', 'These URLs should not be accessible by anyone: %s', 'secupress' ), // "cantfix" /** Translators: 1 is a file name, 2 is some code. */ 300 => sprintf( __( 'Your server runs <strong>Nginx</strong>, the files that disclose your site’s internal path cannot be protected automatically but you can do it yourself by adding the following code to your %1$s file: %2$s', 'secupress' ), '<code>nginx.conf</code>', '%s' ), 301 => __( 'Your server runs an unrecognized system. The files that disclose your site’s internal path cannot be protected automatically.', 'secupress' ), /** Translators: 1 is a file name, 2 is some code. */ 302 => sprintf( __( 'Your %1$s file is not writable. Please add the following lines at the beginning of the file: %2$s', 'secupress' ), "<code>$config_file</code>", '%s' ), /** Translators: 1 is a file name, 2 is a folder path (kind of), 3 is some code. */ 303 => sprintf( __( 'Your %1$s file is not writable. Please add the following lines inside the tags hierarchy %2$s (create it if does not exist): %3$s', 'secupress' ), "<code>$config_file</code>", '%1$s', '%2$s' ), ); if ( isset( $message_id ) ) { return isset( $messages[ $message_id ] ) ? $messages[ $message_id ] : __( 'Unknown message', 'secupress' ); } return $messages; } /** Getters. ================================================================================ */ /** * Get the documentation URL. * * @since 1.2.3 * * @return (string) */ public static function get_docs_url() { return __( 'https://docs.secupress.me/article/107-sensitive-files-access-scan', 'secupress' ); } /** Scan. =================================================================================== */ /** * Scan for flaw(s). * * @since 1.0 * * @return (array) The scan results. */ public function scan() { $activated = $this->filter_scanner( __CLASS__ ); if ( true === $activated ) { $this->add_message( 0 ); return parent::scan(); } // Avoid plugin's hooks. remove_all_filters( 'site_url' ); remove_all_filters( 'includes_url' ); remove_all_filters( 'admin_url' ); remove_all_filters( 'home_url' ); $bads = array(); $warnings = array(); $urls = array( admin_url( 'install.php' ), admin_url( 'network/menu.php' ), admin_url( 'user/menu.php' ), includes_url( 'admin-bar.php' ), ); foreach ( $urls as $url ) { $response = wp_remote_get( $url, $this->get_default_request_args() ); if ( ! is_wp_error( $response ) && 200 === wp_remote_retrieve_response_code( $response ) ) { // "bad" $bads[] = "<code>$url</code>"; } } if ( $bads ) { // "bad" $this->add_message( 200, array( count( $bads ), $bads ) ); if ( ! $this->fixable ) { $this->add_pre_fix_message( 301 ); } } // "good" $this->maybe_set_status( 0 ); return parent::scan(); } /** Fix. ==================================================================================== */ /** * Try to fix the flaw(s). * * @since 1.4.5 * * @return (array) The fix results. */ public function need_manual_fix() { return [ 'fix' => 'fix' ]; } /** * Get an array containing ALL the forms that would fix the scan if it requires user action. * * @since 1.4.5 * * @return (array) An array of HTML templates (form contents most of the time). */ protected function get_fix_action_template_parts() { return [ 'fix' => ' ' ]; } /** * Try to fix the flaw(s) after requiring user action. * * @since 1.4.5 * * @return (array) The fix results. */ public function manual_fix() { if ( $this->has_fix_action_part( 'fix' ) ) { $this->fix(); } // "good" $this->add_fix_message( 1 ); return parent::manual_fix(); } /** * Try to fix the flaw(s). * * @since 1.0 * * @return (array) The fix results. */ public function fix() { global $is_apache, $is_nginx, $is_iis7; if ( $is_apache ) { $this->fix_apache(); } elseif ( $is_iis7 ) { $this->fix_iis7(); } elseif ( $is_nginx ) { $this->fix_nginx(); } // "good" $this->maybe_set_fix_status( 0 ); return parent::fix(); } /** * Fix for Apache system. * * @since 1.0 */ protected function fix_apache() { global $wp_settings_errors; secupress_activate_submodule( 'sensitive-data', 'bad-url-access' ); // Got error? $last_error = is_array( $wp_settings_errors ) && $wp_settings_errors ? end( $wp_settings_errors ) : false; if ( $last_error && 'general' === $last_error['setting'] && 'apache_manual_edit' === $last_error['code'] ) { $rules = static::get_rules_from_error( $last_error ); // "cantfix" $this->add_fix_message( 302, array( $rules ) ); array_pop( $wp_settings_errors ); return; } // "good" $this->add_fix_message( 1 ); } /** * Fix for IIS7 system. * * @since 1.0 */ protected function fix_iis7() { global $wp_settings_errors; secupress_activate_submodule( 'sensitive-data', 'bad-url-access' ); // Got error? $last_error = is_array( $wp_settings_errors ) && $wp_settings_errors ? end( $wp_settings_errors ) : false; if ( $last_error && 'general' === $last_error['setting'] && 'iis7_manual_edit' === $last_error['code'] ) { $rules = static::get_rules_from_error( $last_error ); $path = static::get_code_tag_from_error( $last_error, 'secupress-iis7-path' ); // "cantfix" $this->add_fix_message( 303, array( $path, $rules ) ); array_pop( $wp_settings_errors ); return; } // "good" $this->add_fix_message( 1 ); } /** * Fix for nginx system. * * @since 1.0 */ protected function fix_nginx() { global $wp_settings_errors; secupress_activate_submodule( 'sensitive-data', 'bad-url-access' ); // Get the error. $last_error = is_array( $wp_settings_errors ) && $wp_settings_errors ? end( $wp_settings_errors ) : false; $rules = '<code>Error</code>'; if ( $last_error && 'general' === $last_error['setting'] && 'nginx_manual_edit' === $last_error['code'] ) { $rules = static::get_rules_from_error( $last_error ); array_pop( $wp_settings_errors ); } // "cantfix" $this->add_fix_message( 300, array( $rules ) ); } } free/classes/scanners/class-secupress-scan-bad-old-files.php 0000644 00000013312 15174670627 0020127 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Bad Old Files scan class. * * @package SecuPress * @subpackage SecuPress_Scan * @since 1.0 */ class SecuPress_Scan_Bad_Old_Files extends SecuPress_Scan implements SecuPress_Scan_Interface { /** Constants. ============================================================================== */ /** * Class version. * * @var (string) */ const VERSION = '1.2'; /** Properties. ============================================================================= */ /** * The reference to the *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** Init and messages. ====================================================================== */ /** * Init. * * @since 1.0 */ protected function init() { global $_old_files; require_once( ABSPATH . 'wp-admin/includes/update-core.php' ); $this->title = __( 'Check if your installation still contains old WordPress files.', 'secupress' ); $this->more = sprintf( __( 'Since WordPress 2.0, about %s files were deleted, let’s check if your website needs a clean up.', 'secupress' ), number_format_i18n( count( $_old_files ) ) ); $this->more_fix = __( 'Delete all old files because your actual installation does not need it.', 'secupress' ); // Add wp-config-sample.php into $_old_files. add_filter( 'secupress._old_files', function( $files ) { $files[] = 'wp-config-sample.php'; return $files; } ); } /** * Get messages. * * @since 1.0 * * @param (int) $message_id A message ID. * * @return (string|array) A message if a message ID is provided. An array containing all messages otherwise. */ public static function get_messages( $message_id = null ) { $messages = array( // "good" 0 => __( 'Your installation is free of old files.', 'secupress' ), 1 => __( 'All old files have been deleted.', 'secupress' ), // "bad" /** Translators: 1 is a number, 2 is a file name (or a list of file names). */ 200 => _n_noop( 'Your installation contains <strong>%1$d old file</strong>: %2$s.', 'Your installation contains <strong>%1$d old files</strong>: %2$s.', 'secupress' ), 201 => _n_noop( 'The following file could not be deleted: %s.', 'The following files could not be deleted: %s.', 'secupress' ), ); if ( isset( $message_id ) ) { return isset( $messages[ $message_id ] ) ? $messages[ $message_id ] : __( 'Unknown message', 'secupress' ); } return $messages; } /** Getters. ================================================================================ */ /** * Get the documentation URL. * * @since 1.2.3 * * @return (string) */ public static function get_docs_url() { return __( 'https://docs.secupress.me/article/97-legacy-files-scan', 'secupress' ); } /** Scan. =================================================================================== */ /** * Scan for flaw(s). * * @since 1.0 * * @return (array) The scan results. */ public function scan() { $activated = $this->filter_scanner( __CLASS__ ); if ( true === $activated ) { $this->add_message( 0 ); return parent::scan(); } global $_old_files; require_once( ABSPATH . 'wp-admin/includes/update-core.php' ); $bads = array(); if ( empty( $_old_files ) || ! is_array( $_old_files ) ) { // Should not happen. $this->add_fix_message( 0 ); return parent::fix(); } /** * Filter the old WP files to add more file (don’t remove!) */ $_old_files = apply_filters( 'secupress._old_files', $_old_files ); if ( ! empty( $_old_files ) ) { foreach ( $_old_files as $file ) { if ( @file_exists( ABSPATH . $file ) ) { // "bad" $bads[] = sprintf( '<code>%s</code>', $file ); } } } if ( $count = count( $bads ) ) { // "bad" $this->slice_and_dice( $bads, 10 ); $this->add_message( 200, array( $count, $count, $bads ) ); } else { // "good" $this->add_message( 0 ); } return parent::scan(); } /** Fix. ==================================================================================== */ /** * Try to fix the flaw(s). * * @since 1.4.5 * * @return (array) The fix results. */ public function need_manual_fix() { return [ 'fix' => 'fix' ]; } /** * Get an array containing ALL the forms that would fix the scan if it requires user action. * * @since 1.4.5 * * @return (array) An array of HTML templates (form contents most of the time). */ protected function get_fix_action_template_parts() { return [ 'fix' => ' ' ]; } /** * Try to fix the flaw(s) after requiring user action. * * @since 1.4.5 * * @return (array) The fix results. */ public function manual_fix() { if ( $this->has_fix_action_part( 'fix' ) ) { $this->fix(); } // "good" $this->add_fix_message( 1 ); return parent::manual_fix(); } /** * Try to fix the flaw(s). * * @since 1.0 * * @return (array) The fix results. */ public function fix() { global $_old_files; require_once( ABSPATH . 'wp-admin/includes/update-core.php' ); $not_deleted = []; /** * Filter the old WP files to add more file (don’t remove!) */ $_old_files = apply_filters( 'secupress._old_files', $_old_files ); $filesystem = secupress_get_filesystem(); foreach ( $_old_files as $file ) { if ( $filesystem->exists( ABSPATH . $file ) && ( ! wp_is_writable( ABSPATH . $file ) || ! $filesystem->delete( ABSPATH . $file ) ) ) { $not_deleted[] = sprintf( '<code>%s</code>', $file ); } } if ( $count = count( $not_deleted ) ) { // "bad" $this->slice_and_dice( $not_deleted, 10 ); $this->add_fix_message( 201, array( $count, $count, $not_deleted ) ); } else { // "good" $this->add_fix_message( 1 ); } return parent::fix(); } } free/classes/scanners/class-secupress-scan-bad-old-themes.php 0000644 00000037724 15174670627 0020327 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Bad Old Themes scan class. * * @package SecuPress * @subpackage SecuPress_Scan * @since 2.2.6 */ class SecuPress_Scan_Bad_Old_Themes extends SecuPress_Scan implements SecuPress_Scan_Interface { /** Constants. ============================================================================== */ /** * Class version. * * @var (string) */ const VERSION = '2.2.6'; /** Properties. ============================================================================= */ /** * The reference to the *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** Init and messages. ====================================================================== */ /** * Init. * * @since 2.2.6 */ protected function init() { $this->title = __( 'Check if you are using themes that have been deleted from the official repository or have not been updated for at least two years.', 'secupress' ); $this->more = __( 'Do not use a theme that has been closed on the official repository, and prevent usage of themes that have not been maintained for two years at least.', 'secupress' ); if ( is_network_admin() ) { $this->more_fix = __( 'Select closed and old themes to be deleted.', 'secupress' ); $this->more_fix .= '<br/>' . __( 'Not fixable on Multisite.', 'secupress' ); $this->fixable = false; } elseif ( ! is_multisite() ) { $this->more_fix = __( 'Select and delete closed and old themes', 'secupress' ); } else { $this->more_fix = __( 'Delete closed and old themes', 'secupress' ); } } /** * Get messages. * * @since 2.2.6 * * @param (int) $message_id A message ID. * * @return (string|array) A message if a message ID is provided. An array containing all messages otherwise. */ public static function get_messages( $message_id = null ) { $_110 = ! secupress_is_pro() ? sprintf( __( 'The %sPRO version%s will be more accurate.', 'secupress' ), '<a href="' . secupress_admin_url( 'get-pro' ) . '">', '</a>' ) : ' ' . __( 'Scan it now.', 'secupress' ); $messages = [ // "good" 0 => __( 'You don’t use closed or old themes', 'secupress' ), 1 => __( 'You don’t use closed or old themes anymore.', 'secupress' ), 2 => __( 'All deletable closed or old themes have been deleted.', 'secupress' ), /** Translators: %s is a file name. */ 100 => __( 'Error, could not read %s.', 'secupress' ), 101 => __( 'No themes selected for deletion.', 'secupress' ), 102 => _n_noop( 'Selected theme has been deleted (but some are still there).', 'All selected themes have been deleted (but some are still there).', 'secupress' ), 103 => _n_noop( 'Sorry, the following theme could not be deleted: %s.', 'Sorry, the following themes could not be deleted: %s.', 'secupress' ), /** Translators: %s is the theme name. */ 104 => sprintf( __( 'You have a big network, %s must work on some data before being able to perform this scan.', 'secupress' ), '<strong>' . SECUPRESS_PLUGIN_NAME . '</strong>' ), 110 => sprintf( __( 'Your installation may contain old or closed themes.%s', 'secupress' ), $_110 ), // "bad" /** Translators: 1 is a number, 2 is a theme name (or a list of theme names). */ 200 => _n_noop( '<strong>%1$d theme</strong> is no longer in the WordPress repository: %2$s.', '<strong>%1$d themes</strong> are no longer in the WordPress repository: %2$s.', 'secupress' ), /** Translators: 1 is a number, 2 is a theme name (or a list of theme names). */ 201 => _n_noop( '<strong>%1$d theme</strong> has not been updated for at least 2 years: %2$s.', '<strong>%1$d themes</strong> have not been updated for at least 2 years: %2$s.', 'secupress' ), /** Translators: %s is a theme name. */ 202 => _n_noop( 'The following theme should be deleted if you don’t need it: %s.', 'The following themes should be deleted if you don’t need them: %s.', 'secupress' ), 203 => _n_noop( 'Sorry, this theme could not be deleted.', 'Sorry, those themes could not be deleted.', 'secupress' ), // "cantfix" 301 => __( 'No themes selected.', 'secupress' ), 302 => __( 'Unable to locate WordPress Theme folder', 'secupress' ), /** Translators: %s is the theme name. */ 303 => sprintf( __( 'A new %s menu item has been activated in the relevant site’s administration area to let Administrators know which themes to delete', 'secupress' ), '<strong>' . SECUPRESS_PLUGIN_NAME . '</strong>' ), ]; if ( isset( $message_id ) ) { return isset( $messages[ $message_id ] ) ? $messages[ $message_id ] : __( 'Unknown message', 'secupress' ); } return $messages; } /** Getters. ================================================================================ */ /** * Get the documentation URL. * * @since 2.2.6 * * @return (string) */ public static function get_docs_url() { return __( 'https://docs.secupress.me/article/232-outdated-closed-and-vulnerable-theme-check', 'secupress' ); } /** Scan. =================================================================================== */ /** * Scan for flaw(s). * * @since 2.2.6 * * @return (array) The scan results. */ public function scan() { $activated = $this->filter_scanner( __CLASS__ ); if ( true === $activated ) { $this->add_message( 0 ); return parent::scan(); } if ( ! static::are_centralized_blog_options_filled() ) { // "warning" $this->add_message( 104 ); return parent::scan(); } // Multisite, for the current site. if ( $this->is_for_current_site() ) { // Themes no longer in directory or not updated in over 2 years. $bad_themes = $this->get_installed_themes_to_remove(); if ( $count = count( $bad_themes ) ) { // "bad" $this->add_message( 202, array( $count, $bad_themes ) ); } } // Network admin or not Multisite. else { // If we're in a sub-site, don't list the themes enabled in the network. $to_keep = []; // Themes no longer in directory. $bad_themes = $this->get_closed_themes(); $count = is_array( $bad_themes ) ? count( $bad_themes ) : false; if ( false === $count ) { // "warning" if ( secupress_is_pro() ) { $this->add_message( 100, array( _x( 'data', 'Error, could not read data.', 'secupress' ) ) ); } else { $this->add_message( 110 ); } } if ( $count > 0 ) { // "bad" $this->add_message( 200, array( $count, $count, self::wrap_in_tag( $bad_themes ) ) ); } // Themes not updated in over 2 years. $bad_themes = $this->get_old_themes(); $bad_themes = $to_keep ? array_diff_key( $bad_themes, $to_keep ) : $bad_themes; $count = is_array( $bad_themes ) ? count( $bad_themes ) : false; if ( false === $count ) { // "warning" if ( secupress_is_pro() ) { $this->add_message( 100, array( _x( 'data', 'Error, could not read data.', 'secupress' ) ) ); } else { $this->add_message( 110 ); } } if ( $count > 0 ) { // "bad" $this->add_message( 201, array( $count, $count, self::wrap_in_tag( $bad_themes ) ) ); } } // "good" $this->maybe_set_status( 0 ); return parent::scan(); } /** Fix. ==================================================================================== */ /** * Try to fix the flaw(s). * * @since 2.2.6 * * @return (array) The fix results. */ public function fix() { // "good" $this->add_fix_message( 1 ); return parent::fix(); } /** Manual fix. ============================================================================= */ /** * Return an array of actions if a manual fix is needed here. * * @since 2.2.6 * * @return (array) */ public function need_manual_fix() { $bad_themes = $this->get_installed_themes_to_remove(); $actions = []; if ( count( $bad_themes ) ) { $actions['delete-bad-old-themes'] = 'delete-bad-old-themes'; } return $actions; } /** * Try to fix the flaw(s) after requiring user action. * * @since 2.2.6 * * @return (array) The fix results. */ public function manual_fix() { $bad_themes = $this->get_installed_themes_to_remove(); if ( count( $bad_themes ) ) { // DELETE THEMES. if ( $this->has_fix_action_part( 'delete-bad-old-themes' ) ) { $delete = $this->manual_delete( $bad_themes ); } if ( ! empty( $delete ) ) { // "cantfix": nothing selected in lists. $this->add_fix_message( $delete ); } } else { // "good" $this->add_fix_message( 1 ); } return parent::manual_fix(); } /** * Manual fix to delete themes. * * @since 2.2.6 * * @param (array) $bad_themes An array of bad_themes to delete. Values must be sanitized before. */ protected function manual_delete( $bad_themes ) { if ( ! $bad_themes ) { // "good" return $this->add_fix_message( 1 ); } // Get the list of bad_themes to uninstall. $selected_themes = ! empty( $_POST['secupress-fix-delete-bad-old-themes'] ) && is_array( $_POST['secupress-fix-delete-bad-old-themes'] ) ? array_filter( $_POST['secupress-fix-delete-bad-old-themes'] ) : []; // WPCS: CSRF ok. $selected_themes = $selected_themes ? array_fill_keys( $selected_themes, 1 ) : []; $selected_themes = $selected_themes ? array_intersect_key( $bad_themes, $selected_themes ) : []; // Sanitize submitted values. if ( ! $selected_themes ) { if ( $this->has_fix_action_part( 'delete-bad-old-themes' ) ) { /* * "warning": no themes selected. * No `add_fix_message()`, we need to change the status from warning to cantfix if both lists have no selection. */ return 101; } // "cantfix": no themes selected. return $this->add_fix_message( 301 ); } // Get filesystem. $wp_filesystem = secupress_get_filesystem(); // Get the base theme folder. $themes_dir = $wp_filesystem->wp_themes_dir(); if ( empty( $themes_dir ) ) { // "cantfix": themes dir not located. return $this->add_fix_message( 302 ); } $themes_dir = trailingslashit( $themes_dir ); $themes_translations = wp_get_installed_translations( 'themes' ); ob_start(); $deleted_themes = []; foreach ( $selected_themes as $theme_file => $dummy ) { /** This action is documented in wp-admin/includes/theme.php */ do_action( 'delete_theme', $theme_file ); $this_theme_dir = trailingslashit( dirname( $themes_dir . $theme_file ) ); // If theme is in its own directory, recursively delete the directory. if ( strpos( $theme_file, '/' ) && $this_theme_dir !== $themes_dir ) { // base check on if theme includes directory separator AND that its not the root theme folder. $deleted = $wp_filesystem->delete( $this_theme_dir, true ); } else { $deleted = $wp_filesystem->delete( $themes_dir . $theme_file ); } /** This action is documented in wp-admin/includes/theme.php */ do_action( 'deleted_theme', $theme_file, $deleted ); if ( $deleted ) { $deleted_themes[ $theme_file ] = 1; // Remove language files, silently. $theme_slug = dirname( $theme_file ); if ( '.' !== $theme_slug && ! empty( $themes_translations[ $theme_slug ] ) ) { $translations = $themes_translations[ $theme_slug ]; foreach ( $translations as $translation => $data ) { $wp_filesystem->delete( WP_LANG_DIR . '/themes/' . $theme_slug . '-' . $translation . '.po' ); $wp_filesystem->delete( WP_LANG_DIR . '/themes/' . $theme_slug . '-' . $translation . '.mo' ); } } } } ob_end_clean(); // Everything's deleted, no themes left. if ( ! array_diff_key( $bad_themes, $deleted_themes ) ) { // "good" $this->add_fix_message( 2 ); } // All selected themes deleted. elseif ( ! array_diff_key( $deleted_themes, $selected_themes ) ) { // "partial": some themes still need to be deleted. $this->add_fix_message( 102, array( count( $selected_themes ) ) ); } // No themes deleted. elseif ( ! $deleted_themes ) { // "bad" $this->add_fix_message( 203, array( count( $bad_themes ) ) ); } // Some themes could not be deleted. else { // "cantfix" $not_removed = array_diff_key( $selected_themes, $deleted_themes ); $not_removed = array_map( 'strip_tags', $not_removed ); $not_removed = array_map( 'esc_html', $not_removed ); $this->add_fix_message( 103, array( count( $not_removed ), $not_removed ) ); } // Force refresh of theme update information and cache. if ( $deleted_themes ) { if ( $current = get_site_transient( 'update_themes' ) ) { $current->response = array_diff_key( $current->response, $deleted_themes ); $current->no_update = array_diff_key( $current->no_update, $deleted_themes ); set_site_transient( 'update_themes', $current ); } wp_cache_delete( 'themes', 'themes' ); } } /** * Get an array containing ALL the forms that would fix the scan if it requires user action. * * @since 2.2.6 * * @return (array) An array of HTML templates (form contents most of the time). */ protected function get_fix_action_template_parts() { $themes = $this->get_installed_themes_to_remove(); $out = [ 'delete-bad-old-themes' => static::get_messages( 1 ), ]; if ( count( $themes ) ) { $form = '<h4 id="secupress-fix-bad-old-themes">' . __( 'Checked themes will be deleted:', 'secupress' ) . '</h4>'; $form .= '<fieldset aria-labelledby="secupress-fix-bad-old-themes" class="secupress-boxed-group">'; $theme = wp_get_theme(); foreach ( $themes as $theme_slug => $theme_name ) { $theme_name = esc_html( strip_tags( $theme_name ) ); if ( $theme_slug !== $theme->template && $theme_slug !== $theme->stylesheet ) { $form .= '<input type="checkbox" id="secupress-fix-delete-bad-old-themes-' . sanitize_html_class( $theme_slug ) . '" name="secupress-fix-delete-bad-old-themes[]" value="' . esc_attr( $theme_slug ) . '" checked="checked"' . '/> '; } else { $form .= '<input type="checkbox" id="secupress-fix-delete-bad-old-themes-' . sanitize_html_class( $theme_slug ) . '" disabled="disabled"' . '/>'; } $form .= '<label for="secupress-fix-delete-bad-old-themes-' . sanitize_html_class( $theme_slug ) . '">'; $form .= "<strong>$theme_name</strong>"; if ( $theme_slug === $theme->template || $theme_slug === $theme->stylesheet ) { $form .= ' <em>(' . __( 'Active theme, switch your theme before!', 'secupress' ) . ')</em>'; } $form .= '</label><br/>'; } $form .= '</fieldset>'; $out['delete-bad-old-themes'] = $form; } return $out; } /** Tools. ================================================================================== */ /** * Get all themes to delete. * * @since 2.2.6 * * @return (array). */ final protected function get_installed_themes_to_remove() { $themes = []; // Themes no longer in directory. $tmp = $this->get_closed_themes( true ); if ( $tmp ) { $themes = $tmp; } // Themes not updated in over 2 years. $tmp = $this->get_old_themes( true ); if ( $tmp ) { $themes = array_merge( $themes, $tmp ); } return $themes; } /** * Get themes no longer in directory. * * @since 2.2.6 * * @param (bool) $for_fix False: for scan. True: for fix. * * @return (array). */ final protected function get_closed_themes( $for_fix = false ) { return $this->get_installed_bad_themes( 'closed', $for_fix ); } /** * Get themes not updated in over 2 years. * * @since 2.2.6 * * @param (bool) $for_fix False: for scan. True: for fix. * * @return (array). */ final protected function get_old_themes( $for_fix = false ) { return $this->get_installed_bad_themes( 'old', $for_fix ); } /** * Get an array of installed "bad" themes * * @since 2.2.6 * * @param (string) $themes_type "closed" or "old". * @param (bool) $for_fix False: for scan. True: for fix. * * @return (array) An array like `array( path => theme_name, path => theme_name )`. */ final protected function get_installed_bad_themes( $themes_type, $for_fix = false ) { $bad_themes = secupress_get_bad_themes( $themes_type ); if ( ! $bad_themes ) { return []; } $all_themes = wp_get_themes(); $bad_themes = array_intersect_key( $all_themes, $bad_themes ); $bad_themes = wp_list_pluck( $bad_themes, 'Name' ); return $bad_themes; } } free/classes/scanners/class-secupress-scan-bad-user-agent.php 0000644 00000012511 15174670627 0020323 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Bad User Agent scan class. * * @package SecuPress * @subpackage SecuPress_Scan * @since 1.0 */ class SecuPress_Scan_Bad_User_Agent extends SecuPress_Scan implements SecuPress_Scan_Interface { /** Constants. ============================================================================== */ /** * Class version. * * @var (string) */ const VERSION = '1.2'; /** Properties. ============================================================================= */ /** * The reference to the *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** Init and messages. ====================================================================== */ /** * Init. * * @since 1.0 */ protected function init() { $this->title = __( 'Check if bad User Agents can visit your website.', 'secupress' ); $this->more = sprintf( __( 'Bad User Agents are bots that provide no value to the website. They include scrapers, spambots, email harvesters and more bots that you don’t want on your site. Those bots that are crawling with a malicious purpose, have no desire to follow any %s or meta tag.', 'secupress' ), secupress_code_me( 'robots.txt' ) ); $this->more_fix = sprintf( __( 'Activate the option %1$s in the %2$s module.', 'secupress' ), '<em>' . __( 'Block Bad User Agents', 'secupress' ) . '</em>', '<a href="' . esc_url( secupress_admin_url( 'modules', 'firewall' ) ) . '#row-bbq-headers_user-agents-header">' . __( 'Firewall', 'secupress' ) . '</a>' ); } /** * Get messages. * * @since 1.0 * * @param (int) $message_id A message ID. * * @return (string|array) A message if a message ID is provided. An array containing all messages otherwise. */ public static function get_messages( $message_id = null ) { /** Translators: 1 is the name of a protection, 2 is the name of a module. */ $activate_protection_message = sprintf( __( 'But you can activate the %1$s protection from the module %2$s.', 'secupress' ), '<strong>' . __( 'Block bad User Agents', 'secupress' ) . '</strong>', '<a target="_blank" href="' . esc_url( secupress_admin_url( 'modules', 'firewall' ) ) . '#row-bbq-headers_user-agents-header">' . __( 'Firewall', 'secupress' ) . '</a>' ); $messages = array( // "good" 0 => __( 'You are currently blocking bad User Agents.', 'secupress' ), 1 => __( 'Protection activated', 'secupress' ), // "warning" 100 => __( 'Unable to determine if your homepage is accessible by bad User Agents.', 'secupress' ) . ' ' . $activate_protection_message, // "bad" 200 => __( 'Your website should block <code>HTTP</code> requests for <strong>bad User Agents</strong>.', 'secupress' ), ); if ( isset( $message_id ) ) { return isset( $messages[ $message_id ] ) ? $messages[ $message_id ] : __( 'Unknown message', 'secupress' ); } return $messages; } /** Getters. ================================================================================ */ /** * Get the documentation URL. * * @since 1.2.3 * * @return (string) */ public static function get_docs_url() { return __( 'https://docs.secupress.me/article/108-bad-user-agent-scan', 'secupress' ); } /** Scan. =================================================================================== */ /** * Scan for flaw(s). * * @since 1.0 * * @return (array) The scan results. */ public function scan() { $activated = $this->filter_scanner( __CLASS__ ); if ( true === $activated ) { $this->add_message( 0 ); return parent::scan(); } $request_args = $this->get_default_request_args(); $request_args['user-agent'] = '<script>'; $response = wp_remote_get( add_query_arg( secupress_generate_key( 6 ), secupress_generate_key( 8 ), user_trailingslashit( home_url() ) ), $request_args ); if ( ! is_wp_error( $response ) ) { if ( 200 === wp_remote_retrieve_response_code( $response ) ) { if ( 'SecuPress_Scan_Bad_User_Agent OK' !== wp_remote_retrieve_body( $response ) ) { // "bad" $this->add_message( 200 ); } } else { // "good" $this->add_message( 0 ); } } // Good. $this->maybe_set_status( 0 ); return parent::scan(); } /** Fix. ==================================================================================== */ /** * Try to fix the flaw(s). * * @since 1.4.5 * * @return (array) The fix results. */ public function need_manual_fix() { return [ 'fix' => 'fix' ]; } /** * Get an array containing ALL the forms that would fix the scan if it requires user action. * * @since 1.4.5 * * @return (array) An array of HTML templates (form contents most of the time). */ protected function get_fix_action_template_parts() { return [ 'fix' => ' ' ]; } /** * Try to fix the flaw(s) after requiring user action. * * @since 1.4.5 * * @return (array) The fix results. */ public function manual_fix() { if ( $this->has_fix_action_part( 'fix' ) ) { $this->fix(); } // "good" $this->add_fix_message( 1 ); return parent::manual_fix(); } /** * Try to fix the flaw(s). * * @since 1.0 * * @return (array) The fix results. */ public function fix() { // Activate. secupress_activate_submodule( 'firewall', 'user-agents-header' ); // "good" $this->add_fix_message( 1 ); return parent::fix(); } } free/classes/scanners/class-secupress-scan-admin-user.php 0000644 00000026001 15174670627 0017570 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Admin User scan class. * * @package SecuPress * @subpackage SecuPress_Scan * @since 1.0 */ class SecuPress_Scan_Admin_User extends SecuPress_Scan implements SecuPress_Scan_Interface { /** Constants. ============================================================================== */ /** * Class version. * * @var (string) */ const VERSION = '1.0.1'; /** Properties. ============================================================================= */ /** * The reference to the *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** Init and messages. ====================================================================== */ /** * Init. * * @since 1.0 */ protected function init() { $current_user = wp_get_current_user(); $this->title = sprintf( __( 'Check if the %s account is correctly protected.', 'secupress' ), secupress_tag_me( 'admin', 'em' ) ); $this->more = sprintf( __( 'It is important to protect the famous %s account to prevent simple brute-force attacks on it. This account is usually the first one created when you install WordPress, and it is well known by attackers.', 'secupress' ), secupress_tag_me( 'admin', 'em' ) ); if ( 'admin' === $current_user->user_login ) { $this->more_fix = __( 'You will be asked for a new username and your account will be renamed.', 'secupress' ); } else { $this->more_fix = sprintf( __( 'Remove all roles and capabilities from the %s account if it exists. If it does not exist and user subscriptions are open, the account will be created with no role nor capabilities.', 'secupress' ), secupress_tag_me( 'admin', 'em' ) ); } } /** * Get messages. * * @since 1.0 * * @param (int) $message_id A message ID. * * @return (string|array) A message if a message ID is provided. An array containing all messages otherwise. */ public static function get_messages( $message_id = null ) { $messages = array( // "good" 0 => __( 'The %s account is correctly protected.', 'secupress' ), 1 => __( 'The %s account has no role anymore.', 'secupress' ), // "warning" 100 => __( 'This fix is <strong>pending</strong>, please reload the page to apply it now.', 'secupress' ), // "bad" 200 => __( 'The %s account should have no role at all.', 'secupress' ), 201 => __( 'Because user registrations are open, the %s account should exist (with no role) to prevent someone from registering it.', 'secupress' ), 202 => __( 'Sorry, the username %s is forbidden!', 'secupress' ), 203 => __( 'Cannot create a user with an empty login name!' ), // WPi18n. 204 => __( 'Sorry, the username %s already exists!', 'secupress' ), 205 => __( 'The username %1$s is invalid because it uses illegal characters. Spot the differences: %2$s.', 'secupress' ), 206 => __( 'Sorry, the role cannot be removed from the %s account. You should try to remove it manually.', 'secupress' ), 207 => __( 'Sorry, the %s account could not be created. You should try to create it manually and then remove its role.', 'secupress' ), // "cantfix" 300 => __( 'Oh! The %s account is yours! Please choose a new login for your account in the next step.', 'secupress' ), ); if ( isset( $message_id ) ) { return isset( $messages[ $message_id ] ) ? $messages[ $message_id ] : __( 'Unknown message', 'secupress' ); } return $messages; } /** Getters. ================================================================================ */ /** * Get the documentation URL. * * @since 1.2.3 * * @return (string) */ public static function get_docs_url() { return __( 'https://docs.secupress.me/article/132-admin-user-account-scan', 'secupress' ); } /** Scan. =================================================================================== */ /** * Scan for flaw(s). * * @since 1.0 * * @return (array) The scan results. */ public function scan() { $username = 'admin'; $activated = $this->filter_scanner( __CLASS__ ); if ( true === $activated ) { $this->add_message( 0, array( '<em>' . $username . '</em>' ) ); return parent::scan(); } if ( secupress_get_site_transient( 'secupress-rename-admin-username' ) ) { $this->add_message( 100 ); return parent::scan(); } $user_id = username_exists( $username ); // The "admin" account exists and has a role or capabilities: it should have no role. if ( static::user_has_capas( $user_id ) ) { // "bad" $this->add_message( 200, array( '<em>' . $username . '</em>' ) ); } // The "admin" account should exist to avoid its creation when users can register. if ( ! $user_id && secupress_users_can_register() ) { // "bad" $this->add_message( 201, array( '<em>' . $username . '</em>' ) ); } // "good" $this->maybe_set_status( 0, array( '<em>' . $username . '</em>' ) ); return parent::scan(); } /** Fix. ==================================================================================== */ /** * Try to fix the flaw(s). * * @since 1.0 * * @return (array) The fix results. */ public function fix() { global $wpdb; $username = 'admin'; $user_id = username_exists( $username ); // The "admin" account exists and has a role or capabilities. if ( static::user_has_capas( $user_id ) ) { $current_user_id = get_current_user_id(); // It's not you: remove all roles and capabilities. if ( $user_id !== $current_user_id ) { // Remove all capabilities. $wpdb->query( $wpdb->prepare( "DELETE FROM $wpdb->usermeta WHERE user_id = %d AND meta_key REGEXP '{$wpdb->base_prefix}([0-9]+_)?capabilities'", $user_id ) ); if ( is_multisite() ) { // Not a network administrator anymore. revoke_super_admin( $user_id ); } if ( static::user_has_capas( $user_id ) ) { // "bad" $this->add_fix_message( 206, array( '<em>' . $username . '</em>' ) ); } else { // "good" $this->add_fix_message( 1, array( '<em>' . $username . '</em>' ) ); } } // It's you, you must change your username. else { // This fix requires the user to take action. $this->add_fix_message( 300, array( '<em>' . $username . '</em>' ) ); } } // Registrations are open: the "admin" account should exist to avoid the creation of this user. if ( ! $user_id && secupress_users_can_register() ) { // Make sure our "admin" creation is not blocked by our usernames blacklist. secupress_cache_data( 'allowed_usernames', $username ); $user_id = wp_insert_user( array( 'user_login' => $username, 'user_pass' => wp_generate_password( 64, 1, 1 ), 'user_email' => 'secupress_no_mail_AU@fakemail.' . time(), 'role' => '', ) ); secupress_cache_data( 'allowed_usernames', array() ); if ( is_wp_error( $user_id ) || ! $user_id ) { // "bad" $this->add_fix_message( 207, array( '<em>' . $username . '</em>' ) ); } else { if ( is_multisite() ) { // Make sure the new user is not a network administrator. revoke_super_admin( $user_id ); } // "good" $this->add_fix_message( 0, array( '<em>' . $username . '</em>' ) ); } } // "good" $this->maybe_set_fix_status( 0, array( '<em>' . $username . '</em>' ) ); return parent::fix(); } /** Manual fix. ============================================================================= */ /** * Return an array of actions if a manual fix is needed here. * * @since 1.0 * * @return (array) */ public function need_manual_fix() { $user_id = username_exists( 'admin' ); $has_capas = static::user_has_capas( $user_id ); // The "admin" account exists but has no role or capabilities. if ( $user_id && ! $has_capas ) { // OK. return array(); } // The "admin" account exists and has a role or capabilities. if ( $has_capas ) { // It's you! Manual fix. if ( get_current_user_id() === $user_id ) { return array( 'rename-admin-username' => 'rename-admin-username' ); } // It's not you: automatic fix. return false; } // Registrations are open: the "admin" account should exist to avoid the creation of this user. if ( ! $user_id && secupress_users_can_register() ) { // Automatic fix. return false; } // OK. return array(); } /** * Try to fix the flaw(s) after requiring user action. * * @since 1.0 * * @return (array) The fix results. */ public function manual_fix() { if ( ! $this->has_fix_action_part( 'rename-admin-username' ) ) { return parent::manual_fix(); } $username = ! empty( $_POST['secupress-fix-rename-admin-username'] ) ? sanitize_user( $_POST['secupress-fix-rename-admin-username'] ) : null; // WPCS: CSRF ok. if ( 'admin' === $username ) { // "bad" $this->add_fix_message( 202, array( '<em>' . $username . '</em>' ) ); } elseif ( ! $username ) { // "bad" $this->add_fix_message( 203 ); } elseif ( username_exists( $username ) ) { // "bad" $this->add_fix_message( 204, array( '<em>' . $username . '</em>' ) ); } elseif ( sanitize_user( $username, true ) !== $username ) { // "bad" $this->add_fix_message( 205, array( '<em>' . $username . '</em>', '<em>' . sanitize_user( $username, true ) . '</em>' ) ); } else { // $username ok, can't rename now or all nonces will be broken and the user disconnected $current_user_id = get_current_user_id(); secupress_set_site_transient( 'secupress-rename-admin-username', array( 'ID' => $current_user_id, 'username' => $username ) ); // "warning" $this->add_fix_message( 100 ); } return parent::manual_fix(); } /** * Get an array containing ALL the forms that would fix the scan if it requires user action. * * @since 1.0 * * @return (array) An array of HTML templates (form contents most of the time). */ protected function get_fix_action_template_parts() { $form = '<h4>' . __( 'Choose a new login for your account:', 'secupress' ) . '</h4>'; $form .= '<p><span style="color:red">' . __( 'Your username will be renamed on the next page load.', 'secupress' ) . '</span></p>'; $form .= '<input type="text" id="secupress-fix-rename-admin-username" name="secupress-fix-rename-admin-username" value="admin_' . substr( md5( time() ), 0, 6 ) . '"/>'; $form .= '<p>' . sprintf( __( 'Allowed characters: %s.', 'secupress' ), '<code>A-Z, a-z, 0-9, _, ., -, @</code>' ) . '</p>'; return array( 'rename-admin-username' => $form ); } /** Tools. ================================================================================== */ /** * Tell if a user has a role, capabilities, or is network admin. * * @since 1.0 * * @param (int) $user_id The user ID. * * @return (bool) */ protected static function user_has_capas( $user_id ) { global $wpdb; if ( ! $user_id ) { return false; } if ( is_super_admin( $user_id ) ) { return true; } // Get all user metas "wp_capabilities", "wp_2_capabilities" (MS)... $caps = $wpdb->get_var( $wpdb->prepare( "SELECT meta_key FROM $wpdb->usermeta WHERE user_id = %d AND meta_value != 'a:0:{}' AND meta_key REGEXP '{$wpdb->base_prefix}([0-9]+_)?capabilities' LIMIT 1", $user_id ) ); return (bool) $caps; } } free/classes/scanners/class-secupress-scan-wpml-discloses.php 0000644 00000016026 15174670627 0020477 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * WPML version disclose scan class. * * @package SecuPress * @subpackage SecuPress_Scan * @since 1.0 */ class SecuPress_Scan_Wpml_Discloses extends SecuPress_Scan implements SecuPress_Scan_Interface { /** Constants. ============================================================================== */ /** * Class version. * * @var (string) */ const VERSION = '1.2'; /** Properties. ============================================================================= */ /** * The reference to the *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** Init and messages. ====================================================================== */ /** * Init. * * @since 1.0 */ protected function init() { /** Translators: %s is a plugin name. */ $this->title = sprintf( __( 'Check if the %s plugin discloses its version.', 'secupress' ), 'WPML' ); $this->more = __( 'When an attacker wants to hack into a WordPress site, they will search for all available informations. The goal is to find something useful that will help him penetrate your site. Don’t let them easily find any informations.', 'secupress' ); $this->more_fix = sprintf( /** Translators: 1 is a plugin name, 2 is the name of a protection, 3 is the name of a module. */ __( 'Hide the %1$s version to prevent giving too much information to attackers. The %2$s protection from the module %3$s will be activated.', 'secupress' ), 'WPML', '<strong>' . __( 'Plugin Version Disclosure', 'secupress' ) . '</strong>', '<a href="' . esc_url( secupress_admin_url( 'modules', 'sensitive-data' ) ) . '#row-content-protect_plugin-version-discloses">' . __( 'Sensitive Data', 'secupress' ) . '</a>' ); } /** * Get messages. * * @since 1.0 * * @param (int) $message_id A message ID. * * @return (string|array) A message if a message ID is provided. An array containing all messages otherwise. */ public static function get_messages( $message_id = null ) { /** Translators: 1 is the name of a protection, 2 is the name of a module. */ $activate_protection_message = sprintf( __( 'But you can activate the %1$s protection from the module %2$s.', 'secupress' ), '<strong>' . __( 'Plugin Version Disclosure', 'secupress' ) . '</strong>', '<a target="_blank" href="' . esc_url( secupress_admin_url( 'modules', 'sensitive-data' ) ) . '#row-content-protect_plugin-version-discloses">' . __( 'Sensitive Data', 'secupress' ) . '</a>' ); $messages = array( // "good" /** Translators: %s is a plugin name. */ 0 => sprintf( __( 'The %s plugin does not reveal sensitive information.', 'secupress' ), '<strong>WPML</strong>' ), // "warning" /** Translators: %s is a plugin name. */ 100 => sprintf( __( 'Unable to determine if %s is disclosing its version on your homepage.', 'secupress' ), '<strong>WPML</strong>' ) . ' ' . $activate_protection_message, // "bad" /** Translators: 1 is a plugin name, 2 is some related info. */ 200 => sprintf( __( 'The %1$s plugin displays its version in the source code of your homepage (%2$s).', 'secupress' ), '<strong>WPML</strong>', '%s' ), // DEPRECATED, NOT IN USE ANYMORE. 1 => __( 'The generator meta tag should not be displayed anymore.', 'secupress' ), /** Translators: %s is a plugin name. */ 2 => sprintf( __( 'The %s’s version should be removed from your styles URLs now.', 'secupress' ), 'WPML' ), /** Translators: %s is a plugin name. */ 3 => sprintf( __( 'The %s’s version should be removed from your scripts URLs now.', 'secupress' ), 'WPML' ), ); if ( isset( $message_id ) ) { return isset( $messages[ $message_id ] ) ? $messages[ $message_id ] : __( 'Unknown message', 'secupress' ); } return $messages; } /** Getters. ================================================================================ */ /** * Get the documentation URL. * * @since 1.2.3 * * @return (string) */ public static function get_docs_url() { return __( 'https://docs.secupress.me/article/104-wpml-version-number-disclosure-scan', 'secupress' ); } /** Scan. =================================================================================== */ /** * Scan for flaw(s). * * @since 1.0 * * @return (array) The scan results. */ public function scan() { $activated = $this->filter_scanner( __CLASS__ ); if ( true === $activated ) { $this->add_message( 0 ); return parent::scan(); } $discloses = array(); // Get home page contents. $response = wp_remote_get( add_query_arg( secupress_generate_key( 6 ), secupress_generate_key( 8 ), user_trailingslashit( home_url() ) ), $this->get_default_request_args() ); $has_response = ! is_wp_error( $response ) && 200 === wp_remote_retrieve_response_code( $response ); // Generator meta tag. if ( $has_response ) { $body = wp_remote_retrieve_body( $response ); // WPML version in meta tag. preg_match_all( '#<meta name="generator" content="WPML [^"]*' . ICL_SITEPRESS_VERSION . '[^"]*"[^>]*>#s', $body, $matches ); if ( array_filter( $matches ) ) { // "bad" $discloses[] = 'META'; } } // What about style tag src? $style_url = home_url( '/fake.css?ver=' . ICL_SITEPRESS_VERSION ); /** This filter is documented in wp-includes/class.wp-styles.php */ if ( apply_filters( 'style_loader_src', $style_url, 'secupress' ) === $style_url ) { // "bad" $discloses[] = 'CSS'; } // What about script tag src? $script_url = home_url( '/fake.js?ver=' . ICL_SITEPRESS_VERSION ); /** This filter is documented in wp-includes/class.wp-scripts.php */ if ( apply_filters( 'script_loader_src', $script_url, 'secupress' ) === $script_url ) { // "bad" $discloses[] = 'JS'; } // Sum up! if ( $discloses ) { // "bad" $this->add_message( 200, array( $discloses ) ); } // "good" $this->maybe_set_status( 0 ); return parent::scan(); } /** Fix. ==================================================================================== */ /** * Try to fix the flaw(s). * * @since 1.4.5 * * @return (array) The fix results. */ public function need_manual_fix() { return [ 'fix' => 'fix' ]; } /** * Get an array containing ALL the forms that would fix the scan if it requires user action. * * @since 1.4.5 * * @return (array) An array of HTML templates (form contents most of the time). */ protected function get_fix_action_template_parts() { return [ 'fix' => ' ' ]; } /** * Try to fix the flaw(s) after requiring user action. * * @since 1.4.5 * * @return (array) The fix results. */ public function manual_fix() { if ( $this->has_fix_action_part( 'fix' ) ) { $this->fix(); } // "good" $this->add_fix_message( 1 ); return parent::manual_fix(); } /** * Try to fix the flaw(s). * * @since 1.0 * * @return (array) The fix results. */ public function fix() { // Activate. secupress_activate_submodule( 'discloses', 'wpml-version' ); // "good" $this->add_fix_message( 0 ); return parent::fix(); } } free/classes/scanners/class-secupress-scan-directory-listing.php 0000644 00000021762 15174670627 0021210 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Directory Listing scan class. * * @package SecuPress * @subpackage SecuPress_Scan * @since 1.0 */ class SecuPress_Scan_Directory_Listing extends SecuPress_Scan implements SecuPress_Scan_Interface { /** Constants. ============================================================================== */ /** * Class version. * * @var (string) */ const VERSION = '1.2'; /** Properties. ============================================================================= */ /** * The reference to the *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** Init and messages. ====================================================================== */ /** * Init. * * @since 1.0 */ protected function init() { global $is_apache, $is_nginx, $is_iis7; $this->title = __( 'Check if your server lists the files in a directory (known as Directory Listing).', 'secupress' ); $this->more = __( 'Without the appropriate protection anybody could browse your site’s files. While browsing some of your files might not be a security risk, most of them are sensitive.', 'secupress' ); $this->more_fix = sprintf( __( 'Activate the %1$s protection from the module %2$s.', 'secupress' ), '<strong>' . __( 'Directory Listing', 'secupress' ) . '</strong>', '<a href="' . esc_url( secupress_admin_url( 'modules', 'sensitive-data' ) ) . '#row-content-protect_directory-listing">' . __( 'Sensitive Data', 'secupress' ) . '</a>' ); if ( ! $is_apache && ! $is_nginx && ! $is_iis7 ) { $this->more_fix = static::get_messages( 301 ); $this->fixable = false; return; } } /** * Get messages. * * @since 1.0 * * @param (int) $message_id A message ID. * * @return (string|array) A message if a message ID is provided. An array containing all messages otherwise. */ public static function get_messages( $message_id = null ) { global $is_apache; $config_file = $is_apache ? '.htaccess' : 'web.config'; /** Translators: 1 is the name of a protection, 2 is the name of a module. */ $activate_protection_message = sprintf( __( 'But you can activate the %1$s protection from the module %2$s.', 'secupress' ), '<strong>' . __( 'Directory Listing', 'secupress' ) . '</strong>', '<a target="_blank" href="' . esc_url( secupress_admin_url( 'modules', 'sensitive-data' ) ) . '#row-content-protect_directory-listing">' . __( 'Sensitive Data', 'secupress' ) . '</a>' ); $messages = array( // "good" 0 => __( 'Your site does not reveal the files list.', 'secupress' ), /** Translators: %s is a file name. */ 1 => sprintf( __( 'The rules forbidding access to directory listing have been successfully added to your %s file.', 'secupress' ), "<code>$config_file</code>" ), // "warning" /** Translators: %s is a URL. */ 100 => sprintf( __( 'Unable to determine the status of %1$s to read the directory listing.', 'secupress' ), '%s' ) . ' ' . $activate_protection_message, // "bad" /** Translators: %s is a URL. */ 200 => __( '%s (for example) should not be accessible to anyone because of directory listing.', 'secupress' ), // "cantfix" /** Translators: 1 is a file name, 2 is some code. */ 300 => sprintf( __( 'Your server runs <strong>Nginx</strong>, the directory listing disclosure cannot be fixed automatically but you can do it yourself by adding the following code to your %1$s file: %2$s', 'secupress' ), '<code>nginx.conf</code>', '%s' ), 301 => __( 'Your server runs an unrecognized system. The directory listing disclosure cannot be fixed automatically.', 'secupress' ), /** Translators: 1 is a file name, 2 is some code. */ 302 => sprintf( __( 'Your %1$s file is not writable. Please add the following lines at the beginning of the file: %2$s', 'secupress' ), "<code>$config_file</code>", '%s' ), /** Translators: 1 is a file name, 2 is a tag name, 3 is a folder path (kind of), 4 is some code. */ 303 => sprintf( __( 'Your %1$s file is not writable. Please remove any previous %2$s tag and add the following lines inside the tags hierarchy %3$s (create it if does not exist): %4$s', 'secupress' ), "<code>$config_file</code>", '%1$s', '%2$s', '%3$s' ), ); if ( isset( $message_id ) ) { return isset( $messages[ $message_id ] ) ? $messages[ $message_id ] : __( 'Unknown message', 'secupress' ); } return $messages; } /** Getters. ================================================================================ */ /** * Get the documentation URL. * * @since 1.2.3 * * @return (string) */ public static function get_docs_url() { return __( 'https://docs.secupress.me/article/126-index-listing-scan', 'secupress' ); } /** Scan. =================================================================================== */ /** * Scan for flaw(s). * * @since 1.0 * * @return (array) The scan results. */ public function scan() { $activated = $this->filter_scanner( __CLASS__ ); if ( true === $activated ) { $this->add_message( 0 ); return parent::scan(); } $upload_dir = wp_upload_dir(); $base_url = user_trailingslashit( $upload_dir['baseurl'] ); $response = wp_remote_get( $base_url, $this->get_default_request_args() ); if ( ! is_wp_error( $response ) ) { if ( 200 === wp_remote_retrieve_response_code( $response ) ) { $body = trim( wp_remote_retrieve_body( $response ) ); if ( strlen( $body ) > 25 ) { // "bad" $this->add_message( 200, array( '<code>' . $base_url . '</code>' ) ); if ( ! $this->fixable ) { $this->add_pre_fix_message( 301 ); } } } } // "good" $this->maybe_set_status( 0 ); return parent::scan(); } /** Fix. ==================================================================================== */ /** * Try to fix the flaw(s). * * @since 1.4.5 * * @return (array) The fix results. */ public function need_manual_fix() { return [ 'fix' => 'fix' ]; } /** * Get an array containing ALL the forms that would fix the scan if it requires user action. * * @since 1.4.5 * * @return (array) An array of HTML templates (form contents most of the time). */ protected function get_fix_action_template_parts() { return [ 'fix' => ' ' ]; } /** * Try to fix the flaw(s) after requiring user action. * * @since 1.4.5 * * @return (array) The fix results. */ public function manual_fix() { if ( $this->has_fix_action_part( 'fix' ) ) { $this->fix(); } // "good" $this->add_fix_message( 1 ); return parent::manual_fix(); } /** * Try to fix the flaw(s). * * @since 1.0 * * @return (array) The fix results. */ public function fix() { global $is_apache, $is_nginx, $is_iis7; if ( $is_apache ) { $this->fix_apache(); } elseif ( $is_iis7 ) { $this->fix_iis7(); } elseif ( $is_nginx ) { $this->fix_nginx(); } // "good" $this->maybe_set_fix_status( 0 ); return parent::fix(); } /** * Fix for Apache system. * * @since 1.0 */ protected function fix_apache() { global $wp_settings_errors; secupress_activate_submodule( 'sensitive-data', 'directory-listing' ); // Got error? $last_error = is_array( $wp_settings_errors ) && $wp_settings_errors ? end( $wp_settings_errors ) : false; if ( $last_error && 'general' === $last_error['setting'] && 'apache_manual_edit' === $last_error['code'] ) { $rules = static::get_rules_from_error( $last_error ); // "cantfix" $this->add_fix_message( 302, array( $rules ) ); array_pop( $wp_settings_errors ); return; } // "good" $this->add_fix_message( 1 ); } /** * Fix for IIS7 system. * * @since 1.0 */ protected function fix_iis7() { global $wp_settings_errors; secupress_activate_submodule( 'sensitive-data', 'directory-listing' ); // Got error? $last_error = is_array( $wp_settings_errors ) && $wp_settings_errors ? end( $wp_settings_errors ) : false; if ( $last_error && 'general' === $last_error['setting'] && 'iis7_manual_edit' === $last_error['code'] ) { $rules = static::get_rules_from_error( $last_error ); $path = static::get_code_tag_from_error( $last_error, 'secupress-iis7-path' ); $node_type = static::get_code_tag_from_error( $last_error, 'secupress-iis7-node-type' ); // "cantfix" $this->add_fix_message( 303, array( $node_type, $path, $rules ) ); array_pop( $wp_settings_errors ); return; } // "good" $this->add_fix_message( 1 ); } /** * Fix for nginx system. * * @since 1.0 */ protected function fix_nginx() { global $wp_settings_errors; secupress_activate_submodule( 'sensitive-data', 'directory-listing' ); // Get the error. $last_error = is_array( $wp_settings_errors ) && $wp_settings_errors ? end( $wp_settings_errors ) : false; $rules = '<code>Error</code>'; if ( $last_error && 'general' === $last_error['setting'] && 'nginx_manual_edit' === $last_error['code'] ) { $rules = static::get_rules_from_error( $last_error ); array_pop( $wp_settings_errors ); } // "cantfix" $this->add_fix_message( 300, array( $rules ) ); } } free/classes/scanners/class-secupress-scan-php-404.php 0000644 00000011355 15174670627 0016626 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * PHP 404 Class * * @package SecuPress * @subpackage SecuPress_Scan * @since 1.4 */ class SecuPress_Scan_Php_404 extends SecuPress_Scan implements SecuPress_Scan_Interface { /** Constants. ============================================================================== */ /** * Class version. * * @var (string) */ const VERSION = '1.2'; /** Properties. ============================================================================= */ /** * The reference to the *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** Init and messages. ====================================================================== */ /** * Init. * * @since 1.0 */ protected function init() { $this->title = __( 'Check if your site is allowing 404 on PHP files.', 'secupress' ); $this->more = __( 'Do not let attackers find hidden PHP files by guessing it, ban them!', 'secupress' ); $this->more_fix = sprintf( __( 'Activate the option %1$s in the %2$s module.', 'secupress' ), '<em>' . __( 'Block 404 on .php', 'secupress' ) . '</em>', '<a href="' . esc_url( secupress_admin_url( 'modules', 'firewall' ) ) . '#row-bbq-url-content_ban-404-php">' . __( 'Firewall', 'secupress' ) . '</a>' ); } /** * Get messages. * * @since 1.0 * * @param (int) $message_id A message ID. * * @return (string|array) A message if a message ID is provided. An array containing all messages otherwise. */ public static function get_messages( $message_id = null ) { /** Translators: 1 is the name of a protection, 2 is the name of a module. */ $activate_protection_message = sprintf( __( 'But you can activate the %1$s protection from the module %2$s.', 'secupress' ), '<strong>' . __( 'Block 404 on .php', 'secupress' ) . '</strong>', '<a target="_blank" href="' . esc_url( secupress_admin_url( 'modules', 'firewall' ) ) . '#row-bbq-url-content_ban-404-php">' . __( 'Firewall', 'secupress' ) . '</a>' ); $messages = array( // "good" 0 => __( 'You are currently blocking 404 requests on .php files.', 'secupress' ), 1 => __( 'Protection activated', 'secupress' ), // "warning" 100 => __( 'Unable to determine if your homepage is blocking 404 on .php files.', 'secupress' ) . ' ' . $activate_protection_message, // "bad" 200 => __( 'Your website should block <strong>404 on .php files</strong>.', 'secupress' ), ); if ( isset( $message_id ) ) { return isset( $messages[ $message_id ] ) ? $messages[ $message_id ] : __( 'Unknown message', 'secupress' ); } return $messages; } /** Getters. ================================================================================ */ /** * Get the documentation URL. * * @since 1.0 * * @return (string) */ public static function get_docs_url() { return __( 'https://docs.secupress.me/article/150-ban-404-on-php-files', 'secupress' ); } /** Scan. =================================================================================== */ /** * Scan for flaw(s). * * @since 1.0 * * @return (array) The scan results. */ public function scan() { $response = wp_remote_get( home_url( 'secupress-test-scanner-' . secupress_generate_key( 5 ) . '.php' ), $this->get_default_request_args() ); if ( ! is_wp_error( $response ) ) { if ( 200 === wp_remote_retrieve_response_code( $response ) || 404 === wp_remote_retrieve_response_code( $response ) ) { // "bad" $this->add_message( 200 ); } else { // "good" $this->add_message( 0 ); } } // "good" $this->maybe_set_status( 0 ); return parent::scan(); } /** Fix. ==================================================================================== */ /** * Try to fix the flaw(s). * * @since 1.4.5 * * @return (array) The fix results. */ public function need_manual_fix() { return [ 'fix' => 'fix' ]; } /** * Get an array containing ALL the forms that would fix the scan if it requires user action. * * @since 1.4.5 * * @return (array) An array of HTML templates (form contents most of the time). */ protected function get_fix_action_template_parts() { return [ 'fix' => ' ' ]; } /** * Try to fix the flaw(s) after requiring user action. * * @since 1.4.5 * * @return (array) The fix results. */ public function manual_fix() { if ( $this->has_fix_action_part( 'fix' ) ) { $this->fix(); } // "good" $this->add_fix_message( 1 ); return parent::manual_fix(); } /** * Try to fix the flaw(s). * * @since 1.0 * * @return (array) The fix results. */ public function fix() { // Activate. secupress_activate_submodule( 'firewall', 'ban-404-php' ); // "good" $this->add_fix_message( 1 ); return parent::fix(); } } free/classes/scanners/class-secupress-scan-db-prefix.php 0000644 00000023665 15174670627 0017421 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * DB Prefix scan class. * * @package SecuPress * @subpackage SecuPress_Scan * @since 1.0 */ class SecuPress_Scan_DB_Prefix extends SecuPress_Scan implements SecuPress_Scan_Interface { /** Constants. ============================================================================== */ /** * Class version. * * @var (string) */ const VERSION = '1.0.2'; /** Properties. ============================================================================= */ /** * The reference to the *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** * Tells if a scanner is fixable by SecuPress. The value "pro" means it's fixable only with the version PRO. * * @var (bool|string) */ protected $fixable = 'pro'; /** Init and messages. ====================================================================== */ /** * Init. * * @since 1.0 */ protected function init() { $this->title = __( 'Check if your database tables prefix is correct.', 'secupress' ); $this->more = sprintf( __( 'Avoid the use of %s or %s as database tables prefix to improve your security.', 'secupress' ), secupress_code_me( 'wp_' ), secupress_code_me( 'wordpress_' ) ); $this->more_fix = __( 'Rename all your database table names, then update your configuration with a new and more secure one.', 'secupress' ); } /** * Get messages. * * @since 1.0 * * @param (int) $message_id A message ID. * * @return (string|array) A message if a message ID is provided. An array containing all messages otherwise. */ public static function get_messages( $message_id = null ) { $messages = array( // "good" 0 => __( 'Your database tables prefix is correct.', 'secupress' ), // "bad" 200 => sprintf( __( 'The database tables prefix should not be %1$s. Choose something else besides %2$s or %3$s, they are too easy to guess.', 'secupress' ), '%s', secupress_code_me( 'wp_' ), secupress_code_me( 'wordpress_' ) ), // "cantfix" 301 => __( 'The database user cannot alter tables and so the database tables prefix could not be changed.', 'secupress' ), 302 => sprintf( __( 'The %s file is not writable, so the database tables prefix cannot be changed.', 'secupress' ), secupress_code_me( secupress_get_wpconfig_filename( 'db' ) ) ), 303 => __( 'The database user seems to have to correct rights, but the database tables prefix could still not be changed.', 'secupress' ), 304 => __( 'Too many database tables found, which ones to rename?', 'secupress' ), ); if ( isset( $message_id ) ) { return isset( $messages[ $message_id ] ) ? $messages[ $message_id ] : __( 'Unknown message', 'secupress' ); } return $messages; } /** Getters. ================================================================================ */ /** * Get the documentation URL. * * @since 1.2.3 * * @return (string) */ public static function get_docs_url() { return __( 'https://docs.secupress.me/article/99-database-table-prefix-scan', 'secupress' ); } /** Scan. =================================================================================== */ /** * Scan for flaw(s). * * @since 1.0 * * @return (array) The scan results. */ public function scan() { $activated = $this->filter_scanner( __CLASS__ ); if ( true === $activated ) { $this->add_message( 0 ); return parent::scan(); } global $wpdb; if ( $this->need_fix() ) { // "bad" $this->add_message( 200, array( '<code>' . $wpdb->prefix . '</code>' ) ); } else { // "good" $this->add_message( 0 ); } return parent::scan(); } /** Fix. ==================================================================================== */ /** * Tell if we need to rename the table prefix. * * @since 1.1.1 * @author Grégory Viguier * * @return (bool) */ protected function need_fix() { global $wpdb; return 'wp_' === $wpdb->prefix || 'wordpress_' === $wpdb->prefix; } /** * Tell if the `wp-config.php` file can be fixed. * * @since 1.2.2 Returns the file path instead of true. * @since 1.1.1 * @author Grégory Viguier * * @return (string|bool) The path of `wp-config.php` file or false. */ protected function is_wp_config_fixable() { global $wpdb; $wpconfig_filepath = secupress_is_wpconfig_writable( 'db' ); if ( ! $wpconfig_filepath ) { return false; } // Get the file content $file_content = file_get_contents( $wpconfig_filepath ); // Find the string we need with WP default syntax $match_default = preg_match( '/\$table_prefix\s*=\s*(\'' . $wpdb->prefix . '\'|"' . $wpdb->prefix . '");.*/', $file_content ); if ( $match_default ) { return $wpconfig_filepath; } // Find the string we need with GLOBALS syntax $match_globals = preg_match( '/\$GLOBALS[\'table_prefix\']\s*=\s*(\'' . $wpdb->prefix . '\'|"' . $wpdb->prefix . '");.*/', $file_content ); if ( $match_globals ) { return $wpconfig_filepath; } // Nothing found return false; } /** * Try to fix the flaw(s). * * @since 1.0 * * @return (array) The fix results. */ public function fix() { if ( ! $this->need_fix() ) { // "good" $this->add_fix_message( 0 ); return parent::fix(); } $can_fix = true; if ( ! secupress_db_access_granted() ) { // "cantfix" $this->add_fix_message( 301 ); $can_fix = false; } if ( ! secupress_where_is_table_prefix() ) { // "cantfix" $this->add_fix_message( 302 ); $can_fix = false; } if ( ! $can_fix ) { return parent::fix(); } // "bad" $this->add_fix_message( 200 ); return parent::fix(); } /** Manual fix. ============================================================================= */ /** * Return an array of actions if a manual fix is needed here. * * @since 1.0 * @since 1.1.1 Return false instead of an empty array. * * @return (array|bool) */ public function need_manual_fix() { if ( ! $this->need_fix() ) { return false; } if ( ! secupress_where_is_table_prefix() ) { return false; } if ( ! secupress_db_access_granted() ) { return array( 'db_access' => 'db_access' ); } // We have non WP table(s) to (maybe) rename, the user must choose. return array( 'select-db-tables-to-rename' => 'select-db-tables-to-rename' ); } /** * Try to fix the flaw(s) after requiring user action. * * @since 1.0 * * @return (array) The fix results. */ public function manual_fix() { global $wpdb, $table_prefix; if ( ! empty( $_POST ) && ! $this->has_fix_action_part( 'select-db-tables-to-rename' ) ) { // WPCS: CSRF ok. return parent::manual_fix(); } // Make the tests again, we want to be sure to not run this script unnecessarily. if ( ! $this->need_fix() ) { // "good" $this->add_fix_message( 0 ); return parent::manual_fix(); } $can_fix = true; if ( ! secupress_db_access_granted() ) { // "cantfix" $this->add_fix_message( 301 ); $can_fix = false; } $wpconfig_filepath = secupress_where_is_table_prefix(); if ( ! $wpconfig_filepath ) { // "cantfix" $this->add_fix_message( 302 ); $can_fix = false; } if ( ! $can_fix ) { return parent::manual_fix(); } // Chosen non WP tables. $tables_to_rename = secupress_get_wp_tables(); if ( isset( $_POST['secupress-select-db-tables-to-rename-flag'] ) && ! empty( $_POST['secupress-select-db-tables-to-rename'] ) ) { // WPCS: CSRF ok. $non_wp_tables = (array) $_POST['secupress-select-db-tables-to-rename']; // WPCS: CSRF ok. $non_wp_tables = array_intersect( $non_wp_tables, secupress_get_non_wp_tables() ); $tables_to_rename = array_merge( $non_wp_tables, $tables_to_rename ); } secupress_change_db_prefix( secupress_create_unique_db_prefix(), $tables_to_rename ); $this->add_fix_message( 0 ); return parent::manual_fix(); } /** * Get an array containing ALL the forms that would fix the scan if it requires user action. * * @since 1.0 * * @return (array) An array of HTML templates (form contents most of the time). */ protected function get_fix_action_template_parts() { global $wpdb; $non_wp_tables = secupress_get_non_wp_tables(); $wp_tables = secupress_get_wp_tables(); $blog_ids = ! is_multisite() ? array( '1' ) : $wpdb->get_col( "SELECT blog_id FROM {$wpdb->blogs}" ); $form = '<div class="show-input">'; $form .= '<h4>' . __( 'Checked tables will be renamed:', 'secupress' ) . '</h4>'; $form .= '<input type="hidden" name="secupress-select-db-tables-to-rename-flag">'; $form .= '<fieldset aria-labelledby="select-db-tables-to-rename" class="secupress-boxed-group">'; if ( $non_wp_tables ) { $form .= '<b>' . __( 'Unknown tables', 'secupress' ) . '</b><br/>'; foreach ( $non_wp_tables as $table ) { $table_attr = esc_attr( $table ); $form .= '<input type="checkbox" name="secupress-select-db-tables-to-rename[]" value="' . $table_attr . '" id="select-db-tables-to-rename-' . $table_attr . '" checked="checked"><label for="select-db-tables-to-rename-' . $table_attr . '">' . esc_html( $table ) . '</label><br/>'; } } $form .= '<b>' . __( 'WordPress tables (mandatory)', 'secupress' ) . '</b><br/>'; foreach ( $blog_ids as $blog_id ) { $blog_id = '1' === $blog_id ? '' : $blog_id . '_'; foreach ( $wp_tables as $table ) { $table = substr_replace( $table, $wpdb->prefix . $blog_id, 0, strlen( $wpdb->prefix ) ); $form .= '<input type="checkbox" id="secupress-select-db-tables-to-rename-' . esc_attr( $table ) . '" checked="checked" disabled="disabled"><label>' . esc_html( $table ) . '</label><br/>'; } } $form .= '</fieldset>'; $form .= '</div>'; return [ 'select-db-tables-to-rename' => $form, 'db_access' => $this->get_messages( 301 ) . '<br>' . sprintf( __( 'Please <a href="%s">read the documentation</a>.', 'secupress' ), $this->get_docs_url() ), 'wpconfig_fixable' => $this->get_messages( 302 ) . '<br>' . sprintf( __( 'Please <a href="%s">read the documentation</a>.', 'secupress' ), $this->get_docs_url() ), ]; } } free/classes/scanners/class-secupress-scan-salt-keys.php 0000644 00000016745 15174670627 0017456 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Salt Keys scan class. * * @package SecuPress * @subpackage SecuPress_Scan * @since 2.0 */ class SecuPress_Scan_Salt_Keys extends SecuPress_Scan implements SecuPress_Scan_Interface { /** Constants. ============================================================================== */ /** * Class version. * * @var (string) */ const VERSION = '2.0'; /** Properties. ============================================================================= */ /** * The reference to the *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** Init and messages. ====================================================================== */ /** * Init. * * @since 1.0 */ protected function init() { $this->title = __( 'Check if the security keys are correctly set.', 'secupress' ); $this->more = sprintf( __( 'WordPress provides %d security keys, each key has its own purpose. These keys must be set with long random strings: don’t keep the default value, don’t store them in the database, don’t hardcode them.', 'secupress' ), 10 ); $this->more_fix = sprintf( __( 'Create a <a href="https://codex.wordpress.org/Must_Use_Plugins">must-use plugin</a> to replace your actual keys stored in <code>%s</code> or in your database to keep them safer.', 'secupress' ), secupress_get_wpconfig_filename() ); } /** * Get messages. * * @since 1.0 * * @param (int) $message_id A message ID. * * @return (string|array) A message if a message ID is provided. An array containing all messages otherwise. */ public static function get_messages( $message_id = null ) { $messages = array( // "good" 0 => __( 'All security keys are properly set.', 'secupress' ), // "warning" 100 => __( 'This fix is <strong>pending</strong>, please reload the page to apply it now.', 'secupress' ), 101 => sprintf( __( 'The %s file could not be located.', 'secupress' ), secupress_code_me( secupress_get_wpconfig_filename() ) ), // "bad" 200 => __( 'The following security keys are not set correctly:', 'secupress' ), 201 => _n_noop( '<strong>· Not Set:</strong> %s.', '<strong>· Not Set:</strong> %s.', 'secupress' ), 202 => _n_noop( '<strong>· Default Value:</strong> %s.', '<strong>· Default Value:</strong> %s.', 'secupress' ), 203 => _n_noop( '<strong>· Too Short:</strong> %s.', '<strong>· Too Short:</strong> %s.', 'secupress' ), 204 => _n_noop( '<strong>· Hardcoded:</strong> %s.', '<strong>· Hardcoded:</strong> %s.', 'secupress' ), 205 => _n_noop( '<strong>· From DB:</strong> %s.', '<strong>· From DB:</strong> %s.', 'secupress' ), // "cantfix" 300 => sprintf( __( 'The %s file is not writable, security keys could not be changed.', 'secupress' ), secupress_code_me( secupress_get_wpconfig_filename() ) ), 301 => __( 'The security keys fix has been applied but there is still keys that could not be modified so far.', 'secupress' ), 302 => __( 'Some keys have been deleted from database, but some could not. Please do it manually, refer to the documentation if needed.', 'secupress' ), ); if ( isset( $message_id ) ) { return isset( $messages[ $message_id ] ) ? $messages[ $message_id ] : __( 'Unknown message', 'secupress' ); } return $messages; } /** Getters. ================================================================================ */ /** * Get the documentation URL. * * @since 1.2.3 * * @return (string) */ public static function get_docs_url() { return __( 'https://docs.secupress.me/article/92-security-keys-scan', 'secupress' ); } /** Scan. =================================================================================== */ /** * Scan for flaw(s). * * @since 1.0 * * @return (array) The scan results. */ public function scan() { $activated = $this->filter_scanner( __CLASS__ ); if ( true === $activated ) { $this->add_message( 0 ); return parent::scan(); } $wpconfig_filepath = secupress_find_wpconfig_path(); if ( ! $wpconfig_filepath ) { // "warning" $this->add_message( 100 ); return parent::scan(); } // Get code only from `wp-config.php`. $wp_config_content = php_strip_whitespace( $wpconfig_filepath ); $keys = secupress_get_db_salt_keys(); $bad_keys = [ 201 => [], 202 => [], 203 => [], 204 => [], 205 => [], ]; $pattern = "'" . implode( "'|'", $keys ) . "'|"; $pattern .= '"' . implode( '"|"', $keys ) . '"'; preg_match_all( '/' . $pattern . '/', $wp_config_content, $matches ); if ( ! empty( $matches[0] ) ) { // Hardcoded. $bad_keys[204] = self::wrap_in_tag( $matches[0] ); } foreach ( $keys as $key ) { // Check constant. $constant = defined( $key ) ? constant( $key ) : null; switch ( true ) { case is_null( $constant ) : // Not Set. $bad_keys[201][] = '<code>' . $key . '</code>'; break; case 'put your unique phrase here' === $constant : // Default Value. $bad_keys[202][] = '<code>' . $key . '</code>'; break; case strlen( $constant ) < 64 : // Too Short. $bad_keys[203][] = '<code>' . $key . '</code>'; break; } // Check DB. require_once( ABSPATH . '/wp-admin/includes/upgrade.php' ); $key = strtolower( $key ); $db = __get_option( $key, null ); if ( ! is_null( $db ) ) { // From DB. $bad_keys[205][] = '<code>' . $key . '</code>'; } } $bad_keys = array_filter( $bad_keys ); if ( count( $bad_keys ) ) { // "bad" $this->add_message( 200 ); foreach ( $bad_keys as $message_id => $keys ) { $this->add_message( $message_id, array( count( $keys ), $keys ) ); } } // "good" $this->maybe_set_status( 0 ); return parent::scan(); } /** Fix. ==================================================================================== */ /** * Try to fix the flaw(s). * * @since 1.4.5 * * @return (array) The fix results. */ public function need_manual_fix() { return [ 'fix' => 'fix' ]; } /** * Get an array containing ALL the forms that would fix the scan if it requires user action. * * @since 1.4.5 * * @return (array) An array of HTML templates (form contents most of the time). */ protected function get_fix_action_template_parts() { return [ 'fix' => ' ' ]; } /** * Try to fix the flaw(s) after requiring user action. * * @since 1.4.5 * * @return (array) The fix results. */ public function manual_fix() { if ( $this->has_fix_action_part( 'fix' ) ) { $this->fix(); } // "good" $this->add_fix_message( 1 ); return parent::manual_fix(); } /** * Try to fix the flaw(s). * * @since 1.0 * * @return (array) The fix results. */ public function fix() { global $current_user; if ( 0 === secupress_delete_db_salt_keys() ) { // 0 = Eveything has been deleted OR nothing needed. // good $this->add_fix_message( 0 ); } else { // cantfix $this->add_fix_message( 302 ); } if ( defined( 'SECUPRESS_SALT_KEYS_MODULE_ACTIVE' ) ) { // "cantfix" $this->add_fix_message( 301 ); } if ( ! secupress_is_wpconfig_writable() ) { // "cantfix" $this->add_fix_message( 300 ); } if ( isset( $current_user->ID ) ) { secupress_activate_submodule( 'wordpress-core', 'wp-config-constant-saltkeys' ); // "warning" $this->add_fix_message( 100 ); } // "good" $this->maybe_set_fix_status( 0 ); return parent::fix(); } } free/classes/scanners/class-secupress-scan-malware-scanners.php 0000644 00000010270 15174670627 0020767 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Malware Scanners scan class. * * @package SecuPress * @subpackage SecuPress_Scan * @since 2.6 */ class SecuPress_Scan_Malware_Scanners extends SecuPress_Scan implements SecuPress_Scan_Interface { /** Constants. ============================================================================== */ /** * Class version. * * @var (string) */ const VERSION = '1.0'; /** Properties. ============================================================================= */ /** * The reference to the *Singleton* instance of this class. * * @var (object) */ protected static $_instance; /** * Tells if a scanner is fixable by SecuPress. The value "pro" means it's fixable only with the version PRO. * * @var (bool|string) */ protected $fixable = false; /** Init and messages. ====================================================================== */ /** * Init. * * @since 2.6 */ protected function init() { $this->title = __( 'Check if malware scanners have detected suspicious files.', 'secupress' ); $this->more = __( 'The malware scanners analyze your files and database to detect suspicious content, modified files, or files that should not be part of your WordPress installation.', 'secupress' ); $this->more_fix = static::get_messages( 300 ); } /** * Get messages. * * @since 2.6 * * @param (int) $message_id A message ID. * * @return (string|array) A message if a message ID is provided. An array containing all messages otherwise. */ public static function get_messages( $message_id = null ) { $_200 = sprintf( __( 'Open %sMalware Scanner%s.', 'secupress' ), '<a href="' . secupress_admin_url( 'modules','file-system' ) . '">', '</a>' ); $_100 = ! secupress_is_pro() ? sprintf( __( 'The %sPRO version%s will be more accurate.', 'secupress' ), '<a href="' . secupress_admin_url( 'get-pro' ) . '">', '</a>' ) : ' ' . $_200; $messages = array( // "good" 0 => __( 'No suspicious files or content detected by the malware scanners.', 'secupress' ), // "warning" 100 => sprintf( __( 'Your installation may contain malwares hidden in files or content.%s', 'secupress' ), $_100 ), // "bad" 200 => sprintf( __( 'The malware scanners have detected suspicious files or content on your installation. %s', 'secupress' ), $_200 ), // "cantfix" 300 => __( 'Cannot be fixed automatically. You need to review the detected files manually and remove or fix them.', 'secupress' ), ); if ( isset( $message_id ) ) { return isset( $messages[ $message_id ] ) ? $messages[ $message_id ] : __( 'Unknown message', 'secupress' ); } return $messages; } /** Getters. ================================================================================ */ /** * Get the documentation URL. * * @since 2.6 * * @return (string) */ public static function get_docs_url() { return __( 'https://docs.secupress.me/article/235-malware-scanners', 'secupress' ); } /** Scan. =================================================================================== */ /** * Scan for flaw(s). * * @since 2.6 * * @return (array) The scan results. */ public function scan() { $activated = $this->filter_scanner( __CLASS__ ); if ( true === $activated ) { $this->add_message( 0 ); return parent::scan(); } if ( ! secupress_is_pro() || ! function_exists( 'secupress_file_scanner_get_result' ) ) { $this->add_message( 100 ); $this->add_pre_fix_message( 300 ); return parent::scan(); } $results = secupress_file_scanner_get_result(); if ( ! is_array( $results ) ) { $this->add_message( 100 ); $this->add_pre_fix_message( 300 ); return parent::scan(); } $has_content = ! empty( array_filter( $results ) ); if ( $has_content ) { $this->add_message( 200 ); } else { $this->add_message( 0 ); } $this->add_pre_fix_message( 300 ); return parent::scan(); } /** Fix. ==================================================================================== */ /** * Try to fix the flaw(s). * * @since 2.6 * * @return (array) The fix results. */ public function fix() { $this->add_fix_message( 300 ); return parent::fix(); } } free/admin/scanner-step-2.php 0000644 00000027350 15174670627 0012055 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); // Keep only scans with "bad" status. $this_step_scans = $bad_scans; // `array( $class_name_part_lower => $status )` $fixable_modules = array(); // Will tell which modules have fixable items. $secupress_is_pro = secupress_is_pro(); // Keep only scans that are fixable automatically + require the scan files. foreach ( $secupress_tests as $module_name => $class_name_parts ) { $class_name_parts = array_combine( array_map( 'strtolower', $class_name_parts ), $class_name_parts ); $class_name_parts = array_intersect_key( $class_name_parts, $this_step_scans ); // Only those with "bad" status. if ( ! $class_name_parts ) { unset( $secupress_tests[ $module_name ] ); continue; } $secupress_tests[ $module_name ] = $class_name_parts; $fixable_modules[ $module_name ] = false; foreach ( $class_name_parts as $class_name_part_lower => $class_name_part ) { if ( ! file_exists( secupress_class_path( 'scan', $class_name_part ) ) ) { unset( $secupress_tests[ $module_name ][ $class_name_part_lower ] ); continue; } secupress_require_class( 'scan', $class_name_part ); $class_name = 'SecuPress_Scan_' . $class_name_part; $current_test = $class_name::get_instance(); $is_fixable = $current_test->is_fixable(); // Remove those that are not fixable automatically. if ( false === $is_fixable ) { unset( $secupress_tests[ $module_name ][ $class_name_part_lower ] ); } // Tell if the module has fixable items. elseif ( ! $fixable_modules[ $module_name ] && ( true === $is_fixable || 'pro' === $is_fixable && $secupress_is_pro ) ) { $fixable_modules[ $module_name ] = true; } } } $secupress_tests = array_filter( $secupress_tests ); // Move along, move along... if ( ! $secupress_tests ) { ?> <div class="secupress-step-content-header secupress-flex secupress-flex-spaced"> <p class="secupress-step-title"><?php _e( 'Nothing to do here', 'secupress' ); ?></p> <p class="secupress-flex"> <a href="<?php echo esc_url( secupress_admin_url( 'scanners' ) ); ?>&step=3" class="secupress-button shadow light"> <span class="icon"> <i class="secupress-icon-cross" aria-hidden="true"></i> </span> <span class="text"><?php _e( 'Next step', 'secupress' ); ?></span> </a> </p> </div> <?php return; } ?> <div class="secupress-step-content-header secupress-flex secupress-flex-spaced"> <?php $has_fixes = (bool) array_filter( $fixable_modules ); if ( $secupress_is_pro ) { $main_button = '<button class="secupress-button secupress-button-tertiary secupress-button-autofix shadow' . ( $has_fixes ? '' : ' hidden' ) . '" type="button"> <span class="icon"> <i class="secupress-icon-wrench" aria-hidden="true"></i> </span> <span class="text">' . __( 'Fix it', 'secupress' ) . '</span> </button>'; } else { $main_button = ' <a href="' . esc_url( secupress_admin_url( 'scanners' ) ) . '&step=3" class="secupress-button secupress-button-tertiary shadow"> <span class="icon"> <i class="secupress-icon-wrench" aria-hidden="true"></i> </span> <span class="text">' . __( 'Next step', 'secupress' ) . '</span> </a>'; } $main_button .= '<a href="' . esc_url( secupress_admin_url( 'scanners' ) ) . '&step=3" class="secupress-button shadow light' . ( $has_fixes ? ' hidden' : '' ) . '"> <span class="icon"> <i class="secupress-icon-cross" aria-hidden="true"></i> </span> <span class="text">' . __( 'Ignore this step', 'secupress' ) . '</span> </a>'; if ( ! $secupress_is_pro ) { ?> <span><?php // Flex col placeholder. ?></span> <div class="secupress-step-content-header secupress-flex secupress-flex-spaced"> <p class="secupress-step-title"> </p> <p class="secupress-flex"> <a href="<?php echo esc_url( secupress_admin_url( 'scanners' ) ); ?>&step=3" class="secupress-button secupress-button-tertiary shadow"> <span class="icon"> <i class="secupress-icon-wrench" aria-hidden="true"></i> </span> <span class="text"><?php _e( 'Next step', 'secupress' ); ?></span> </a> </p> </div> <?php } else { ?> <p class="secupress-step-title"><?php _e( 'Only checked items will be automatically fixed', 'secupress' ); ?></p> <p> <?php echo $main_button; ?> </p> <?php } ?> </div> <div id="secupress-tests" class="secupress-tests"> <?php if ( secupress_is_pro() ) { ?> <div class="secupress-scans-group secupress-group-all"> <div class="secupress-sg-header secupress-flex secupress-flex-spaced"> <div class="secupress-sgh-name"> <i class="secupress-icon-gear" aria-hidden="true"></i> <p class="secupress-sgh-title"> </p> </div> <div class="secupress-sgh-actions secupress-flex"> <label class="text hide-if-no-js" for="secupress-toggle-check-all"> <span class="label-before-text"><?php _e( 'Toggle check all', 'secupress' ); ?></span> <input type="checkbox" id="secupress-toggle-check-all" class="secupress-checkbox secupress-toggle-check" checked="checked"/> <span class="label-text"></span> </label> </div> </div> </div> <?php } $modules = secupress_get_modules(); foreach ( $secupress_tests as $module_name => $class_name_parts ) { $module_icon = ! empty( $modules[ $module_name ]['icon'] ) ? $modules[ $module_name ]['icon'] : ''; $module_title = ! empty( $modules[ $module_name ]['title'] ) ? $modules[ $module_name ]['title'] : ''; $module_summary = ! empty( $modules[ $module_name ]['summaries']['small'] ) ? $modules[ $module_name ]['summaries']['small'] : ''; ?> <div class="secupress-scans-group secupress-group-<?php echo $module_name; ?>"> <div class="secupress-sg-header secupress-flex secupress-flex-spaced"> <div class="secupress-sgh-name"> <i class="secupress-icon-<?php echo $module_icon; ?>" aria-hidden="true"></i> <p class="secupress-sgh-title"><?php echo $module_title; ?></p> <p class="secupress-sgh-description"><?php echo $module_summary; ?></p> </div> <div class="secupress-sgh-actions secupress-flex"> <?php if ( $fixable_modules[ $module_name ] && $secupress_is_pro ) : ?> <label class="text hide-if-no-js" for="secupress-toggle-check-<?php echo $module_name; ?>"> <span class="label-before-text"><?php _e( 'Toggle group check', 'secupress' ); ?></span> <input type="checkbox" id="secupress-toggle-check-<?php echo $module_name; ?>" class="secupress-checkbox secupress-toggle-check" checked="checked"/> <span class="label-text"></span> </label> <?php endif; ?> </div> </div><!-- .secupress-sg-header --> <?php if ( ! secupress_is_pro() ) { ?> <div class="secupress-get-pro-version-div"> <span class="secupress-get-pro-version"> <?php printf( __( 'The <a href="%s" target="_blank">Pro Version</a> is required to autofix issues, fix it manually on next step.', 'secupress' ), esc_url( secupress_admin_url( 'get-pro' ) ) ); ?> </span> </div> <?php } ?> <div id="secupress-group-content-<?php echo $module_name; ?>" class="secupress-sg-content"> <?php foreach ( $class_name_parts as $class_name_part_lower => $class_name_part ) { $class_name = 'SecuPress_Scan_' . $class_name_part; $current_test = $class_name::get_instance(); $referer = urlencode( esc_url_raw( self_admin_url( 'admin.php?page=' . SECUPRESS_PLUGIN_SLUG . '_scanners&step=2#' . $class_name_part ) ) ); $needs_pro = 'pro' === $current_test->is_fixable() && ! $secupress_is_pro; // Scan. $scanner = isset( $scanners[ $class_name_part_lower ] ) ? $scanners[ $class_name_part_lower ] : array(); $scan_status = ! empty( $scanner['status'] ) ? $scanner['status'] : 'notscannedyet'; $scan_nonce_url = wp_nonce_url( admin_url( 'admin-post.php?action=secupress_scanner&test=' . $class_name_part . '&_wp_http_referer=' . $referer ), 'secupress_scanner_' . $class_name_part ); // Fix. $fix = ! empty( $fixes[ $class_name_part_lower ] ) ? $fixes[ $class_name_part_lower ] : array(); $fix_nonce_url = wp_nonce_url( admin_url( 'admin-post.php?action=secupress_fixit&test=' . $class_name_part . '&_wp_http_referer=' . $referer ), 'secupress_fixit_' . $class_name_part ); // Row css class. $row_css_class = 'secupress-item-' . $class_name_part; $row_css_class .= ' status-' . sanitize_html_class( $scan_status ); $row_css_class .= $needs_pro ? ' secupress-only-pro not-fixable' : ''; $row_css_class .= ! secupress_is_pro() ? ' disabled' : ''; ?> <div class="secupress-item-all <?php echo $row_css_class; ?>" id="<?php echo $class_name_part; ?>" data-scan-url="<?php echo esc_url( $scan_nonce_url ); ?>"> <div class="secupress-flex"> <p class="secupress-item-status secupress-status-mini"> <span class="secupress-dot-bad"></span> </p> <p class="secupress-item-title"><?php echo wp_kses( $current_test->more_fix, $allowed_tags ); ?></p> <p class="secupress-row-actions"> <?php if ( $needs_pro ) { // It is fixable with the pro version but the free version is used. ?> <span class="secupress-get-pro-version"> <?php printf( __( 'This feature and its fix are available in <a href="%s" target="_blank">Pro Version</a>', 'secupress' ), esc_url( secupress_admin_url( 'get-pro' ) ) ); ?> </span> <?php } else { ?> <a class="secupress-button-primary secupress-button-mini hide-if-js secupress-fixit<?php echo $current_test->is_delayed_fix() ? ' delayed-fix delayed-fix-' . $current_test->get_delayed_fix_value() : ''; ?>" href="<?php echo esc_url( $fix_nonce_url ); ?>"> <span class="icon" aria-hidden="true"> <i class="secupress-icon-shield"></i> </span> <span class="text"> <?php _e( 'Fix it', 'secupress' ); ?> </span> </a> <?php // It can be fixed. if ( $secupress_is_pro ) { ?> <input type="checkbox" id="secupress-item-<?php echo $class_name_part; ?>" class="secupress-checkbox secupress-row-check hide-if-no-js" checked="checked"/> <?php } ?> <label for="secupress-item-<?php echo $class_name_part; ?>" class="label-text hide-if-no-js"> <span class="screen-reader-text"><?php _e( 'Auto-fix this item', 'secupress' ); ?></span> </label> <?php } ?> </p> </div><!-- .secupress-flex --> </div><!-- .secupress-item-all --> <?php } ?> </div><!-- .secupress-sg-content --> </div><!-- .secupress-scans-group --> <?php } ?> </div><!-- .secupress-tests --> <div class="secupress-step-content-footer secupress-flex secupress-flex-top secupress-flex-spaced"> <span><?php // Flex col placeholder. ?></span> <p> <?php echo $main_button; ?> </p> </div> <div id="secupress-spinner" class="secupress-scans-group secupress-group-spinner hidden" aria-hidden="true"> <div class="secupress-sg-header"> <div class="secupress-sgh-name"> <p class="secupress-sgh-title"><?php esc_html_e( 'Currently fixing…', 'secupress' ); ?></p> <p class="secupress-sgh-description"><?php esc_html_e( 'Please grab a cup of water, open a book and just wait a few minutes.', 'secupress' ); ?></p> </div> </div> <div class="secupress-spinner-content secupress-text-center secupress-p3"> <img class="secupress-big-spinner secupress-mb1" src="<?php echo SECUPRESS_ADMIN_IMAGES_URL; ?>spinner-big.png" srcset="<?php echo SECUPRESS_ADMIN_IMAGES_URL; ?>spinner-big2x.png 2x" alt="<?php esc_attr_e( 'Fixing…', 'secupress' ); ?>" width="128" height="128"> <p class="secupress-text-basup"><?php _e( 'You will be automatically redirected to the next step,<br>if you are not within 3 minutes, please reload the page or ignore this step.', 'secupress' ); ?></p> </div> </div> free/admin/upgrader.php 0000644 00000066512 15174670627 0011130 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** --------------------------------------------------------------------------------------------- */ /** SECUPRESS UPGRADER ========================================================================== */ /** --------------------------------------------------------------------------------------------- */ /** * Tell WP what to do when admin is loaded aka upgrader * * @since 1.0 */ function secupress_upgrader() { $actual_version = secupress_get_option( 'version' ); // You can hook the upgrader to trigger any action when SecuPress is upgraded. // First install. if ( ! $actual_version ) { /** * Allow to prevent plugin first install hooks to fire. * * @since 1.0 * * @param (bool) $prevent True to prevent triggering first install hooks. False otherwise. */ if ( ! apply_filters( 'secupress.prevent_first_install', false ) ) { /** * Fires on the plugin first install. * * @since 1.0 * * @param (string) $module The module to reset. "all" means all modules at once. */ do_action( 'secupress.first_install', 'all' ); } } // Already installed but got updated. elseif ( SECUPRESS_VERSION !== $actual_version ) { $new_version = SECUPRESS_VERSION; /** * Fires when SecuPress is upgraded. * * @since 1.0 * * @param (string) $new_version The version being upgraded to. * @param (string) $actual_version The previous version. */ do_action( 'secupress.upgrade', $new_version, $actual_version ); } if ( defined( 'SECUPRESS_PRO_VERSION' ) && ( ! defined( 'SECUPRESS_PRO_SECUPRESS_MIN' ) || version_compare( SECUPRESS_VERSION, SECUPRESS_PRO_SECUPRESS_MIN ) >= 0 ) ) { $actual_pro_version = secupress_get_option( 'pro_version' ); // You can hook the upgrader to trigger any action when SecuPress Pro is upgraded. // First install. if ( ! $actual_pro_version ) { /** * Allow to prevent SecuPress Pro first install hooks to fire. * * @since 1.1.4 * * @param (bool) $prevent True to prevent triggering first install hooks. False otherwise. */ if ( ! apply_filters( 'secupress_pro.prevent_first_install', false ) ) { /** * Fires on SecuPress Pro first install. * * @since 1.1.4 * * @param (string) $module The module to reset. "all" means all modules at once. */ do_action( 'secupress_pro.first_install', 'all' ); } } // Already installed but got updated. elseif ( SECUPRESS_PRO_VERSION !== $actual_pro_version ) { $new_pro_version = SECUPRESS_PRO_VERSION; /** * Fires when SecuPress Pro is upgraded. * * @since 1.0 * * @param (string) $new_pro_version The version being upgraded to. * @param (string) $actual_pro_version The previous version. */ do_action( 'secupress_pro.upgrade', $new_pro_version, $actual_pro_version ); } } // If any upgrade has been done, we flush and update version. if ( did_action( 'secupress.first_install' ) || did_action( 'secupress.upgrade' ) || did_action( 'secupress_pro.first_install' ) || did_action( 'secupress_pro.upgrade' ) ) { // Do not use secupress_get_option() here. $options = get_site_option( SECUPRESS_SETTINGS_SLUG ); $options = is_array( $options ) ? $options : array(); // Free version. $options['version'] = SECUPRESS_VERSION; // Pro version. if ( did_action( 'secupress_pro.first_install' ) || did_action( 'secupress_pro.upgrade' ) ) { $options['pro_version'] = SECUPRESS_PRO_VERSION; } // First install. if ( did_action( 'secupress.first_install' ) ) { $options['hash_key'] = secupress_generate_key( 64 ); $options['install_time'] = time(); } secupress_update_options( $options ); /** * Fires when an updated has been done. * * @since 2.0 * @author Julio Potier * * @param (string) $actual_version * @param (string) $new_version * @param (array) $options */ do_action( 'secupress.did_upgrade', $actual_version, SECUPRESS_VERSION, $options ); } } add_action( 'secupress.first_install', 'secupress_install_users_login_module' ); /** * Create default option on install and reset. * * @since 1.0 * * @param (string) $module The module(s) that will be reset to default. `all` means "all modules". */ function secupress_install_users_login_module( $module ) { // First install. if ( 'all' === $module ) { // Activate "Ask for old password" submodule. // secupress_activate_submodule_silently( 'users-login', 'ask-old-password' ); } } add_action( 'secupress_pro.upgrade', 'secupress_new_pro_upgrade', 10, 2 ); /** * What to do when SecuPress Pro is updated, depending on versions. * * @since 2.0 * * @param (string) $secupress_version The version being upgraded to. * @param (string) $actual_version The previous version. */ function secupress_new_pro_upgrade( $secupress_version, $actual_pro_version ) { global $wpdb; // < 2.0 if ( version_compare( $actual_pro_version, '2.0', '<' ) ) { secupress_remove_old_plugin_file( SECUPRESS_PRO_MODULES_PATH . 'services/callbacks.php' ); delete_site_option( SECUPRESS_FULL_FILETREE ); } } add_action( 'secupress.upgrade', 'secupress_new_upgrade', 10, 2 ); /** * What to do when SecuPress is updated, depending on versions. * * @since 1.0 * * @param (string) $secupress_version The version being upgraded to. * @param (string) $actual_version The previous version. */ function secupress_new_upgrade( $secupress_version, $actual_version ) { global $wpdb, $current_user; // < 1.4.3 if ( version_compare( $actual_version, '1.4.3', '<' ) ) { secupress_deactivate_submodule( 'file-system', 'directory-index' ); secupress_remove_old_plugin_file( SECUPRESS_MODULES_PATH . 'file-system/plugins/directory-index.php' ); secupress_deactivate_submodule( 'wordpress-core', 'wp-config-constant-unfiltered-html' ); secupress_remove_old_plugin_file( SECUPRESS_MODULES_PATH . 'wordpress-core/plugins/wp-config-constant-unfiltered-html.php' ); secupress_deactivate_submodule( 'sensitive-data', 'restapi' ); secupress_remove_old_plugin_file( SECUPRESS_MODULES_PATH . 'sensitive-data/plugins/restapi.php' ); set_site_transient( 'secupress-common', time(), 2 * DAY_IN_SECONDS ); } // < 1.4.4 if ( version_compare( $actual_version, '1.4.4', '<' ) ) { $value = secupress_get_module_option( 'bbq-headers_user-agents-list', secupress_firewall_bbq_headers_user_agents_list_default(), 'firewall' ); $value = str_replace( 'Wget, ', '', $value ); secupress_update_module_option( 'bbq-headers_user-agents-list', $value, 'firewall' ); } // < 1.4.9 if ( version_compare( $actual_version, '1.4.9', '<' ) ) { secupress_remove_old_plugin_file( SECUPRESS_MODULES_PATH . 'users-login/plugins/inc/php/move-login/deprecated.php' ); secupress_remove_old_plugin_file( SECUPRESS_MODULES_PATH . 'users-login/plugins/inc/php/move-login/redirections-and-dies.php' ); secupress_remove_old_plugin_file( SECUPRESS_MODULES_PATH . 'users-login/plugins/inc/php/move-login/admin.php' ); secupress_remove_old_plugin_file( SECUPRESS_MODULES_PATH . 'users-login/plugins/inc/php/move-login/url-filters.php' ); } // < 2.0 if ( version_compare( $actual_version, '2.0', '<' ) ) { // Cannot use secupress_is_submodule_active() here because these are not modules yet (< 2.0...) if ( defined( 'SECUPRESS_SALT_KEYS_ACTIVE' ) ) { secupress_set_site_transient( 'secupress-add-salt-muplugin', array( 'ID' => $current_user->ID ) ); } if ( defined( 'COOKIEHASH' ) && COOKIEHASH !== md5( get_site_option( 'siteurl' ) ) ) { secupress_set_site_transient( 'secupress-add-cookiehash-muplugin', array( 'ID' => $current_user->ID, 'username' => $current_user->user_login ) ); } secupress_remove_old_plugin_file( SECUPRESS_MODULES_PATH . 'firewall/plugins/bad-sqli-scan.php' ); secupress_remove_old_plugin_file( SECUPRESS_MODULES_PATH . 'users-login/plugins/ask-old-password.php' ); secupress_remove_old_plugin_file( SECUPRESS_CLASSES_PATH . 'class-secupress-admin-support.php.php' ); delete_site_option( 'secupress_scan_wp_config' ); } // < 2.2.6 if ( version_compare( $actual_version, '2.2.6', '<' ) ) { // See secupress_init_get_malware_files() if ( secupress_is_pro() ) { wp_clear_scheduled_hook( 'secupress_bad_plugins' ); wp_clear_scheduled_hook( 'secupress_bad_themes' ); wp_clear_scheduled_hook( 'secupress_license_check' ); wp_schedule_event( time(), 'weekly', 'secupress_license_check' ); wp_clear_scheduled_hook( 'secupress_malware_files' ); wp_schedule_event( time(), 'daily', 'secupress_malware_files' ); } // Cannot use secupress_is_submodule_active() here because these are not modules yet (< 2.0...) secupress_remove_old_plugin_file( SECUPRESS_CLASSES_PATH . 'class-secupress-admin-support.php' ); // from < 2.0, fix the ".php.php", yep... secupress_remove_old_plugin_file( SECUPRESS_MODULES_PATH . 'firewall/plugins/request-methods-header.php' ); // Deleted module (obsolete). secupress_remove_old_plugin_file( SECUPRESS_CLASSES_PATH . 'scanners/class-secupress-scan-bad-request-methods.php' ); secupress_remove_old_plugin_file( SECUPRESS_MODULES_PATH . 'addons/settings/backup.php' ); // Deleted settings. if ( secupress_is_submodule_active( 'wordpress-core', 'wp-config-constant-dieondberror' ) ) { // We need to create the dropin file secupress_deactivate_submodule( 'wordpress-core', 'wp-config-constant-dieondberror' ); secupress_activate_submodule( 'wordpress-core', 'wp-config-constant-dieondberror' ); } if ( secupress_is_submodule_active( 'users-login', 'forbid-user-creation' ) ) { // this one depends on... secupress_activate_submodule( 'users-login', 'user-creation-protection' ); // ... this one now. } secupress_deactivate_submodule_silently( 'plugins-themes', 'theme-activation' ); if ( secupress_has_pro() ) { secupress_remove_old_plugin_file( SECUPRESS_PRO_MODULES_PATH . 'plugins-themes/plugins/theme-activation.php' ); } secupress_deactivate_submodule_silently( 'plugins-themes', 'theme-deletion' ); if ( secupress_has_pro() ) { secupress_remove_old_plugin_file( SECUPRESS_PRO_MODULES_PATH . 'plugins-themes/plugins/theme-deletion.php' ); } if ( secupress_is_submodule_active( 'plugins-themes', 'theme-installation' ) ) { secupress_deactivate_submodule( 'plugins-themes', 'theme-installation' ); secupress_activate_submodule( 'plugins-themes', 'theme-installation' ); secupress_add_transient_notice( __( 'The modules for `No Theme Activation`, `No Theme Deletion` have been consolidated into the `No Theme Actions` module.', 'secupress' ) ); } secupress_deactivate_submodule( 'sensitive-data', 'bad-file-extensions' ); // Not silently because we need to remove the htaccess rules. secupress_remove_old_plugin_file( SECUPRESS_MODULES_PATH . 'sensitive-data/settings/bad-file-extensions.php' ); if ( secupress_has_pro() ) { secupress_remove_old_plugin_file( SECUPRESS_PRO_MODULES_PATH . 'sensitive-data/plugins/bad-file-extensions.php' ); } secupress_deactivate_submodule_silently( 'plugins-themes', 'plugin-activation' ); if ( secupress_has_pro() ) { secupress_remove_old_plugin_file( SECUPRESS_PRO_MODULES_PATH . 'plugins-themes/plugins/plugin-activation.php' ); } secupress_deactivate_submodule_silently( 'plugins-themes', 'plugin-deactivation' ); if ( secupress_has_pro() ) { secupress_remove_old_plugin_file( SECUPRESS_PRO_MODULES_PATH . 'plugins-themes/plugins/plugin-deactivation.php' ); } secupress_deactivate_submodule_silently( 'plugins-themes', 'plugin-deletion' ); if ( secupress_has_pro() ) { secupress_remove_old_plugin_file( SECUPRESS_PRO_MODULES_PATH . 'plugins-themes/plugins/plugin-deletion.php' ); } if ( secupress_is_submodule_active( 'plugins-themes', 'plugin-installation' ) ) { secupress_deactivate_submodule( 'plugins-themes', 'plugin-installation' ); secupress_activate_submodule( 'plugins-themes', 'plugin-installation' ); secupress_add_transient_notice( __( 'The modules for `No Plugin Activation`, `No Plugin Deactivation`, and `No Plugin Deletion` have been consolidated into the `No Plugin Actions` module.', 'secupress' ) ); } delete_transient( 'secupress_unlock_admin_key' ); // name changed, now contains user_email. delete_site_option( 'secupress_scan_wp_config' ); // new CONCATENE_SCRIPT scan, remove the last one. delete_site_option( 'secupress_captcha_keys' ); // old captcha v1 $filesystem = secupress_get_filesystem(); $api_key_content = $filesystem->get_contents( SECUPRESS_PATH . 'defines.php' ); $api_key_content = str_replace( base64_encode( SECUPRESS_WEB_MAIN ), base64_encode( home_url() ), $api_key_content ); $filesystem->put_contents( SECUPRESS_PATH . 'defines.php', $api_key_content, FS_CHMOD_FILE ); } // < 2.3.7 if ( version_compare( $actual_version, '2.3.7', '<' ) ) { $value = secupress_get_module_option( 'bbq-headers_user-agents-list', secupress_firewall_bbq_headers_user_agents_list_default(), 'firewall' ); $value = str_replace( 'c99, ', '', $value ); secupress_update_module_option( 'bbq-headers_user-agents-list', $value, 'firewall' ); // Rewrite the new htaccess rules to allow sandbox requests if ( secupress_is_submodule_active( 'sensitive-data', 'bad-url-access' ) ) { secupress_deactivate_submodule( 'sensitive-data', 'bad-url-access' ); secupress_activate_submodule( 'sensitive-data', 'bad-url-access' ); } } // < 2.3.8 if ( version_compare( $actual_version, '2.3.8', '<' ) ) { // Rewrite the new htaccess rules to allow sandbox requests, again if ( secupress_is_submodule_active( 'sensitive-data', 'bad-url-access' ) ) { secupress_deactivate_submodule( 'sensitive-data', 'bad-url-access' ); secupress_activate_submodule( 'sensitive-data', 'bad-url-access' ); } } // < 2.3.13 if ( version_compare( $actual_version, '2.3.13', '<' ) ) { if ( secupress_is_submodule_active( 'sensitive-data', 'bad-url-access' ) ) { secupress_deactivate_submodule( 'sensitive-data', 'bad-url-access' ); $GLOBALS['contentprotectbadurlaccess'] = 'disallowed'; secupress_activate_submodule( 'sensitive-data', 'bad-url-access' ); } } // < 2.3.16.2 if ( version_compare( $actual_version, '2.3.16.2', '<' ) ) { $files = secupress_find_mu_plugin( 'salt_keys' ); if ( count( $files ) > 1 ) { unset( $files[0] ); // Delete all others salt keys files array_map( 'secupress_delete_mu_plugin', $files ); } } // < 2.3.17 if ( version_compare( $actual_version, '2.3.17', '<' ) ) { if ( defined( 'SECUPRESS_NO_PLUGIN_ACTION_RUNNING' ) ) { secupress_delete_mu_plugin( 'no_plugins_installation' ); secupress_deactivate_submodule_silently( 'plugins-themes', 'plugin-installation' ); secupress_activate_submodule_silently( 'plugins-themes', 'plugin-installation' ); if ( function_exists( 'secupress_no_plugin_actions__deactivation' ) ) { secupress_no_plugin_actions__deactivation(); } if ( function_exists( 'secupress_pro_no_plugin_actions__deactivation' ) ) { secupress_pro_no_plugin_actions__deactivation(); } } // Removed if ( secupress_is_pro() ) { secupress_deactivate_submodule_silently( 'firewall', 'block-functions' ); secupress_remove_old_plugin_file( SECUPRESS_PRO_MODULES_PATH . 'firewall/plugins/block-functions.php' ); } // Recreate the file if ( secupress_is_submodule_active( 'wordpress-core', 'wp-config-constant-dieondberror' ) ) { secupress_deactivate_submodule( 'wordpress-core', 'wp-config-constant-dieondberror' ); secupress_activate_submodule( 'wordpress-core', 'wp-config-constant-dieondberror' ); } } // < 2.3.18.1 if ( version_compare( $actual_version, '2.3.18.1', '<' ) ) { $plugin_file = secupress_find_mu_plugin( 'salt_keys' ); if ( $plugin_file ) { $plugin_file = reset( $plugin_file ); $plugin_data = get_plugin_data( $plugin_file ); if ( isset( $plugin_data['Version'] ) && version_compare( $plugin_data['Version'], '2.3.17' ) < 0 ) { $filesystem = secupress_get_filesystem(); $content = $filesystem->get_contents( SECUPRESS_INC_PATH . 'data/salt-keys.phps' ); $args = array( '{{PLUGIN_NAME}}' => SECUPRESS_PLUGIN_NAME, '{{HASH1}}' => wp_generate_password( 64, true, true ), '{{HASH2}}' => wp_generate_password( 64, true, true ), ); $content = str_replace( array_keys( $args ), $args, $content ); $filesystem->put_contents( $plugin_file, $content ); } } } // < 2.3.19 if ( version_compare( $actual_version, '2.3.19', '<' ) ) { // Forgot to add our prefix... it won't be uninstalled! $wpdb->query( $wpdb->prepare( "UPDATE $wpdb->usermeta SET meta_key = CONCAT( 'secupress-', meta_key ) WHERE meta_key LIKE %s", 'advanced-settings%m%' ) ); if ( secupress_is_pro() ) { secupress_deactivate_submodule_silently( 'users-login', array( 'password-expiration' ) ); if ( secupress_get_module_option( 'password-policy_password_expiration', 0, 'users-login' ) > 0 && ! secupress_is_submodule_active( 'users-login', 'strong-passwords' ) ) { secupress_add_notice( sprintf( __( 'For information, the module "Password Lifespan" has been deactivated. We cannot reactivated it unless you activate the <a href="%s">module "Force Strong Passwords"</a>.', 'secupress' ), secupress_admin_url( 'modules', 'users-login#row-password-policy_strong_passwords' ) ), 'info', '' ); } } } // < 2.3.20 if ( version_compare( $actual_version, '2.3.20', '<' ) ) { if ( secupress_is_pro() ) { wp_schedule_single_event( time(), 'secupress_malware_files' ); } } // < 2.3.21 if ( version_compare( $actual_version, '2.3.21', '<' ) ) { secupress_create_master_key(); } // < 2.4.1 if ( version_compare( $actual_version, '2.4.1', '<' ) ) { // Migrate attacks data from old format to new format $attack_types = get_option( SECUPRESS_ATTACKS, [] ); if ( ! empty( $attack_types ) ) { secupress_migrate_attacks_data( $attack_types ); } } // < 2.6 if ( version_compare( $actual_version, '2.6', '<' ) ) { delete_site_option( SECUPRESS_WP_CORE_FILES_HASHES ); if ( defined( 'SECUPRESS_CONTENT_ALLOWED' ) ) { $content_allowed = get_site_option( SECUPRESS_CONTENT_ALLOWED ); if ( is_array( $content_allowed ) ) { update_site_option( SECUPRESS_CONTENT_ALLOWED, str_rot13( json_encode( $content_allowed ) ) ); } } } // DEV: DO NOT REDIRECT / DO NOT AUTOLOGIN / DO NOT USE $modulenow // } add_action( 'admin_init', 'secupress_better_changelog' ); /** * If the plugin is secupress free or pro, let's add our changelog content * * @since 1.4.3 * @author Julio Potier **/ function secupress_better_changelog() { if ( isset( $_GET['tab'], $_GET['plugin'], $_GET['section'] ) && ( 'secupress' === $_GET['plugin'] || 'secupress-pro' === $_GET['plugin'] ) && 'changelog' === $_GET['section'] && 'plugin-information' === $_GET['tab'] ) { remove_action( 'install_plugins_pre_plugin-information', 'install_plugin_information' ); add_action( 'install_plugins_pre_plugin-information', 'secupress_hack_changelog' ); } } /** * Will display our changelog content with our CSS * * @since 1.4.3 * @author Julio Potier **/ function secupress_hack_changelog() { global $admin_body_class; $api = plugins_api( 'plugin_information', array( 'slug' => 'secupress', 'is_ssl' => secupress_server_is_ssl(), 'fields' => [ 'short_description' => false, 'reviews' => false, 'downloaded' => false, 'downloadlink' => false, 'last_updated' => false, 'added' => false, 'tags' => false, 'homepage' => false, 'donate_link' => false, 'ratings' => false, 'active_installs' => true, 'banners' => true, 'sections' => true, ] ) ); if ( is_wp_error( $api ) ) { wp_die( $api ); } $changelog_content = $api->sections['changelog']; $changelog_content = explode( "\n", $changelog_content ); $changelog_content = array_slice( $changelog_content, 0, array_search( '</ul>', $changelog_content, true ) ); $changelog_content = array_map( 'strip_tags', $changelog_content ); $changelog_version = array_shift( $changelog_content ); $changelog_content = array_filter( $changelog_content ); $changelog_date = array_shift( $changelog_content ); $pro_suffix = secupress_has_pro() ? 'Pro ' : 'Free '; $banner = secupress_has_pro() ? 'banner-secupress-pro.jpg' : 'banner-1544x500.png'; iframe_header( __( 'Plugin Installation' ) ); ?> <style type="text/css"> body { color: #333; font-family: Helvetica, Arial, sans-serif; margin: 0; padding: 0; background-color: #fff } section { margin: 20px 25px; max-width: 830px } header { position: relative; margin-bottom: 20px; width: 100%; max-width: 830px; height: 276px; color: #fff; } #plugin-information-title.with-banner div.vignette { background-image: url( 'https://plugins.svn.wordpress.org/secupress/assets/<?php echo $banner; ?>' ); background-size: contain; } header h1, header h2 { font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", sans-serif; font-size: 2em; font-weight: normal; margin: 0; color: #fff; line-height: 1em } header h2 { font-size: 1.4em; margin-bottom: 3px } hgroup { float: right; padding-right: 50px } h2 { font-size: 1.2em } ul { margin-bottom: 30px } li { margin-bottom: 0.5em } .changelog tr { line-height: 1.5em; } .changelog td { padding: 3px; font-size: 15px; vertical-align: middle; } .changelog .type { font-size: 12px; text-transform: uppercase; padding-right: 15px; padding-top: 5px; padding-left: 0; text-align: left; color: #999; min-width: 100px; border-right: 2px solid #eee; } .changelog .type, .changelog .description { vertical-align: top; } code { background-color: #EEE; padding: 2px } .star-rating { display: inline; } #plugin-information-footer { text-align: center; line-height: 1.7em; } </style> </head> <body class="$admin_body_class"> <header id="plugin-information-title" class="with-banner"> <div class="vignette"></div> <h2>SecuPress <?php echo $pro_suffix; ?> <?php echo esc_html( $changelog_version ); ?> – <?php echo esc_html( $changelog_date ); ?></h2> </header> <section id="plugin-information-scrollable"> <table class="changelog"> <?php foreach ( $changelog_content as $content ) { if ( ! $content ) { continue; } $content = explode( ' ', $content, 2 ); ?> <tr> <td class="type"><?php echo wp_kses_post( '<strong>' . str_replace( '#', '</strong> #', reset( $content ) ) ); ?></td> <td class="description"><?php echo wp_kses_post( end( $content ) ); ?></td> </tr> <?php } ?> <tr> <td class="type"><strong><?php _e( 'Full Changelog', 'secupress' ); ?></strong></td> <td class="description"><a href="<?php echo SECUPRESS_WEB_MAIN; ?>changelog/" target="_blank"><?php echo SECUPRESS_WEB_MAIN; ?>changelog/</a></td> </tr> </table> <hr> <?php $status = install_plugin_install_status( $api ); if ( $status['url'] ) { echo '<p><a data-slug="' . esc_attr( $api->slug ) . '" data-plugin="' . esc_attr( $status['file'] ) . '" id="plugin_update_from_iframe" class="button button-primary right" href="' . esc_url( $status['url'] ) . '" target="_parent">' . __( 'Install Update Now' ) . '</a></p>'; } if ( ! secupress_has_pro() ) { ?> <p><a href="<?php echo SECUPRESS_WEB_MAIN; ?>pricing/" class="button button-secondary"><?php _e( 'Get SecuPress Pro Now!', 'secupress' ); ?></a></p> <?php } ?> </section> <div id="plugin-information-footer"> <strong><?php _e( 'Requires WordPress Version:' ); ?></strong> <?php printf( __( '%s or higher' ), $api->requires ); if ( ! empty( $api->requires_php ) ) { echo '& PHP ' . printf( __( '%s or higher' ), $api->requires ); } ?> | <strong><?php _e( 'Compatible up to:' ); ?></strong> <?php echo $api->tested; ?> <br> <strong><?php _e( 'Active Installations:' ); ?></strong> <?php if ( $api->active_installs >= 1000000 ) { _ex( '1+ Million', 'Active plugin installations' ); } elseif ( 0 === $api->active_installs ) { _ex( 'Less Than 10', 'Active plugin installations' ); } else { echo number_format_i18n( $api->active_installs ) . '+'; } ?> | <strong><?php _e( 'Average Rating' ); ?>:</strong> <?php wp_star_rating( [ 'type' => 'percent', 'rating' => $api->rating, 'number' => $api->num_ratings ] ); ?> <p aria-hidden="true" class="fyi-description"><?php printf( _n( '(based on %s rating)', '(based on %s ratings)', $api->num_ratings ), number_format_i18n( $api->num_ratings ) ); ?></p> <br> </div> <?php iframe_footer(); exit; } if ( ! secupress_is_white_label() ) { add_action( 'admin_notices', 'secupress_display_whats_new' ); /** * Display a "what's new" notice when not in WhiteLabel and user has the correct capa * * @since 2.3 SECUPRESS_MAJOR_VERSION * @since 2.0 secupress_add_transient_notice + SECUPRESS_VERSION * @since 1.4.10 * @author Julio Potier * * @hook admin_notices * @return (void) **/ function secupress_display_whats_new() { $notice_id1 = 'new-' . sanitize_key( SECUPRESS_MAJOR_VERSION ); // $notice_id2 = 'new-' . sanitize_key( SECUPRESS_VERSION ); if ( current_user_can( secupress_get_capability() ) && ! secupress_notice_is_dismissed( $notice_id1 ) ) { $title = sprintf( '<strong>' . __( 'What’s new in SecuPress %s%s', 'secupress' ) . '</strong>', defined( 'SECUPRESS_PRO_VERSION' ) ? 'Pro ' : '', SECUPRESS_MAJOR_VERSION ); $readmore = '<a href="https://secupress.me/changelog" target="_blank"><em>' . __( 'Or read full changelog on secupress.me', 'secupress' ) . '</em></a>'; $blogpost = __( 'https://secupress.me/blog/secupress-v2-6/', 'secupress' ); $newitems = [ __( 'New: GeoIP Location on Login', 'secupress' ), __( 'New: Search field in admin UI.', 'secupress' ), __( 'Improvement: UI for Malware Scanner has been improved.', 'secupress' ), __( '6 more fixes.', 'secupress' ), make_clickable($blogpost) ]; if ( ! empty( $newitems ) ) { $newitems = '<ul><li>• ' . implode( '</li><li>• ', $newitems ) . '</li></ul>'; secupress_add_transient_notice( $title . $newitems . $readmore, 'updated', $notice_id1 ); // secupress_dismiss_notice( $notice_id2 ); // Do not show the second one. } return; } // else { // if ( current_user_can( secupress_get_capability() ) && ! secupress_notice_is_dismissed( $notice_id2 ) ) { // $title = sprintf( '<strong>' . __( 'What’s new in SecuPress %s%s', 'secupress' ) . '</strong>', defined( 'SECUPRESS_PRO_VERSION' ) ? 'Pro ' : '', SECUPRESS_VERSION ); // $readmore = '<a href="https://secupress.me/changelog" target="_blank"><em>' . __( 'Or read full changelog on secupress.me', 'secupress' ) . '</em></a>'; // $newitems = [ // __( 'Here are some key improvement for this version:', 'secupress' ), // __( 'Fix: JS Error in Console related to delayed comments', 'secupress' ), // __( 'Improve: <em>"Strong Passwords"</em> and <em>"Bad Usernames"</em> module are now bypassable using the already existing constant <code>SECUPRESS_ALLOW_LOGIN_ACCESS</code>', 'secupress' ), // ]; // if ( secupress_is_pro() ) { // $newitems[] = __( 'Improve: Our data files are now stored in <code>/wp-content/secupress-data/</code> instead of inside the plugin to prevent data deletion on each plugin update.', 'secupress' ); // } // if ( ! empty( $newitems ) ) { // $newitems = '<ul><li>• ' . implode( '</li><li>• ', $newitems ) . '</li></ul>'; // secupress_add_transient_notice( $title . $newitems . $readmore, 'updated', $notice_id2 ); // } // } // } } } free/admin/scanner-step-3.php 0000644 00000035111 15174670627 0012050 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); // Keep only scans with "bad" and "warning" status. $bad_scan_results = array_merge( $bad_scans, $warning_scans ); // `array( $class_name_part_lower => $status )`. $fix_actions = array(); // `array( $class_name_part_lower => array( $fix_action, $fix_action ) )`. // We'll order the tests depending if they're fixable in Pro, manually, etc. $tests_1 = []; // 1: fix action. $tests_2 = []; // 2: manual. $tests_3 = []; // 3: fallback. $tests_4 = []; // 4: fix auto failed or can't proceed further. $tests_5 = []; // 5: pro. $tests_6 = []; // 6: a list to be moved in last position when fixing things, order is imporant. $lasts = [ 'WP_Config' => true, 'Salt_Keys' => true ]; /** * Keep only scanners where: * - it needs a manual fix, * - or, is not fixable by SecuPress (it needs the user to go to the hoster administration interface), * - or, is fixable only with the Pro Version (and we use the Free version), * - or, an automatic fix has been attempted (but maybe it's an old result), * - or, the scan status is a "warning", * Also, require the scan files + get the "fix actions". */ foreach ( $secupress_tests as $module_name => $class_name_parts ) { $class_name_parts = array_combine( array_map( 'strtolower', $class_name_parts ), $class_name_parts ); $class_name_parts = array_intersect_key( $class_name_parts, $bad_scan_results ); // Only "bad" and "warning" status. if ( ! $class_name_parts ) { unset( $secupress_tests[ $module_name ] ); continue; } foreach ( $class_name_parts as $class_name_part_lower => $class_name_part ) { if ( ! file_exists( secupress_class_path( 'scan', $class_name_part ) ) ) { // Excluded. unset( $bad_scan_results[ $class_name_part_lower ] ); continue; } secupress_require_class( 'scan', $class_name_part ); $class_name = 'SecuPress_Scan_' . $class_name_part; $current_test = $class_name::get_instance(); $is_fixable = $current_test->is_fixable(); $this_fix_actions = $current_test->need_manual_fix(); // Those that need a manual fix. if ( is_array( $this_fix_actions ) && ( ( secupress_is_pro() && 'pro' !== $is_fixable ) || 'pro' !== $is_fixable ) ) { // Store the "fix actions". if ( $this_fix_actions ) { $fix_actions[ $class_name_part_lower ] = $this_fix_actions; if ( isset( $lasts[ $class_name_part ] ) ) { $tests_6[ $class_name_part ] = $module_name; // last } else { $tests_1[ $class_name_part ] = $module_name; // 1: fix action. } } else { // Doesn't need to be fixed, the scan is simply not up to date. Excluded. unset( $bad_scan_results[ $class_name_part_lower ] ); } continue; } // Pro. if ( 'pro' === $is_fixable && ! secupress_is_pro() ) { if ( isset( $lasts[ $class_name_part ] ) ) { $tests_6[ $class_name_part ] = $module_name; // last } else { $tests_5[ $class_name_part ] = $module_name; // 5: pro. } continue; } // Only fixable manually. if ( false === $is_fixable ) { if ( isset( $lasts[ $class_name_part ] ) ) { $tests_6[ $class_name_part ] = $module_name; // last } else { $tests_2[ $class_name_part ] = $module_name; // 2: manual. } continue; } // An automatic fix has been attempted (and failed or can't do more). if ( ! empty( $fixes[ $class_name_part_lower ] ) ) { if ( isset( $lasts[ $class_name_part ] ) ) { $tests_6[ $class_name_part ] = $module_name; // last } else { $tests_4[ $class_name_part ] = $module_name; // 4: fix auto failed or can't proceed further. } continue; } // A "bad" scan status means the user didn't try to fix it. if ( secupress_is_pro() && 'warning' !== $bad_scan_results[ $class_name_part_lower ] ) { // Excluded. unset( $bad_scan_results[ $class_name_part_lower ] ); continue; } // Should not happen. if ( isset( $lasts[ $class_name_part ] ) ) { $tests_6[ $class_name_part ] = $module_name; // last } else { $tests_3[ $class_name_part ] = $module_name; // 3: fallback. } } } $secupress_tests = array_merge( $tests_5, $tests_1, $tests_2, $tests_3, $tests_4, $tests_6 ); unset( $tests_1, $tests_2, $tests_3, $tests_4, $tests_5, $tests_6, $lasts ); if ( ! $secupress_tests ) { ?> <div class="secupress-step-content-header secupress-flex secupress-flex-spaced"> <p class="secupress-step-title"><?php _e( 'Nothing to do here', 'secupress' ); ?></p> <p class="secupress-flex"> <a href="<?php echo esc_url( secupress_admin_url( 'scanners' ) ); ?>&step=4" class="secupress-button shadow light"> <span class="icon"> <i class="secupress-icon-cross" aria-hidden="true"></i> </span> <span class="text"><?php _e( 'Next step', 'secupress' ); ?></span> </a> </p> </div> <?php return; } ?> <div class="secupress-step-content-header secupress-flex secupress-flex-spaced"> <?php $nb_actions = count( $secupress_tests ); $page_title = sprintf( _n( '%d issue requires your attention', '%d issues require your attention', $nb_actions, 'secupress' ), $nb_actions ); $steps_counter = '<span class="secupress-step-by-step secupress-flex hide-if-no-js">' . /** Translators: Params are numbers like "1 of 3" */ sprintf( __( '%1$s of %2$s', 'secupress' ), '<span class="text step3-advanced-text">1</span>', '<span class="text">' . $nb_actions . '</span>' ) . '</span>'; $main_button = '<a href="' . esc_url( secupress_admin_url( 'scanners' ) ) . '&step=4" class="secupress-button shadow light"> <span class="icon"> <i class="secupress-icon-angle-double-right" aria-hidden="true"></i> </span> <span class="text">' . __( 'Ignore all & Go to the next step', 'secupress' ) . '</span> </a>'; ?> <p class="secupress-step-title"><?php echo $page_title; ?></p> <p class="secupress-flex"> <?php echo $steps_counter; ?> <?php echo $main_button; ?> </p> </div> <div id="secupress-tests" class="secupress-tests"> <?php $modules = secupress_get_modules(); $hidden_class = ''; foreach ( $secupress_tests as $class_name_part => $module_name ) { $class_name_part_lower = strtolower( $class_name_part ); $class_name = 'SecuPress_Scan_' . $class_name_part; $current_test = $class_name::get_instance(); $referer = urlencode( esc_url_raw( self_admin_url( 'admin.php?page=' . SECUPRESS_PLUGIN_SLUG . '_scanners&step=3#' . $class_name_part ) ) ); $module_icon = ! empty( $modules[ $module_name ]['icon'] ) ? $modules[ $module_name ]['icon'] : ''; // Scan. $scanner = isset( $scanners[ $class_name_part_lower ] ) ? $scanners[ $class_name_part_lower ] : array(); $scan_status = $scanner['status']; $scan_nonce_url = wp_nonce_url( admin_url( 'admin-post.php?action=secupress_scanner&test=' . $class_name_part . '&_wp_http_referer=' . $referer ), 'secupress_scanner_' . $class_name_part ); // Fix. $fix_result = ! empty( $fixes[ $class_name_part_lower ] ) ? $fixes[ $class_name_part_lower ] : array(); $fix_status = ! empty( $fix_result['status'] ) ? $fix_result['status'] : true; // State. $has_actions = ! empty( $fix_actions[ $class_name_part_lower ] ); $needs_pro = 'pro' === $current_test->is_fixable() && ! secupress_is_pro(); $is_fixable = true === $current_test->is_fixable() || 'pro' === $current_test->is_fixable() && secupress_is_pro(); $not_fixable_by_sp = false === $current_test->is_fixable(); $is_fixable_with_action = $is_fixable && $has_actions; // Row css class. $row_css_class = 'secupress-item-' . $class_name_part; $row_css_class .= ' status-' . sanitize_html_class( $scan_status ); $row_css_class .= $is_fixable_with_action ? ' fixable' : ' not-fixable'; ?> <div class="secupress-manual-fix secupress-manual-fix-<?php echo $module_name; ?> secupress-group-item-<?php echo $class_name_part; ?><?php echo $hidden_class; ?>"> <div class="secupress-mf-header secupress-flex-spaced<?php echo $needs_pro ? ' secupress-is-only-pro' : '' ?>"> <div class="secupress-mfh-name secupress-flex"> <span class="secupress-header-dot"> <span class="secupress-dot-warning"></span> </span> <p class="secupress-mfh-title"><?php echo $current_test->title; ?></p> <?php if ( ! $needs_pro ) { ?> <i class="secupress-icon-<?php echo $module_icon; ?>" aria-hidden="true"></i> <?php } else { ?> <div class="secupress-mfh-pro"> <p class="secupress-get-pro-version"> <?php printf( __( 'Available in <a href="%s" target="_blank">Pro Version</a>', 'secupress' ), esc_url( secupress_admin_url( 'get-pro' ) ) ); ?> </p> </div> <?php } ?> </div> </div><!-- .secupress-mf-header --> <div id="secupress-mf-content-<?php echo $class_name_part; ?>" class="secupress-mf-content <?php echo $row_css_class; ?>" data-scan-url="<?php echo esc_url( $scan_nonce_url ); ?>"> <?php if ( $is_fixable_with_action ) { ?> <form class="secupress-item-content" method="post" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>"> <?php } else { ?> <div class="secupress-item-content"> <?php } ?> <p class="secupress-ic-title"> <?php _e( 'How to fix this issue', 'secupress' ); ?> </p> <p class="secupress-ic-desc"> <?php // Case 1: needs Pro, or not fixable by SecuPress (example: DB password). if ( ! $is_fixable ) { if ( ! empty( $scanner['msgs'] ) ) { $message = secupress_format_message( $scanner['msgs'], $class_name_part ); } else { $message = $current_test->more_fix; } echo wp_kses( $message, $allowed_tags ); } // Case 2: can be fixed manually (form). elseif ( $needs_pro || $has_actions ) { echo wp_kses( $current_test->more_fix, $allowed_tags ); } // Automatic fix failed. elseif ( $fix_result ) { // Case 3: the fix has been applied but the flaw persists (bug?), or the flaw reappeared (example: the user enabled debug in wp-config.php). if ( 'good' === $fix_status && ! empty( $scanner['msgs'] ) ) { $message = secupress_format_message( $scanner['msgs'], $class_name_part ); } // Case 4: the fix couldn't be applied (example: `.htaccess` not writable). elseif ( ! empty( $fix_result['msgs'] ) ) { $message = secupress_format_message( $fix_result['msgs'], $class_name_part ); } // Fallback 1, shouldn't happen: display the scan message. elseif ( ! empty( $scanner['msgs'] ) ) { $message = secupress_format_message( $scanner['msgs'], $class_name_part ); } // Fallback 2, shouldn't happen: display the "more fix" text. else { $message = $current_test->more_fix; } echo wp_kses( $message, $allowed_tags ); } // Case 4: the scan status is a "warning", and no fix have been tried yet (example: unable to reach homepage). elseif ( 'warning' === $scan_status && ! empty( $scanner['msgs'] ) ) { echo wp_kses( secupress_format_message( $scanner['msgs'], $class_name_part ), $allowed_tags ); } // Fallback 3, shouldn't happen: display the "more fix" text. else { echo wp_kses( $current_test->more_fix, $allowed_tags ); } ?> </p> <?php if ( $is_fixable_with_action ) { ?> <div class="secupress-ic-fix-actions"> <?php $current_test->get_required_fix_action_template_parts( $fix_actions[ $class_name_part_lower ] ); ?> </div> <?php } ?> <div class="secupress-row-actions secupress-flex secupress-flex-spaced secupress-mt2"> <?php if ( ! secupress_is_white_label() ) { ?> <p class="secupress-action-doc"> <a href="<?php echo esc_url( $current_test::get_docs_url() ); ?>" class="secupress-button secupress-button-doc shadow" target="_blank" title="<?php esc_attr_e( 'Open in a new window.', 'secupress' ); ?>"> <span class="icon"> <i class="secupress-icon-file-text" aria-hidden="true"></i> </span> <span class="text"><?php _e( 'Documentation', 'secupress' ); ?></span> </a> </p> <?php } ?> <p class="secupress-actions"> <a href="<?php echo esc_url( secupress_admin_url( 'scanners' ) ); ?>&step=4" class="secupress-button secupress-button-ignoreit hide-is-no-js shadow light" data-parent="secupress-group-item-<?php echo $class_name_part; ?>"> <span class="icon"> <i class="secupress-icon-cross" aria-hidden="true"></i> </span> <span class="text"><?php _e( 'Ignore it', 'secupress' ); ?></span> </a> <?php if ( $not_fixable_by_sp || 'cantfix' === $fix_status ) { ?> <a href="<?php echo esc_url( $scan_nonce_url ); ?>" class="secupress-button secupress-button-primary secupress-button-manual-scanit shadow"> <span class="icon"> <i class="secupress-icon-check" aria-hidden="true"></i> </span> <span class="text"><?php _e( 'I did the job, continue', 'secupress' ); ?></span> </a> <?php } elseif ( $needs_pro ) { ?> <a href="<?php echo esc_url( secupress_admin_url( 'get-pro' ) ); ?>" class="secupress-button secupress-button-tertiary secupress-button-getpro shadow" target="_blank" title="<?php esc_attr_e( 'Open in a new window.', 'secupress' ); ?>"> <span class="icon"> <i class="secupress-icon-secupress-simple bold" aria-hidden="true"></i> </span> <span class="text"><?php _e( 'Get Pro', 'secupress' ); ?></span> </a> <?php } elseif ( $is_fixable_with_action ) { ?> <button type="submit" class="secupress-button secupress-button-primary secupress-button-manual-fixit shadow"> <span class="icon"> <i class="secupress-icon-check" aria-hidden="true"></i> </span> <span class="text"><?php _e( 'Fix it and continue', 'secupress' ); ?></span> </button> <?php } ?> </p> </div> <?php if ( $is_fixable_with_action ) { ?> </form><!-- .secupress-item-content --> <?php } else { ?> </div><!-- .secupress-item-content --> <?php } if ( apply_filters( 'secupress.settings.help', true ) ) { ?> <div class="secupress-item-details" id="details-<?php echo $class_name_part; ?>"> <div class="secupress-flex"> <span class="secupress-details-icon"> <i class="secupress-icon-i" aria-hidden="true"></i> </span> <p class="details-content"><?php echo wp_kses( $current_test->more, $allowed_tags ); ?></p> <span class="secupress-placeholder"></span> </div> </div> <?php } ?> </div><!-- .secupress-mf-content --> </div><!-- .secupress-manual-fix --> <?php $hidden_class = ' hide-if-js'; } // Eo foreach $secupress_tests. ?> </div><!-- .secupress-tests --> free/admin/scanner-step-1-all.php 0000644 00000012543 15174670627 0012620 0 ustar 00 <div class="secupress-scans-group secupress-group-<?php echo $module_name; ?>"> <?php if ( ! $is_subsite ) { $module_icon = ! empty( $modules[ $module_name ]['icon'] ) ? $modules[ $module_name ]['icon'] : ''; $module_title = ! empty( $modules[ $module_name ]['title'] ) ? $modules[ $module_name ]['title'] : ''; $module_summary = ! empty( $modules[ $module_name ]['summaries']['small'] ) ? $modules[ $module_name ]['summaries']['small'] : ''; ?> <div class="secupress-sg-header secupress-flex secupress-flex-spaced"> <div class="secupress-sgh-name"> <i class="secupress-icon-<?php echo $module_icon; ?>" aria-hidden="true"></i> <p class="secupress-sgh-title"><?php echo $module_title; ?></p> <p class="secupress-sgh-description"><?php echo $module_summary; ?></p> </div> <div class="secupress-sgh-actions secupress-flex secupress-flex-top"> <a href="<?php echo secupress_admin_url( 'modules' ) . '&module=' . $module_name; ?>" target="_blank" class="secupress-link-icon secupress-vcenter"> <span class="icon"><i class="secupress-icon-cog" aria-hidden="true"></i></span> <span class="text"><?php _e( 'Go to module settings', 'secupress' ); ?></span> </a> <button class="secupress-vnormal hide-if-no-js dont-trigger-hide trigger-hide-first" type="button" data-trigger="slidetoggle" data-target="secupress-group-content-<?php echo $module_name; ?>"> <i class="secupress-icon-angle-up" aria-hidden="true"></i> <span class="screen-reader-text"><?php _e( 'Show/hide panel', 'secupress' ); ?></span> </button> </div> </div><!-- .secupress-sg-header --> <?php } ?> <div id="secupress-group-content-<?php echo $module_name; ?>" class="secupress-sg-content"> <?php foreach ( $class_name_parts as $option_name => $class_name_part ) { $class_name = 'SecuPress_Scan_' . $class_name_part; $current_test = $class_name::get_instance(); $referer = urlencode( esc_url_raw( self_admin_url( 'admin.php?page=' . SECUPRESS_PLUGIN_SLUG . '_scanners' . ( $is_subsite ? '' : '#' . $class_name_part ) ) ) ); // Scan. $scanner = isset( $scanners[ $option_name ] ) ? $scanners[ $option_name ] : array(); $scan_status = ! empty( $scanner['status'] ) ? $scanner['status'] : 'notscannedyet'; $scan_nonce_url = 'secupress_scanner_' . $class_name_part . ( $is_subsite ? '-' . $site_id : '' ); $scan_nonce_url = wp_nonce_url( admin_url( 'admin-post.php?action=secupress_scanner&test=' . $class_name_part . '&_wp_http_referer=' . $referer . ( $is_subsite ? '&for-current-site=1&site=' . $site_id : '' ) ), $scan_nonce_url ); $scan_message = $current_test->title; if ( ! empty( $scanner['msgs'] ) ) { $scan_message = secupress_format_message( $scanner['msgs'], $class_name_part ); } // Row css class. $row_css_class = 'secupress-item-' . $class_name_part; $row_css_class .= ' status-' . sanitize_html_class( $scan_status ); $row_css_class .= isset( $autoscans[ $class_name_part ] ) ? ' autoscan' : ''; ?> <div class="secupress-item-all <?php echo $row_css_class; ?>" id="<?php echo $class_name_part; ?>"> <div class="secupress-flex"> <p class="secupress-item-status"> <span class="secupress-label"><?php echo secupress_status( $scan_status ); ?></span> </p> <p class="secupress-item-title"><?php echo $scan_message; ?></p> <p class="secupress-row-actions"> <a class="secupress-button secupress-button-mini secupress-scanit light hide-if-js" href="<?php echo esc_url( $scan_nonce_url ); ?>"> <span class="icon" aria-hidden="true"> <i class="secupress-icon-refresh"></i> </span> <span class="text"> <?php _ex( 'Scan', 'verb', 'secupress' ); ?> </span> </a><br class="hide-if-js"> <?php /** * Things changed: * data-trigger added * data-target instead of data-test * data-target === .secupress-item-details' ID */ if ( apply_filters( 'secupress.settings.help', true ) ) { ?> <button data-trigger="slidetoggle" data-target="details-<?php echo $class_name_part; ?>" class="secupress-details link-like hide-if-no-js" type="button"> <span class="secupress-toggle-button"> <span aria-hidden="true" class="icon"> <i class="secupress-icon-info-disk"></i> </span> <span class="text"><?php _e( 'Learn more', 'secupress' ); ?></span> </span> <span class="secupress-toggle-button hidden" aria-hidden="true"> <span aria-hidden="true" class="icon"> <i class="secupress-icon-cross"></i> </span> <span class="text"><?php _e( 'Close' ); ?></span> </span> </button> <?php } ?> </p> </div><!-- .secupress-flex --> <div class="secupress-item-details hide-if-js" id="details-<?php echo $class_name_part; ?>"> <div class="secupress-flex"> <span class="secupress-details-icon"> <i class="secupress-icon-i" aria-hidden="true"></i> </span> <p class="details-content"><?php echo wp_kses( $current_test->more, $allowed_tags ); ?></p> <span class="secupress-placeholder"></span> </div> </div><!-- .secupress-item-details --> </div><!-- .secupress-item-all --> <?php } // Eo foreach $class_name_parts. ?> </div><!-- .secupress-sg-content --> </div><!-- .secupress-scans-group --> <?php free/admin/functions/modules.php 0000644 00000011323 15174670627 0012765 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * This is used when submitting a module form. * If we submitted the given module form, it will return an array containing the values of sub-modules to activate. * * @since 2.3.12 Return the previous result instead of false when done * @since 1.0 * * @param (string) $module The module. * * @return (array|bool) False if we're not submitting the module form. An array like `array( 'submodule1' => 1, 'submodule2' => 1 )` otherwise. */ function secupress_get_submodule_activations( $module ) { static $done = []; if ( isset( $done[ $module ] ) ) { return $done[ $module ]; } if ( isset( $_POST['option_page'] ) && 'secupress_' . $module . '_settings' === $_POST['option_page'] ) { // WPCS: CSRF ok. $return = isset( $_POST['secupress-plugin-activation'] ) && is_array( $_POST['secupress-plugin-activation'] ) ? $_POST['secupress-plugin-activation'] : array(); // WPCS: CSRF ok. $done[ $module ] = $return; return $return; } return false; } /** * Invert the roles values in settings. * Note: the "*_affected_role" options are misnamed, they should be called "*_excluded_roles", because we store roles that won't be affected. * * @since 1.0 * * @param (array) $settings The settings passed by reference. * @param (string) $module The module. * @param (string) $plugin The plugin. */ function secupress_manage_affected_roles( &$settings, $module, $plugin ) { static $roles; if ( ! isset( $roles ) ) { $roles = new WP_Roles(); $roles = $roles->get_names(); $roles = array_flip( $roles ); $roles = array_combine( $roles, $roles ); } if ( empty( $settings[ $plugin . '_affected_role' ]['witness'] ) ) { // Use old values, `$settings` does not come from our module page. $old_settings = get_site_option( "secupress_{$module}_settings" ); if ( empty( $old_settings[ $plugin . '_affected_role' ] ) || ! is_array( $old_settings[ $plugin . '_affected_role' ] ) ) { // All roles. unset( $settings[ $plugin . '_affected_role' ] ); } else { // Old roles that still exist. $settings[ $plugin . '_affected_role' ] = array_intersect( $roles, $old_settings[ $plugin . '_affected_role' ] ); } } else { // Reverse submited values to store the excluded roles. if ( empty( $settings[ $plugin . '_affected_role' ] ) || ! is_array( $settings[ $plugin . '_affected_role' ] ) ) { // We won't allow to have no roles set, so we take them all. unset( $settings[ $plugin . '_affected_role' ] ); } else { // Roles that are not selected. $settings[ $plugin . '_affected_role' ] = array_diff( $roles, $settings[ $plugin . '_affected_role' ] ); } } // Useless, just to be sure. unset( $settings[ $plugin . '_affected_role' ]['witness'] ); if ( empty( $settings[ $plugin . '_affected_role' ] ) || $roles === $settings[ $plugin . '_affected_role' ] ) { // We won't allow to have no roles set, so we take them all. unset( $settings[ $plugin . '_affected_role' ] ); } } /** * Returns a i18n message used with a packed plugin activation checkbox to tell the user that the standalone plugin will be deactivated. * * @since 1.0 * * @param (string) $plugin_basename The standalone plugin basename. * * @return (string|null) Return null if the plugin is not activated. */ function secupress_get_deactivate_plugin_string( $plugin_basename ) { if ( ! secupress_is_plugin_active( $plugin_basename ) ) { return null; } $plugin_basename = path_join( WP_PLUGIN_DIR, $plugin_basename ); $plugin = get_plugin_data( $plugin_basename, false, false ); return sprintf( __( 'will deactivate the plugin %s.', 'secupress' ), '<strong>' . $plugin['Name'] . '</strong>' ); } /** * Returns a i18n message used with a packed plugin activation checkbox to tell the user that a plugin doing the same thing is already active * * @since 1.3.2 * * @param (string) $plugin_basename The standalone plugin basename. * @param (string) $settings_page The possible setting page. * * @return (string|null) Return null if the plugin is not activated. */ function secupress_plugin_in_usage_string( $plugin_basename, $settings_page = '' ) { if ( ! secupress_is_plugin_active( $plugin_basename ) ) { return null; } $plugin_basename = path_join( WP_PLUGIN_DIR, $plugin_basename ); $plugin = get_plugin_data( $plugin_basename, false, false ); $content = sprintf( __( 'You cannot use this feature now because you are using the plugin %s. Please deactivate it.', 'secupress' ), '<strong>' . esc_html( $plugin['Name'] ) . '</strong>' ); if ( $settings_page ) { $content .= sprintf( '<br><a href="%s">' . __( 'Open the %s settings page', 'secupress' ) . '.</a>', esc_url( admin_url( $settings_page ) ), '<strong>' . esc_html( $plugin['Name'] ) . '</strong>' ); } return $content; } free/admin/functions/admin.php 0000644 00000017415 15174670627 0012415 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Enqueue SweetAlert script and style. * * @since 1.0 * @author Grégory Viguier */ function secupress_enqueue_sweet_alert() { static $done = false; if ( $done ) { return; } if ( ! did_action( 'admin_enqueue_scripts' ) ) { add_action( 'admin_enqueue_scripts', __FUNCTION__ ); return; } $done = true; $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'; $version = $suffix ? '1.3.4' : time(); // Enqueue Swal2 CSS. wp_enqueue_style( 'wpmedia-css-sweetalert2', SECUPRESS_ADMIN_CSS_URL . 'sweetalert2' . $suffix . '.css', array(), $version ); // Enqueue Swal2 JS. wp_enqueue_script( 'wpmedia-js-sweetalert2', SECUPRESS_ADMIN_JS_URL . 'sweetalert2' . $suffix . '.js', array( 'jquery' ), $version, true ); } /** * Enqueue styles for not generic SP notices (OCS, Key API). * * @since 1.0 * @author Geoffrey */ function secupress_enqueue_notices_styles() { static $done = false; if ( $done ) { return; } if ( ! did_action( 'admin_enqueue_scripts' ) ) { add_action( 'admin_enqueue_scripts', __FUNCTION__ ); return; } $done = true; $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'; $version = $suffix ? SECUPRESS_VERSION : time(); wp_enqueue_style( 'secupress-notices', SECUPRESS_ADMIN_CSS_URL . 'secupress-notices' . $suffix . '.css', array(), $version ); } /** * Used for the "last 5 scans", formate each row. * * @since 1.0 * * @param (array) $item An item array containing "percent", "time" and "grade". * @param (int) $last_percent Percentage of the previous item. -1 for the first one. * * @return (string) */ function secupress_formate_latest_scans_list_item( $item, $last_percent = -1 ) { $icon = 'minus'; $time_offset = get_option( 'gmt_offset' ) * HOUR_IN_SECONDS; if ( $last_percent > -1 ) { if ( $last_percent < $item['percent'] ) { $icon = 'grade-up'; } elseif ( $last_percent > $item['percent'] ) { $icon = 'grade-down'; } } return sprintf( '<li> <span class="secupress-latest-list-time timeago">%3$s</span> <span class="secupress-latest-list-date">%4$s</span> <strong class="secupress-latest-list-grade letter l%2$s">%2$s</strong> <i class="mini secupress-icon-%1$s" aria-hidden="true"></i> </li>', $icon, $item['grade'], sprintf( _x( '%s ago', 'date', 'secupress' ), human_time_diff( $item['time'] ) ), date_i18n( _x( 'M dS, Y \a\t h:ia', 'date', 'secupress' ), $item['time'] + $time_offset ) ); } /** * Return a <table> containing 2 strings displayed with the Diff_Renderer from WP Core. * * @since 1.0 * * @param (string) $left_string 1st text to compare. * @param (string) $right_string 2nd text to compare. * @param (array) $args An array of arguments (titles). * * @return (string) */ function secupress_text_diff( $left_string, $right_string, $args = array() ) { global $wp_local_package; if ( ! class_exists( 'WP_Text_Diff_Renderer_Table' ) ) { require_once( ABSPATH . WPINC . '/wp-diff.php' ); } if ( ! class_exists( 'SecuPress_Text_Diff_Renderer_Table' ) ) { /** * Table renderer to display the diff lines. * * @since 1.0 * @uses WP_Text_Diff_Renderer_Table Extends */ class SecuPress_Text_Diff_Renderer_Table extends WP_Text_Diff_Renderer_Table { /** * Number of leading context "lines" to preserve. * * @var int * @access public * @since 1.0 */ public $_leading_context_lines = 0; /** * Number of trailing context "lines" to preserve. * * @var int * @access public * @since 1.0 */ public $_trailing_context_lines = 0; } } $args = wp_parse_args( $args, array( 'title' => __( 'File Differences', 'secupress' ), 'title_left' => __( 'Real file', 'secupress' ), 'title_right' => __( 'Your file', 'secupress' ), ) ); $left_string = normalize_whitespace( $left_string ); $right_string = normalize_whitespace( $right_string ); $left_lines = explode( "\n", $left_string ); $right_lines = explode( "\n", $right_string ); $text_diff = new Text_Diff( $left_lines, $right_lines ); $renderer = new SecuPress_Text_Diff_Renderer_Table( $args ); $diff = $renderer->render( $text_diff ); $encoded_diff = urlencode( str_replace( __( 'Added:' ), '', trim( wp_strip_all_tags( $diff ) ) ) ); $compare_diff = '%26nbsp%3B+%24wp_local_package+%3D+%26%23039%3B'.$wp_local_package.'%26%23039%3B%3B'; if ( ( ! $wp_local_package && ! $diff ) || ( $wp_local_package && ( ! $diff || strcmp( $compare_diff, $encoded_diff ) === 0 ) ) ) { $diff = '<tr><td>// ' . __( 'No differences', 'secupress' ) . '</td><td>// ' . __( 'No differences', 'secupress' ) . '</td></tr>'; } $r = "<table class='diff is-split-view'>\n"; $r .= '<thead>'; $r .= '<tr class="diff-title"><th colspan="2">' . $args['title'] . "</th></tr>\n"; $r .= "</thead>\n"; $r .= '<tbody>'; $r .= "<tr class='diff-sub-title'>\n"; $r .= "\t<th>$args[title_left]</th>\n"; $r .= "\t<th>$args[title_right]</th>\n"; $r .= "</tr>\n"; $r .= $diff; $r .= "</tbody>\n"; $r .= "</table>\n"; return $r; } /** * Keep the old scan report (grade + status) to be compared on step4 * * @since 1.0 * @author Julio Potier */ function secupress_set_old_report() { $grade = secupress_get_scanner_counts( 'grade' ); $report = secupress_get_scan_results(); update_option( 'secupress_step1_report', array( 'grade' => $grade, 'report' => $report ) ); } /** * Return the old scan report. * * @since 1.0 * @author Julio Potier * @see secupress_set_old_report() * * @return (array|false) */ function secupress_get_old_report() { return get_option( 'secupress_step1_report' ); } /** * Print Marketing block with SecuPress pro advantages. * * @since 1.0 * @author Geoffrey Crofte */ function secupress_print_pro_advantages() { ?> <div class="secupress-flex secupress-wrap secupress-pt1 secupress-pb1 secupress-pro-advantages"> <div class="secupress-col-1-2 secupress-flex secupress-landscape-blob"> <div class="secupress-col"> <i class="secupress-icon-antispam" aria-hidden="true"></i> </div> <div class="secupress-col"> <p class="secupress-blob-title"><?php _e( 'Anti-Spam', 'secupress' ); ?></p> <p class="secupress-blob-desc"><?php _e( 'Bots represent about 60% of internet traffic. Don’t let them add their spam to your site!', 'secupress' ); ?></p> </div> </div> <div class="secupress-col-1-2 secupress-flex secupress-landscape-blob"> <div class="secupress-col"> <i class="secupress-icon-information" aria-hidden="true"></i> </div> <div class="secupress-col"> <p class="secupress-blob-title"><?php _e( 'Alerts', 'secupress' ); ?></p> <p class="secupress-blob-desc"><?php _e( 'Get alerts via SMS, mobile notifications, or even by social networks in addition to email.', 'secupress' ); ?></p> </div> </div> <div class="secupress-col-1-2 secupress-flex secupress-landscape-blob"> <div class="secupress-col"> <i class="secupress-icon-firewall" aria-hidden="true"></i> </div> <div class="secupress-col"> <p class="secupress-blob-title"><?php _e( 'Firewall', 'secupress' ); ?></p> <p class="secupress-blob-desc"><?php _e( 'Other features of the firewall add an additional level of protection from Internet attacks.', 'secupress' ); ?></p> </div> </div> <div class="secupress-col-1-2 secupress-flex secupress-landscape-blob"> <div class="secupress-col"> <i class="secupress-icon-logs" aria-hidden="true"></i> </div> <div class="secupress-col"> <p class="secupress-blob-title"><?php _ex( 'Logs', 'post type general name', 'secupress' ); ?></p> <p class="secupress-blob-desc"><?php _e( 'All actions considered dangerous are kept in this log available at any time to check what is happening on your site.', 'secupress' ); ?></p> </div> </div> </div> <?php } free/admin/functions/ajax-post.php 0000644 00000012022 15174670627 0013220 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** --------------------------------------------------------------------------------------------- */ /** AJAX AND POST HELPERS ======================================================================= */ /** --------------------------------------------------------------------------------------------- */ /** * A simple shorthand to `die()`, depending on the admin context. * * @since 1.0 * @since 1.2.4 Added `$data` parameter. * * @param (string|object) $data A message to display or a WP_Error object. */ function secupress_admin_die( $data = null ) { if ( wp_doing_ajax() ) { wp_send_json_error( $data ); } if ( ! $data ) { wp_nonce_ays( '' ); } if ( is_wp_error( $data ) ) { $result = array(); foreach ( $data->errors as $messages ) { foreach ( $messages as $message ) { $result[] = $message; } } $data = implode( '</p><p>', $result ); if ( wp_get_referer() ) { $data .= '</p><p>'; $data .= sprintf( '<a href="%s">%s</a>', esc_url( wp_get_referer() ), __( 'Please try again.', 'secupress' ) ); } } wp_die( $data, __( 'WordPress Failure Notice', 'secupress' ), 403 ); } /** * A simple shorthand to send a json response, die, or redirect to one of our settings pages, depending on the admin context. * * @since 1.0 * * @param (array) $response A scan/fix result or false. * @param (string) $redirect One of our pages slug. Can include an URL identifier (#azerty). If omitted, the referrer is used. */ function secupress_admin_send_response_or_redirect( $response = false, $redirect = false ) { if ( ! $response ) { secupress_admin_die( 'Missing $response in ' . __FUNCTION__ ); // Do not translate. } if ( wp_doing_ajax() ) { wp_send_json_success( $response ); } if ( $redirect ) { $redirect = explode( '#', $redirect ); $redirect = secupress_admin_url( $redirect[0] ) . ( ! empty( $redirect[1] ) ? '#' . $redirect[1] : '' ); } else { $redirect = wp_get_referer(); } wp_redirect( esc_url_raw( $redirect ) ); die(); } /** * A simple shorthand to send a json response with message, die, or redirect with a message, depending on the admin context. * * @since 1.0 * * @param (array) $args An array of arguments like: * (string) $message The message to return. * (string|bool) $redirect_to The URL to redirect to: false for the referer, or a complete URL, or the slug of one of our settings pages. * (string) $code An error code used by `secupress_add_settings_error()`. * (string) $type `success` (default) or `error`. Will decide to send a success or an error message. **/ function secupress_admin_send_message_die( $args ) { $args = array_merge( array( 'message' => '', 'redirect_to' => false, 'code' => '', 'type' => 'success', ), $args ); $is_ajax = wp_doing_ajax(); if ( ! $args['message'] && ! $is_ajax ) { secupress_admin_die(); } if ( $is_ajax ) { if ( 'success' === $args['type'] ) { unset( $args['redirect_to'], $args['type'] ); wp_send_json_success( $args ); } unset( $args['redirect_to'], $args['type'] ); wp_send_json_error( $args ); } if ( ! $args['redirect_to'] ) { $args['redirect_to'] = wp_get_referer(); } elseif ( 0 !== strpos( $args['redirect_to'], 'http' ) ) { $args['redirect_to'] = secupress_admin_url( $args['redirect_to'] ); } $args['type'] = 'success' === $args['type'] ? 'updated' : 'error'; secupress_add_settings_error( 'general', $args['code'], $args['message'], $args['type'] ); set_transient( 'settings_errors', secupress_get_settings_errors(), 30 ); $goback = add_query_arg( 'settings-updated', 'true', $args['redirect_to'] ); wp_redirect( esc_url_raw( $goback ) ); die(); } /** * A shorthand to test if the current user can perform SecuPress operations. Die otherwise. * * @since 1.0 * * @param (bool) $force_mono Set to true to force the use of the capability/role for monosite. */ function secupress_check_user_capability( $force_mono = false ) { if ( ! current_user_can( secupress_get_capability( $force_mono ) ) ) { secupress_admin_die(); } } /** * A `check_admin_referer()` that also works for ajax. * * @since 1.0 * * @param (int|string) $action Action nonce. * @param (string) $query_arg Optional. Key to check for nonce in `$_REQUEST` (since 2.5). * Default '_wpnonce'. * * @return (false|int) No ajax: * False if the nonce is invalid, 1 if the nonce is valid and generated between 0-12 hours ago, 2 if the nonce is valid and generated between 12-24 hours ago. * Ajax: * Send a JSON response back to an Ajax request, indicating failure. */ function secupress_check_admin_referer( $action = -1, $query_arg = '_wpnonce' ) { if ( wp_doing_ajax() ) { if ( false === check_ajax_referer( $action, $query_arg, false ) ) { wp_send_json_error(); } } else { return check_admin_referer( $action, $query_arg ); } } free/admin/functions/scan-fix.php 0000644 00000016454 15174670627 0013037 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** --------------------------------------------------------------------------------------------- */ /** SCAN / FIX ================================================================================== */ /** --------------------------------------------------------------------------------------------- */ /** * Get the result of a scan. * * @since 1.0 * * @param (string) $test_name The suffix of the class name. Format example: Admin_User (not admin-user). * @param (bool) $format_response Change the output format. * @param (bool) $for_current_site If multisite, tell to perform the scan for the current site, not network-wide. * It has no effect on non multisite installations. * * @return (array|bool) The scan result or false on failure. */ function secupress_scanit( $test_name, $format_response = false, $for_current_site = false ) { $response = false; $formated_response = 'error'; if ( ! $test_name ) { return false; } if ( ! file_exists( secupress_class_path( 'scan', $test_name ) ) ) { $test_name = str_replace( '_', ' ', $test_name ); $test_name = ucwords( $test_name ); $test_name = str_replace( ' ', '_', $test_name ); } if ( ! file_exists( secupress_class_path( 'scan', $test_name ) ) ) { return false; } secupress_require_class( 'scan' ); secupress_require_class( 'scan', $test_name ); $classname = 'SecuPress_Scan_' . $test_name; if ( class_exists( $classname ) ) { ob_start(); secupress_time_limit( 0 ); $response = $classname::get_instance()->for_current_site( $for_current_site )->scan(); /** * $response is an array that MUST contain "status" and MUST contain "msgs". */ // If the scan is good, remove fix result. if ( isset( $response['status'] ) && 'good' === $response['status'] ) { SecuPress_Scanner_Results::delete_fix_result( $test_name ); } ob_end_clean(); } if ( $response ) { $formated_response = array( 'status' => secupress_status( $response['status'] ), 'class' => sanitize_key( $response['status'] ), 'message' => isset( $response['msgs'] ) ? secupress_format_message( $response['msgs'], $test_name ) : '', 'fix_msg' => isset( $response['fix_msg'] ) ? secupress_format_message( $response['fix_msg'], $test_name ) : '', ); } /** * Perform action on scanner on each item * For info: Check DOING_AJAX, if true, this is out global scanner where all is triggered, if not, this is a one by one * * @since 2.0 * * @param (string) $test_name * @param (array) $formated_response */ do_action( 'secupress.scanit.response', $test_name, $formated_response ); if ( $format_response ) { return $format_response; } return $response; } /** * Scan a particular test asynchronously with a delay * * @since 2.0 * @author Julio Potier * * @param (string) $test_name The suffix of the class name. Format example: Admin_User (not admin-user). * @param (int) $delay A delay in seconds if needed */ function secupress_scanit_async( $test_name, $delay = 0 ) { $http_args = [ 'timeout' => 0.01, 'blocking' => false, 'cookies' => $_COOKIE, 'sslverify' => apply_filters( 'https_local_ssl_verify', false ), ]; $is_subsite = (int) ( is_multisite() && ! is_network_admin() ); $site_id = $is_subsite ? get_current_blog_id() : ''; $nonce_action = 'secupress_scanner_' . $test_name . ( $is_subsite ? '-' . $site_id : '' ); $delay = max( min( 5, (int) $delay ), 0 ); $delay = $delay ? '&delay=' . $delay : ''; $scanner_url = admin_url( 'admin-ajax.php?action=secupress_scanner' . $delay . '&test=' . $test_name . ( $is_subsite ? '&for-current-site=1&site=' . $site_id : '' ) ); $scan_nonce_url = add_query_arg( '_wpnonce', wp_create_nonce( $nonce_action ), $scanner_url ); wp_remote_get( $scan_nonce_url, $http_args ); } /** * Get the result of a fix. * * @since 1.0 * * @param (string) $test_name The suffix of the class name. Format example: Admin_User (not admin-user). * @param (bool) $format_response Change the output format. * @param (bool) $for_current_site If multisite, tell to perform the fix for the current site, not network-wide. * It has no effect on non multisite installations. * * @return (array|bool) The scan result or false on failure. */ function secupress_fixit( $test_name, $format_response = false, $for_current_site = false ) { $response = false; if ( ! $test_name || ! file_exists( secupress_class_path( 'scan', $test_name ) ) ) { return false; } secupress_require_class( 'scan' ); secupress_require_class( 'scan', $test_name ); $classname = 'SecuPress_Scan_' . $test_name; if ( class_exists( $classname ) ) { ob_start(); secupress_time_limit( 0 ); $response = $classname::get_instance()->for_current_site( $for_current_site )->fix(); /** * $response is an array that MUST contain "status" and MUST contain "msgs". */ ob_end_clean(); } if ( $response && $format_response ) { $response = array_merge( $response, array( 'class' => sanitize_key( $response['status'] ), 'status' => secupress_status( $response['status'] ), 'message' => isset( $response['msgs'] ) ? secupress_format_message( $response['msgs'], $test_name ) : '', ) ); unset( $response['msgs'] ); } return $response; } /** * Get the result of a manual fix. * * @since 1.0 * * @param (string) $test_name The suffix of the class name. * @param (bool) $format_response Change the output format. * @param (bool) $for_current_site If multisite, tell to perform the manual fix for the current site, not network-wide. * It has no effect on non multisite installations. * * @return (array|bool) The scan result or false on failure. */ function secupress_manual_fixit( $test_name, $format_response = false, $for_current_site = false ) { $response = false; if ( ! $test_name || ! file_exists( secupress_class_path( 'scan', $test_name ) ) ) { return false; } secupress_require_class( 'scan' ); secupress_require_class( 'scan', $test_name ); $classname = 'SecuPress_Scan_' . $test_name; if ( class_exists( $classname ) ) { ob_start(); secupress_time_limit( 0 ); $response = $classname::get_instance()->for_current_site( $for_current_site )->manual_fix(); /** * $response is an array that MUST contain "status" and MUST contain "msgs". */ ob_end_clean(); } if ( $response && $format_response ) { $response = array_merge( $response, array( 'class' => sanitize_key( $response['status'] ), 'status' => secupress_status( $response['status'] ), 'message' => isset( $response['msgs'] ) ? secupress_format_message( $response['msgs'], $test_name ) : '', ) ); unset( $response['msgs'] ); } return $response; } add_action( 'admin_footer', 'secupress_pre_check_php_version' ); /** * Runs a pre check to see if we need to launch a PHPVersion Scanner silently * * @since 2.0 * @author Julio Potier * * @see SecuPress_Scan_PhpVersion **/ function secupress_pre_check_php_version() { $info = get_option( 'secupress_scan_phpversion' ); if ( ! isset( $info['status'] ) || 'bad' !== $info['status'] ) { return; } $versions = secupress_get_php_versions(); if ( version_compare( $versions['current'], $versions['mini'] ) >= 0 ) { secupress_scanit( 'PhpVersion' ); } } free/admin/functions/notices.php 0000644 00000007005 15174670627 0012763 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Add a notice with the SecuPress_Admin_Notices class. * * @since 1.0 * * @param (string) $message The message to display in the notice. * @param (string) $error_code Like WordPress notices: "error" or "updated". Default is "updated". * @param (string|bool) $notice_id A unique identifier to tell if the notice is dismissible. * false: the notice is not dismissible. * string: the notice is dismissible and send an ajax call to store the "dismissed" state into a user meta to prevent it to popup again. * empty string: meant for a one-shot use. The notice is dismissible but the "dismissed" state is not stored, it will popup again. This is the exact same behavior than the WordPress dismissible notices. * @param (null|string) $capa A WordPress capability or role. "null" = secupress_get_capability() */ function secupress_add_notice( $message, $error_code = 'updated', $notice_id = '', $capa = null ) { SecuPress_Admin_Notices::get_instance()->add( $message, $error_code, $notice_id, $capa ); } /** * Add a temporary notice with the SecuPress_Admin_Notices class. * * @since 1.0 * @since 1.3 Added $notice_id parameter. * * @param (string) $message The message to display in the notice. * @param (string) $error_code Like WordPress notices: "error" or "updated". Default is "updated". * @param (string|bool) $notice_id A unique identifier to tell if the notice is dismissible. * false: the notice is not dismissible. * string: the notice is dismissible and send an ajax call to store the "dismissed" state into a user meta to prevent it to popup again. * empty string: meant for a one-shot use. The notice is dismissible but the "dismissed" state is not stored, it will popup again. This is the exact same behavior than the WordPress dismissible notices. * @param (null|string) $capa A WordPress capability or role. "null" = secupress_get_capability() */ function secupress_add_transient_notice( $message, $error_code = 'updated', $notice_id = '', $capa = null ) { SecuPress_Admin_Notices::get_instance()->add_temporary( $message, $error_code, $notice_id, $capa ); } /** * Dismiss a notice added with the SecuPress_Admin_Notices class. * * @since 1.0 * * @param (string) $notice_id The notice identifier. * @param (int) $user_id User ID. If not set, fallback to the current user ID. * * @return (bool) true on success. */ function secupress_dismiss_notice( $notice_id, $user_id = 0 ) { return SecuPress_Admin_Notices::dismiss( $notice_id, $user_id ); } /** * "Undismiss" a notice added with the SecuPress_Admin_Notices class. * * @since 1.0 * * @param (string) $notice_id The notice identifier. * @param (int) $user_id User ID. If not set, fallback to the current user ID. * * @return (bool) true on success. */ function secupress_reinit_notice( $notice_id, $user_id = 0 ) { return SecuPress_Admin_Notices::reinit( $notice_id, $user_id ); } /** * Test if a notice added with the SecuPress_Admin_Notices class is dismissed. * * @since 1.0 * * @param (string) $notice_id The notice identifier. * * @return (bool|null) true if dismissed, false if not, null if the notice is not dismissible. */ function secupress_notice_is_dismissed( $notice_id ) { return SecuPress_Admin_Notices::is_dismissed( $notice_id ); } free/admin/functions/options.php 0000644 00000000076 15174670627 0013013 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); free/admin/functions/settings.php 0000644 00000041201 15174670627 0013153 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Register the correct setting with the correct callback for the module. * * @param (string) $module A module. Used to build the option group and maybe the option name. * @param (string) $option_name An option name. * * @since 1.0 */ function secupress_register_setting( $module, $option_name = false ) { $option_group = "secupress_{$module}_settings"; $option_name = $option_name ? $option_name : "secupress_{$module}_settings"; $sanitize_module = str_replace( '-', '_', $module ); $sanitize_callback = "secupress_pro_{$sanitize_module}_settings_callback"; if ( ! secupress_is_pro() || ! function_exists( $sanitize_callback ) ) { $sanitize_callback = "secupress_{$sanitize_module}_settings_callback"; } if ( ! is_multisite() ) { if ( is_admin() ) { // Filter the capability required when using the Settings API. add_filter( "option_page_capability_$option_group", 'secupress_setting_capability_filter' ); } // Register the setting. register_setting( $option_group, $option_name, $sanitize_callback ); return; } $whitelist = secupress_cache_data( 'new_whitelist_network_options' ); $whitelist = is_array( $whitelist ) ? $whitelist : []; $whitelist[ $option_group ] = isset( $whitelist[ $option_group ] ) ? $whitelist[ $option_group ] : array(); $whitelist[ $option_group ][] = $option_name; secupress_cache_data( 'new_whitelist_network_options', $whitelist ); add_filter( "sanitize_option_{$option_name}", $sanitize_callback ); } /** * Used to filter the capability required when using the Settings API. * * @since 1.0 * @author Grégory Viguier */ function secupress_setting_capability_filter() { return secupress_get_capability(); } /** * Print the scanner UI. * * @since 1.0 * @author Julio Potier * * @param (array) $scanner_results An array of scanner results. */ function secupress_print_scanner_ui( $scanner_results = [] ) { global $wpdb, $running, $scan_ko, $last_scan; $scanners = secupress_get_malware_scanners(); $last_scan = secupress_get_malware_scan_last_time(); $has_warning = false; if ( secupress_is_pro() && function_exists( 'secupress_file_monitoring_get_instance' ) ) { $running = secupress_file_monitoring_get_instance()->is_monitoring_running(); } else { $running = false; } $signature_scanners = 0; $signature_scanners_with_file = 0; foreach ( $scanners as $scanner ) { $scanner_file = isset( $scanner['file'] ) ? $scanner['file'] : null; $scanner_class = isset( $scanner['class'] ) ? $scanner['class'] : ''; $is_under_dev = false !== stripos( $scanner_class, 'unavailable' ); if ( $scanner_file && is_string( $scanner_file ) && ! $is_under_dev ) { $signature_scanners++; $filename = secupress_get_data_file_path( $scanner_file ); if ( $filename && file_exists( $filename ) ) { $signature_scanners_with_file++; } } } $all_signature_files_missing = $signature_scanners > 0 && 0 === $signature_scanners_with_file; $scan_ko = $all_signature_files_missing ? 1 : 0; $promo_classes = 'secupress-scanner-promo'; if ( $running ) { $promo_classes .= ' secupress-scanner-promo-running'; } ?> <div class="<?php echo esc_attr( $promo_classes ); ?>"> <div class="secupress-scanner-promo-header"> <img src="<?php echo SECUPRESS_ADMIN_IMAGES_URL; ?>icon-radar.png" alt="" class="secupress-scanner-promo-img"> <div> <h4><?php _e( 'Deep Malware Scanning', 'secupress' ); ?></h4> <p><?php _e( 'Scan your entire WordPress installation for malware, backdoors, and suspicious code.', 'secupress' ); ?></p> </div> </div> <p class="secupress-scanner-last-scan"> <?php if ( $last_scan ) { $date_format = get_option( 'date_format' ) . ' ' . get_option( 'time_format' ); $date_text = human_time_diff( $last_scan ); $abbr_text = date_i18n( $date_format, $last_scan ); printf( esc_html__( 'Last scan: %s ago', 'secupress' ), sprintf( '<abbr title="%s">%s</abbr>', esc_attr( $abbr_text ), secupress_tag_me( esc_html( $date_text ), 'strong' ) ) ); } else { _e( 'No scan has been run yet.', 'secupress' ); } ?> </p> <div class="secupress-scanner-features"> <?php foreach ( $scanners as $scanner ) { $scanner_file = isset( $scanner['file'] ) ? $scanner['file'] : null; $scanner_cback = isset( $scanner['cback'] ) ? $scanner['cback'] : null; $scanner_class = isset( $scanner['class'] ) ? $scanner['class'] : ''; $is_under_dev = false !== stripos( $scanner_class, 'unavailable' ); $is_core_scanner = isset( $scanner['icon'] ) && 'core' === $scanner['icon']; $file_available = true; if ( ! $is_under_dev && $scanner_file && is_string( $scanner_file ) ) { $filename = secupress_get_data_file_path( $scanner_file ); $file_available = $filename && file_exists( $filename ); } $results_key = null; if ( is_string( $scanner_file ) ) { $results_key = $scanner_file; } elseif ( $is_core_scanner ) { $results_key = 'core-integrity'; } $has_html_results = $results_key && isset( $scanner_results[ $results_key ]['html'] ) && ! empty( trim( $scanner_results[ $results_key ]['html'] ) ); $cback_ran = false; $cback_has_data = false; if ( ! $is_under_dev && $file_available && $scanner_cback && is_callable( $scanner_cback ) ) { $cback_result = call_user_func( $scanner_cback ); $cback_ran = true; $cback_has_data = ! empty( array_filter( $cback_result ) ); } if ( $all_signature_files_missing ) { $state = 'file-missing'; $status_text = secupress_is_pro() ? __( 'Unavailable, signature file missing', 'secupress' ) : __( 'Pro version required to continue', 'secupress' ); $state_class = 'state-file-missing'; } elseif ( $is_under_dev ) { $state = 'dev'; $status_text = __( 'Under development', 'secupress' ); $state_class = 'state-dev'; } elseif ( $scanner_file && ! $file_available ) { $state = 'file-missing'; $status_text = secupress_is_pro() ? __( 'Unavailable, signature file missing', 'secupress' ) : __( 'Pro version required to continue', 'secupress' ); $state_class = 'state-file-missing'; } elseif ( $cback_ran && ! $is_core_scanner ) { if ( $cback_has_data ) { if ( $results_key && isset( $scanner_results[ $results_key ]['html'] ) ) { if ( empty( trim( $scanner_results[ $results_key ]['html'] ) ) ) { $state = 'clean'; $status_text = __( 'Scanned, nothing found', 'secupress' ); $state_class = 'state-available'; } else { if ( empty( $scanner_results[ $results_key ]['count'] ) ) { $state = 'clean'; $status_text = __( 'Scanned, nothing found', 'secupress' ); $state_class = 'state-available'; } else { $state = 'warning'; $status_text = sprintf( _n( 'Scanned, %d issue found', 'Scanned, %d issues found', $scanner_results[ $results_key ]['count'], 'secupress' ), $scanner_results[ $results_key ]['count'] ); $state_class = 'state-warning'; } } } } elseif ( $last_scan ) { $state = 'clean'; $status_text = __( 'Scanned, nothing found', 'secupress' ); $state_class = 'state-available'; } else { $state = 'not-scanned'; $status_text = __( 'Not scanned yet', 'secupress' ); $state_class = 'state-available'; } } elseif ( $has_html_results ) { if ( empty( $scanner_results[ $results_key ]['count'] ) ) { $state = 'clean'; $status_text = __( 'Scanned, nothing found', 'secupress' ); $state_class = 'state-available'; } else { $state = 'warning'; $status_text = sprintf( _n( 'Scanned, %d issue found', 'Scanned, %d issues found', $scanner_results[ $results_key ]['count'], 'secupress' ), $scanner_results[ $results_key ]['count'] ); $state_class = 'state-warning'; } } elseif ( $last_scan ) { $state = 'clean'; $status_text = __( 'Scanned, nothing found', 'secupress' ); $state_class = 'state-available'; } else { $state = 'not-scanned'; $status_text = __( 'Not scanned yet', 'secupress' ); $state_class = 'state-available'; } if ( 'warning' === $state ) { $has_warning = true; } if ( $running && 'dev' !== $state && 'file-missing' !== $state ) { $state = 'scanning'; $status_text = __( 'Scanning…', 'secupress' ); $state_class = 'state-available'; } $scanner_id = $results_key ? sanitize_html_class( $results_key ) : sanitize_title( $scanner['name'] ); $show_toggle = secupress_is_pro() && ! $running && 'dev' !== $state; ?> <div class="secupress-scanner-feature <?php echo esc_attr( $state_class ); ?>"> <?php if ( $running && 'dev' !== $state && 'file-missing' !== $state ) { ?> <span class="spinner secupress-inline-spinner secupress-inline-spinner-active"></span> <?php } else { ?> <span class="secupress-icon secupress-icon-<?php echo esc_attr( $scanner['icon'] ); ?>"></span> <?php } ?> <div class="secupress-scanner-feature-content"> <div class="secupress-scanner-feature-header"> <strong><?php echo esc_html( $scanner['name'] ); ?></strong> <span class="secupress-scanner-feature-desc"><?php echo esc_html( $scanner['desc'] ); ?></span> </div> <div class="secupress-scanner-feature-status"> <span class="secupress-scanner-status-text"><?php echo esc_html( $status_text ); ?></span> <?php if ( $show_toggle ) { ?> <button type="button" class="secupress-scanner-toggle-btn" data-target="secupress-results-<?php echo $scanner_id; ?>" aria-expanded="false" title="<?php esc_attr_e( 'View details', 'secupress' ); ?>"> <span class="dashicons dashicons-arrow-down-alt2"></span> </button> <?php } ?> </div> </div> </div> <?php if ( $show_toggle ) { ?> <div id="secupress-results-<?php echo $scanner_id; ?>" class="secupress-scanner-results-content" style="display: none;"> <?php if ( $results_key && isset( $scanner_results[ $results_key ]['html'] ) ) { $content = trim( $scanner_results[ $results_key ]['html'] ); if ( ! empty( $content ) ) { echo $scanner_results[ $results_key ]['html']; } elseif ( 'warning' === $state ) { echo '<p class="secupress-scanner-info-message"><span class="dashicons dashicons-info"></span> ' . esc_html__( 'All detected links have already been added to the allowed list. No action needed.', 'secupress' ) . '</p>'; } } elseif ( 'file-missing' === $state ) { echo '<p class="secupress-scanner-info-message"><span class="dashicons dashicons-warning"></span> ' . esc_html__( 'The signature file for this scanner is missing. Wait 12 hours, or update the data, or contact our support team.', 'secupress' ) . '</p>'; } elseif ( 'clean' === $state ) { echo '<p class="secupress-scanner-info-message secupress-scanner-info-success"><span class="dashicons dashicons-yes-alt"></span> ' . esc_html__( 'Last scan completed successfully. No issues found.', 'secupress' ) . '</p>'; } elseif ( 'not-scanned' === $state ) { echo '<p class="secupress-scanner-info-message"><span class="dashicons dashicons-info"></span> ' . esc_html__( 'This scanner has not been run yet. "Run Scan" to start.', 'secupress' ) . '</p>'; } ?> </div> <?php } ?> <?php } ?> </div> <?php if ( ! secupress_is_pro() ) { ?> <div class="secupress-scanner-cta-alert free"> <div class="secupress-scanner-blocked"> <div class="secupress-scanner-progress"> <div class="secupress-scanner-fill"></div> <div class="secupress-scanner-blocker"> <span class="dashicons dashicons-lock"></span> </div> <span class="secupress-scanner-status"><?php _e( 'Get <strong>Pro version</strong> to continue', 'secupress' ); ?></span> </div> </div> <div class="secupress-scanner-cta"> <span class="secupress-button light disabled" data-cta-title="<?php echo esc_attr__( 'Malware Scanner', 'secupress' ); ?>"> <?php _e( 'Run Scan', 'secupress' ); ?> <span class="dashicons dashicons-arrow-right-alt2"></span> </span> </div> </div> <?php } else { if ( ! empty( $scan_ko ) ) { $label = __( 'Unavailable', 'secupress' ); $info = __( 'Scanners unavailable right now', 'secupress' ); $bar_class = ' disabled" disabled="disabled"'; $btn_class = ' disabled'; $turn = 'none'; $url = '#'; $icon = 'dashicons dashicons-warning'; $icon_btn = 'dashicons dashicons-warning'; } elseif ( $running ) { $label = __( 'Stop Scan', 'secupress' ); $bar_class = ' working'; $btn_class = ' secupress-button-secondary'; $turn = 'off'; $icon = ''; $icon_btn = 'dashicons dashicons-dismiss'; $url = esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=secupress_toggle_file_scan&turn=' . $turn ), 'secupress_toggle_file_scan' ) ); } else { $label = __( 'Run Scan', 'secupress' ); $info = __( 'Scanner ready', 'secupress' ); $bar_class = ''; $btn_class = ' secupress-button-primary'; $turn = 'on'; $icon = 'dashicons dashicons-yes-alt'; $icon_btn = 'dashicons dashicons-update'; $url = esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=secupress_toggle_file_scan&turn=' . $turn ), 'secupress_toggle_file_scan' ) ); } ?> <div class="secupress-scanner-cta-alert pro"> <div class="secupress-scanner-blocked"> <div class="secupress-scanner-progress <?php echo $bar_class; ?>"> <div class="secupress-scanner-fill"></div> <?php if ( ! $running && empty( $scan_ko ) ) { ?> <div class="secupress-scanner-blocker"> <span class="<?php echo $icon; ?>"></span> </div> <span class="secupress-scanner-status"><?php echo esc_html( $info ); ?></span> <?php } elseif ( ! empty( $scan_ko ) ) { ?> <div class="secupress-scanner-blocker"> <span class="dashicons dashicons-warning"></span> </div> <span class="secupress-scanner-status"><?php _e( 'Scanners unavailable right now', 'secupress' ); ?></span> <?php } ?> </div> </div> <div class="secupress-scanner-cta"> <a data-original-i18n="<?php esc_attr_e( 'Run Scan', 'secupress' ); ?>" data-loading-i18n="<?php esc_attr_e( 'Loading…', 'secupress' ); ?>" id="toggle_file_scanner" href="<?php echo $url; ?>" class="secupress-button <?php echo sanitize_html_class( $btn_class ); ?>"> <?php echo esc_html( $label ); ?> <span class="secupress-icon <?php echo $icon_btn; ?>"></span> </a> </div> <?php $label_scan = ''; $full_filetree = secupress_get_site_transient( SECUPRESS_FULL_FILETREE ); $label_scan = 'off' === $turn ? '<p><code>' . __( 'Cleaning', 'secupress' ) . '…</code></p>' : ''; if ( $running && is_array( $full_filetree ) ) { $label_scan = __( 'Scanning: %s', 'secupress' ); if ( ! isset( $full_filetree[1] ) && ABSPATH === $full_filetree[0] ) { $label_scan = sprintf( $label_scan, '<code>' . esc_html( sprintf( __( 'Database: %s', 'secupress' ), $wpdb->posts . ', ' . $wpdb->options . '…' ) ) . '</code>' ); } else { $label_scan = sprintf( $label_scan, '<code>' . esc_html( __( 'Loading paths', 'secupress' ) ) . '…</code>' ); } $label_scan .= ' <span class="secupress-icon secupress-icon-loader"></span>'; } if ( $running ) { ?> <p class="secupress-scanner-info-wrapper"> <span id="secupress-scanner-info" data-nonce="<?php echo wp_create_nonce( 'secupress_malwareScanStatus' ); ?>"> <?php echo $label_scan; ?> </span> </p> <?php } ?> </div> <?php } if ( $has_warning && ! $running ) { ?> <div class="secupress-help-banner"> <div class="secupress-help-banner-content"> <div class="secupress-help-banner-icon"> <span class="dashicons dashicons-sos"></span> </div> <div class="secupress-help-banner-text"> <p class="secupress-help-banner-title"><?php _e( 'What to do now?', 'secupress' ); ?></p> <p><?php _e( 'Check each file content using FTP and each post content to determine if it has to be cleaned, deleted or is a false positive.', 'secupress' ); ?></p> <p class="secupress-help-banner-highlight"><?php _e( 'Need help cleaning your hacked website? Our security experts are here for you!', 'secupress' ); ?></p> </div> <div class="secupress-help-banner-cta"> <a class="secupress-button secupress-button-tertiary" href="<?php echo esc_url( secupress_admin_url( 'get-pro' ) ); ?>#services"> <span class="dashicons dashicons-businessman"></span> <?php _e( 'Ask an Expert', 'secupress' ); ?> </a> </div> </div> </div> <?php } ?> </div> <?php } free/admin/modal.php 0000644 00000015350 15174670627 0010405 0 ustar 00 <?php /** * Deactivation form template. * * @since 2.0 * */ defined( 'ABSPATH' ) or die( 'Something went wrong.' ); ?> <div class="secupress-Modal" id="secupress-Modal" data-nonce="<?php echo wp_create_nonce( 'deactivation-info' ); ?>"> <div class="secupress-Modal-header"> <div> <button class="secupress-Modal-return"><span class="dashicons dashicons-arrow-left-alt"></span></button> <h2><i class="secupress-icon-secupress" aria-hidden="true"></i> <?php _e( 'SecuPress Feedback', 'secupress' ); ?></h2> </div> <button class="secupress-Modal-close"><span class="dashicons dashicons-no-alt"></span></button> </div> <div class="secupress-Modal-content"> <div class="secupress-Modal-question secupress-isOpen"> <h3><?php _e( 'SecuPress is currently protecting this website.<br>Are you sure to deactivate it? Why?', 'secupress' ); ?></h3> <ul> <li> <input type="radio" name="reason" id="sp-reason-temporary" value="Temporary Deactivation"> <label for="sp-reason-temporary"><?php _e( 'It is a temporary deactivation. I am just debugging an issue', 'secupress' ); ?></label> </li> <li> <input type="radio" name="reason" id="sp-reason-broke" value="Broken Website"> <label for="sp-reason-broke"><?php _e( 'The plugin broke my website or some functionality', 'secupress' ); ?></label> </li> <li> <input type="radio" name="reason" id="sp-reason-score" value="Score"> <label for="sp-reason-score"><?php _e( 'My Security Grade is not A and I cannot reach it', 'secupress' ); ?></label> </li> <li> <input type="radio" name="reason" id="sp-reason-hacked" value="Hacked"> <label for="sp-reason-loading"><?php _e( 'Even with SecuPress, my website was hacked', 'secupress' ); ?></label> </li> <li> <input type="radio" name="reason" id="sp-reason-complicated" value="Complicated"> <label for="sp-reason-complicated"><?php _e( 'The plugin is too complicated to understand', 'secupress' ); ?></label> </li> <li> <input type="radio" name="reason" id="sp-reason-competitor" value="Competitor"> <label for="sp-reason-competitor"><?php _e( 'I will use another security plugin', 'secupress' ); ?></label> <div class="secupress-Modal-fieldHidden"> <input type="text" name="reason-competname" id="sp-reason-competitor-details" value="" placeholder="<?php esc_attr_e( 'What is the name of this plugin?', 'secupress' ); ?>"> </div> </li> <li> <input type="radio" name="reason" id="sp-reason-other" value="Other"> <label for="sp-reason-other"><?php _e( 'Other reason', 'secupress' ); ?></label> <div class="secupress-Modal-fieldHidden"> <textarea name="reason-other-details" id="sp-reason-other-details" placeholder="<?php esc_attr_e( 'Let us know why you are deactivating SecuPress so we can improve the plugin', 'secupress' ); ?>"></textarea> </div> </li> </ul> <p class="secupress-check-help"> <em><a target="_blank" href="<?php _e( 'https://docs.secupress.me/article/175-what-informations-are-sent-from-your-site-to-secupress-me', 'secupress' ); ?>"><?php _e( 'Check what we send.', 'secupress' ); ?></a></em> <span class="dashicons dashicons-external"></span> </p> <input id="secupress-reason" type="hidden" value=""> <input id="secupress-details" type="hidden" value=""> </div> </div> <div id="sp-reason-broke-panel" class="secupress-Modal-hidden"> <p><?php _e( 'We’re sorry to hear that. We can still help you to recover the website, never hesitate to reach us!', 'secupress' ); ?></p> <div class="text-center"> <a href="<?php echo trailingslashit( set_url_scheme( SECUPRESS_WEB_MAIN, 'https' ) ) . _x( 'support', 'link to website (Only FR or EN!)', 'secupress' ); ?>" class="secupress-button secupress-button-tertiary shadow"><?php _e( 'Ask for Support Now', 'secupress' ); ?></a> </div> <p> </p> </div> <div id="sp-reason-score-panel" class="secupress-Modal-hidden"> <p><?php _e( 'SecuPress makes your site more secure. The A grade is not necessary to get a secure website. It’s like having 10 locks on a door but only lock 9 of them, is your house stil safe? Yes.', 'secupress' ); ?></p> <p><?php _e( 'Did you know that we can propose you a Pro Configuration Service?', 'secupress' ); ?></p> <div class="text-center"> <a href="<?php echo trailingslashit( set_url_scheme( SECUPRESS_WEB_MAIN, 'https' ) ) . _x( 'pricing', 'link to website (Only FR or EN!)', 'secupress' ); ?>" class="secupress-button secupress-button-tertiary shadow"><?php _e( 'Visit Pricing Now', 'secupress' ); ?></a> </div> <p> </p> </div> <div id="sp-reason-hacked-panel" class="secupress-Modal-hidden"> <p><?php _e( 'We’re sorry to hear that because our goal is to prevent this. Still, SecuPress was protecting you, without it, maybe your website would have been hacked earlier! Also sometimes a flaw is exploited in a clean way so no script nor plugin could detect that and block it.', 'secupress' ); ?> <p><?php _e( 'Did you know that we can propose you a Post Hack Service?', 'secupress' ); ?></p> <div class="text-center"> <a href="<?php echo trailingslashit( set_url_scheme( SECUPRESS_WEB_MAIN, 'https' ) ) . _x( 'pricing', 'link to website (Only FR or EN!)', 'secupress' ); ?>" class="secupress-button secupress-button-tertiary shadow"><?php _e( 'Visit Pricing Now', 'secupress' ); ?></a> </div> <p> </p> </div> <div id="sp-reason-complicated-panel" class="secupress-Modal-hidden"> <p><?php _e( 'We are sorry to hear you are finding it difficult to use SecuPress.', 'secupress' ); ?></p> <p><?php _e( 'We tried to be the less speak-tech as possible but sometimes we have to. If you are talking about the email alerts or security logs, we understand that it could be unsettling at first.', 'secupress' ); ?></p> <p><?php _e( 'Did you know that we can propose you a Security Maintenance? So we are the one in charge of that for you, all year long.', 'secupress' ); ?></p> <div class="text-center"> <a href="<?php echo trailingslashit( set_url_scheme( SECUPRESS_WEB_MAIN, 'https' ) ) . _x( 'pricing', 'link to website (Only FR or EN!)', 'secupress' ); ?>" class="secupress-button secupress-button-tertiary shadow"><?php _e( 'Visit Pricing Now', 'secupress' ); ?></a> </div> <p> </p> </div> <div class="secupress-Modal-footer"> <div> <button class="secupress-Modal-cancel"><?php _ex( 'Cancel', 'verb', 'secupress' ); ?></button> </div> <a href="#" class="secupress-button-send secupress-button secupress-button-primary shadow" id="secupress-send-reason"><?php _ex( 'Send & Deactivate', 'verb', 'secupress' ); ?></a> <a href="#" class="secupress-button-skip secupress-button secupress-button-secondary light shadow"><?php _ex( 'Skip & Deactivate', 'verb', 'secupress' ); ?></a> </div> </div> <div class="secupress-Modal-overlay"></div> free/admin/ajax-post-callbacks.php 0000644 00000114010 15174670627 0013125 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** --------------------------------------------------------------------------------------------- */ /** ADMIN POST / AJAX CALLBACKS ================================================================= */ /** --------------------------------------------------------------------------------------------- */ /** * Scan callback. */ add_action( 'admin_post_secupress_scanner', 'secupress_scanit_ajax_post_cb' ); add_action( 'wp_ajax_secupress_scanner', 'secupress_scanit_ajax_post_cb' ); /** * Used to scan a test in scanner page. * Prints a JSON or redirects the user. * * @since 1.0 */ function secupress_scanit_ajax_post_cb() { if ( empty( $_GET['test'] ) ) { secupress_admin_die(); } $test_name = $_GET['test']; // WPCS: XSS ok. $for_current_site = ! empty( $_GET['for-current-site'] ); $site_id = $for_current_site && ! empty( $_GET['site'] ) ? '-' . absint( $_GET['site'] ) : ''; secupress_check_user_capability( $for_current_site ); secupress_check_admin_referer( 'secupress_scanner_' . $test_name . $site_id ); if ( isset( $_GET['delay'] ) ) { $delay = max( min( 5, (int) $_GET['delay'] ), 0 ); sleep( $delay ); } $response = secupress_scanit( $test_name, wp_doing_ajax(), $for_current_site ); secupress_admin_send_response_or_redirect( $response ); } /** * Fix callback. */ add_action( 'admin_post_secupress_fixit', 'secupress_fixit_ajax_post_cb' ); add_action( 'wp_ajax_secupress_fixit', 'secupress_fixit_ajax_post_cb' ); /** * Used to automatically fix a test in scanner page. * Prints a JSON or redirects the user. * * @since 1.0 */ function secupress_fixit_ajax_post_cb() { if ( empty( $_GET['test'] ) ) { secupress_admin_die(); } $test_name = esc_attr( $_GET['test'] ); $for_current_site = ! empty( $_GET['for-current-site'] ); $site_id = $for_current_site && ! empty( $_GET['site'] ) ? '-' . absint( $_GET['site'] ) : ''; secupress_check_user_capability( $for_current_site ); secupress_check_admin_referer( 'secupress_fixit_' . $test_name . $site_id ); $doing_ajax = wp_doing_ajax(); $response = secupress_fixit( $test_name, $doing_ajax, $for_current_site ); // If not ajax, perform a scan. if ( ! $doing_ajax ) { secupress_scanit( $test_name, false, $for_current_site ); } secupress_admin_send_response_or_redirect( $response ); } /** * Manual fix callback. */ add_action( 'admin_post_secupress_manual_fixit', 'secupress_manual_fixit_ajax_post_cb' ); add_action( 'wp_ajax_secupress_manual_fixit', 'secupress_manual_fixit_ajax_post_cb' ); /** * Used to manually fix a test in scanner page. * Prints a JSON or redirects the user. * * @since 1.0 */ function secupress_manual_fixit_ajax_post_cb() { if ( empty( $_POST['test'] ) ) { // WPCS: CSRF ok. secupress_admin_die(); } $test_name = esc_attr( $_POST['test'] ); // WPCS: CSRF ok. $for_current_site = ! empty( $_POST['for-current-site'] ); // WPCS: CSRF ok. $site_id = $for_current_site && ! empty( $_POST['site'] ) ? '-' . absint( $_POST['site'] ) : ''; // WPCS: CSRF ok. secupress_check_user_capability( $for_current_site ); secupress_check_admin_referer( 'secupress_manual_fixit_' . $test_name . $site_id ); $doing_ajax = wp_doing_ajax(); $response = secupress_manual_fixit( $test_name, $doing_ajax, $for_current_site ); // If not ajax, perform a scan. if ( ! $doing_ajax ) { secupress_scanit( $test_name, false, $for_current_site ); } secupress_admin_send_response_or_redirect( $response ); } /** * Get all translated strings for the scans UI. */ add_action( 'wp_ajax_secupress-get-scan-counters', 'secupress_get_scan_counters_ajax_cb' ); /** * Used to get all the needed translated strings and counters needed after each single scan/one-click scan. * * @since 1.0 */ function secupress_get_scan_counters_ajax_cb() { secupress_check_user_capability(); secupress_check_admin_referer( 'secupress-get-scan-counters' ); $counts = secupress_get_scanner_counts(); foreach ( array( 'notscannedyet', 'hasaction', 'good', 'warning', 'bad' ) as $status ) { $counts[ $status . '-text' ] = sprintf( _n( '%d issue', '%d issues', $counts[ $status ], 'secupress' ), $counts[ $status ] ); } wp_send_json_success( $counts ); } /** * Date of the last One-click scan. */ add_action( 'wp_ajax_secupress-update-oneclick-scan-date', 'secupress_update_oneclick_scan_date_ajax_cb' ); /** * Used to update the date of the last One-click scan. * Prints a JSON containing the HTML of the new line to insert in the page. * * @since 1.0 */ function secupress_update_oneclick_scan_date_ajax_cb() { secupress_check_user_capability(); secupress_check_admin_referer( 'secupress-update-oneclick-scan-date' ); $items = array_filter( (array) get_site_option( SECUPRESS_SCAN_TIMES ) ); // Build the new item. $counts = secupress_get_scanner_counts(); $item = array( 'percent' => round( $counts['good'] * 100 / $counts['total'] ), 'grade' => $counts['grade'], 'time' => time(), ); // Get the previous percentage. if ( $items ) { $last_percent = end( $items ); $last_percent = $last_percent['percent']; } else { $last_percent = -1; } // Add the new item and limit to 5 results. array_push( $items, $item ); $items = array_slice( $items, -5, 5 ); update_site_option( SECUPRESS_SCAN_TIMES, $items ); // Send the formated new item. wp_send_json_success( secupress_formate_latest_scans_list_item( $item, $last_percent ) ); } // WHITELIST IPs add_action( 'admin_post_secupress-whitelist-ip', 'secupress_whitelist_ip_ajax_post_cb' ); add_action( 'wp_ajax_secupress-whitelist-ip', 'secupress_whitelist_ip_ajax_post_cb' ); /** * Whitelist an IP address. * * @since 1.4.9 */ function secupress_whitelist_ip_ajax_post_cb() { // Make all security tests. secupress_check_admin_referer( 'secupress-whitelist-ip' ); secupress_check_user_capability(); if ( empty( $_REQUEST['ip'] ) ) { secupress_admin_send_message_die( array( 'message' => __( 'IP address not provided.', 'secupress' ), 'code' => 'no_ip', 'type' => 'error', ) ); } // Test the IP. $ip = trim( urldecode( $_REQUEST['ip'] ) ); $original_ip = $ip; $is_list = false; $sep = "\n"; if ( strpos( $ip, ', ' ) > 0 ) { $sep = ', '; } elseif ( strpos( $ip, ',' ) > 0 ) { $sep = ','; } elseif ( strpos( $ip, ';' ) > 0 ) { $sep = ';'; } elseif ( strpos( $ip, ' ' ) > 0 ) { $sep = ' '; } if ( strpos( $ip, $sep ) > 0 ) { $is_list = true; $ip = explode( $sep, $ip ); $count_1 = count( $ip ); $ip = array_filter( $ip , function( $_ip ) { return secupress_ip_is_valid( $_ip, true ); } ); $count_2 = count( $ip ); } if ( ! $is_list && ! secupress_ip_is_valid( $ip, true ) ) { secupress_admin_send_message_die( array( 'message' => __( 'This is not a valid IP address.', 'secupress' ), 'code' => 'invalid_ip', 'type' => 'error', ) ); } if ( ! $is_list && ( secupress_ip_is_whitelisted( $ip ) ) ) { secupress_admin_send_message_die( [ 'message' => __( 'This IP address is already allowed.', 'secupress' ), 'code' => 'already_whitelisted', 'type' => 'error', ] ); } if ( $is_list && 0 === $count_2 ) { secupress_admin_send_message_die( [ 'message' => __( 'The list does not contains any valid IP address.', 'secupress' ), 'code' => 'invalid_ip', 'type' => 'error', ] ); } // Add the IP to the option. $white_ips = get_site_option( SECUPRESS_WHITE_IP ); $white_ips = is_array( $white_ips ) ? $white_ips : []; if ( ! is_array( $ip ) ) { $ip = [ $ip ]; } $ip = array_flip( $ip ); $white_ips = array_merge( $white_ips, $ip ); update_site_option( SECUPRESS_WHITE_IP, $white_ips ); /* This hook is documented in /inc/functions/admin.php */ do_action( 'secupress.ip_allowed', $ip, $white_ips ); $referer_arg = '&_wp_http_referer=' . urlencode( esc_url_raw( secupress_admin_url( 'modules', 'logs' ) ) ); // Send a response. if ( ! $is_list ) { secupress_admin_send_message_die( [ 'message' => sprintf( __( 'The IP address %s has been allowed.', 'secupress' ), '<code>' . esc_html( $original_ip ) . '</code>' ), 'code' => 'ip_whitelist', 'tmplValues' => [ [ 'ip' => $original_ip, 'unwhitelist_url' => esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=secupress-unwhitelist-ip&ip=' . esc_attr( $original_ip ) . $referer_arg ), 'secupress-unwhitelist-ip_' . $original_ip ) ), ], ] ] ); } else { $tmplValues = []; foreach ( $white_ips as $_ip => $time ) { $tmplValues[] = [ 'ip' => $_ip, 'unwhitelist_url' => esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=secupress-unwhitelist-ip&ip=' . esc_attr( $_ip ) . $referer_arg ), 'secupress-unwhitelist-ip_' . $_ip ) ), ]; } if ( $count_1 === $count_2 ) { secupress_admin_send_message_die( [ 'message' => __( 'The IP address list has been allowed.', 'secupress' ), 'code' => 'ip_whitelist', 'tmplValues' => $tmplValues, ] ); } else { secupress_admin_send_message_die( [ 'message' => __( 'Some of the IP address list has been allowed.', 'secupress' ), 'code' => 'ip_whitelist', 'tmplValues' => $tmplValues, ] ); } } } add_action( 'admin_post_secupress-unwhitelist-ip', 'secupress_unwhitelist_ip_ajax_post_cb' ); add_action( 'wp_ajax_secupress-unwhitelist-ip', 'secupress_unwhitelist_ip_ajax_post_cb' ); /** * Unwhitelist an IP address. * * @since 1.4.9 */ function secupress_unwhitelist_ip_ajax_post_cb() { // Make all security tests. if ( empty( $_REQUEST['ip'] ) ) { secupress_admin_send_message_die( array( 'message' => __( 'IP address not provided.', 'secupress' ), 'code' => 'no_ip', 'type' => 'error', ) ); } secupress_check_admin_referer( 'secupress-unwhitelist-ip_' . $_REQUEST['ip'] ); secupress_check_user_capability(); $ip = trim( urldecode( $_REQUEST['ip'] ) ); // Remove the IP from the option. $white_ips = get_site_option( SECUPRESS_WHITE_IP ); $white_ips = is_array( $white_ips ) ? $white_ips : []; if ( ! isset( $white_ips[ $ip ] ) ) { secupress_admin_send_message_die( [ 'message' => sprintf( __( 'The IP address %s is not allowed.', 'secupress' ), '<code>' . esc_html( $ip ) . '</code>' ), 'code' => 'ip_not_whitelisted', ] ); } unset( $white_ips[ $ip ] ); if ( $white_ips ) { update_site_option( SECUPRESS_WHITE_IP, $white_ips ); } else { delete_site_option( SECUPRESS_WHITE_IP ); } /** * Fires once a IP is unbanned. * * @since 1.0 * * @param (string) $ip The IP unbanned. * @param (array) $white_ips The list of IPs banned (keys) and the time they were banned (values). */ do_action( 'secupress.ip_unallowed', $ip, $white_ips ); // Send a response. secupress_admin_send_message_die( array( 'message' => sprintf( __( 'The IP address %s has been remove from the list.', 'secupress' ), '<code>' . esc_html( $ip ) . '</code>' ), 'code' => 'ip_unwhitelisted', ) ); } add_action( 'admin_post_secupress-clear-whitelist-ips', 'secupress_clear_whitelist_ips_ajax_post_cb' ); add_action( 'wp_ajax_secupress-clear-whitelist-ips', 'secupress_clear_whitelist_ips_ajax_post_cb' ); /** * Unwhitelist all IP addresses. * * @since 1.4.9 */ function secupress_clear_whitelist_ips_ajax_post_cb() { // Make all security tests. secupress_check_admin_referer( 'secupress-clear-whitelist-ips' ); secupress_check_user_capability(); // Remove all IPs from the option. delete_site_option( SECUPRESS_WHITE_IP ); /** * Fires once all IPs are unbanned. * * @since 1.0 */ do_action( 'secupress.ips_cleared' ); // Send a response. secupress_admin_send_message_die( [ 'message' => __( 'All IP addresses have been removed from list.', 'secupress' ), 'code' => 'whitelisted_ips_cleared', ] ); } // BANNED IPs add_action( 'admin_post_secupress-ban-ip', 'secupress_ban_ip_ajax_post_cb' ); add_action( 'wp_ajax_secupress-ban-ip', 'secupress_ban_ip_ajax_post_cb' ); /** * Ban an IP address. * * @since 1.0 */ function secupress_ban_ip_ajax_post_cb() { // Make all security tests. secupress_check_admin_referer( 'secupress-ban-ip' ); secupress_check_user_capability(); if ( empty( $_REQUEST['ip'] ) ) { secupress_admin_send_message_die( [ 'message' => __( 'IP address not provided.', 'secupress' ), 'code' => 'no_ip', 'type' => 'error', ] ); } // Test the IP. $ip = trim( urldecode( $_REQUEST['ip'] ) ); $original_ip = $ip; $is_list = false; $sep = "\n"; $unbanned = ''; if ( strpos( $ip, ', ' ) > 0 ) { $sep = ', '; } elseif ( strpos( $ip, ',' ) > 0 ) { $sep = ','; } elseif ( strpos( $ip, ';' ) > 0 ) { $sep = ';'; } elseif ( strpos( $ip, ' ' ) > 0 ) { $sep = ' '; } if ( strpos( $ip, $sep ) > 0 ) { $is_list = true; $ip = explode( $sep, $ip ); $count_1 = count( $ip ); $ip = array_filter( $ip , function( $_ip ) { return secupress_ip_is_valid( $_ip, true ); } ); $count_2 = count( $ip ); } if ( ! $is_list && ! secupress_ip_is_valid( $ip, true ) ) { secupress_admin_send_message_die( array( 'message' => __( 'This is not a valid IP address.', 'secupress' ), 'code' => 'invalid_ip', 'type' => 'error', ) ); } // Don't ban your IP if ( secupress_get_ip() === $ip ) { secupress_admin_send_message_die( array( 'message' => __( 'You cannot ban your own IP address.', 'secupress' ), 'code' => 'own_ip', 'type' => 'error', ) ); } // No valid IP in a list if ( $is_list && 0 === $count_2 ) { secupress_admin_send_message_die( array( 'message' => __( 'The list does not contains any valid IP address.', 'secupress' ), 'code' => 'invalid_ip', 'type' => 'error', ) ); } // Already banned $ban_ips = get_site_option( SECUPRESS_BAN_IP ); $ban_ips = is_array( $ban_ips ) ? $ban_ips : []; if ( ! $is_list && isset( $ban_ips[ $ip ] ) ) { secupress_admin_send_message_die( array( 'message' => __( 'This IP is already banned.', 'secupress' ), 'code' => 'already_banned', 'type' => 'error', ) ); } // Transform the non list as a list now if ( ! is_array( $ip ) ) { $ip = [ $ip ]; } $ip = array_flip( $ip ); array_walk( $ip, function( &$_ip, $time ) { $_ip = strtotime('+10 years'); }); $ban_ips = array_merge( $ban_ips, $ip ); // Update the ips now update_site_option( SECUPRESS_BAN_IP, $ban_ips ); /* This hook is documented in /inc/functions/admin.php */ do_action( 'secupress.ban.ip_banned', $ip, $ban_ips ); $referer_arg = '&_wp_http_referer=' . urlencode( esc_url_raw( secupress_admin_url( 'modules', 'logs' ) ) ); $format = _x( 'M jS Y', 'date', 'secupress' ) . ' ' . _x( 'G:i', 'date', 'secupress' ); $_time = date_i18n( $format, strtotime('+10 years')); // Send a response. if ( ! $is_list ) { secupress_admin_send_message_die( [ 'message' => sprintf( __( 'The IP address %s has been banned.', 'secupress' ) . $unbanned, '<code>' . esc_html( $original_ip ) . '</code>' ), 'code' => 'ip_banned', 'tmplValues' => [ [ 'ip' => $original_ip, 'time' => $_time, 'unban_url' => esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=secupress-unban-ip&ip=' . esc_attr( $original_ip ) . $referer_arg ), 'secupress-unban-ip_' . $original_ip ) ), ], ] ] ); } else { $tmplValues = []; foreach ( $ban_ips as $_ip => $time ) { $tmplValues[] = [ 'ip' => $_ip, 'time' => $_time, 'unban_url' => esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=secupress-unban-ip&ip=' . esc_attr( $_ip ) . $referer_arg ), 'secupress-unban-ip_' . $_ip ) ), ]; } if ( $count_1 === $count_2 ) { secupress_admin_send_message_die( [ 'message' => __( 'The IP address list has been banned.' . $unbanned, 'secupress' ), 'code' => 'ip_banned', 'tmplValues' => $tmplValues, ] ); } else { secupress_admin_send_message_die( [ 'message' => __( 'Some of the IP address list has been banned.' . $unbanned, 'secupress' ), 'code' => 'ip_banned', 'tmplValues' => $tmplValues, ] ); } } } add_action( 'admin_post_secupress-unban-ip', 'secupress_unban_ip_ajax_post_cb' ); add_action( 'wp_ajax_secupress-unban-ip', 'secupress_unban_ip_ajax_post_cb' ); /** * Unban an IP address. * * @since 1.0 */ function secupress_unban_ip_ajax_post_cb() { // Make all security tests. if ( empty( $_REQUEST['ip'] ) ) { secupress_admin_send_message_die( array( 'message' => __( 'IP address not provided.', 'secupress' ), 'code' => 'no_ip', 'type' => 'error', ) ); } secupress_check_admin_referer( 'secupress-unban-ip_' . $_REQUEST['ip'] ); secupress_check_user_capability(); $ip = urldecode( $_REQUEST['ip'] ); // Remove the IP from the option. $ban_ips = get_site_option( SECUPRESS_BAN_IP ); $ban_ips = is_array( $ban_ips ) ? $ban_ips : array(); if ( empty( $ban_ips[ $ip ] ) ) { secupress_admin_send_message_die( array( 'message' => sprintf( __( 'The IP address %s is not banned.', 'secupress' ), '<code>' . esc_html( $ip ) . '</code>' ), 'code' => 'ip_not_banned', ) ); } unset( $ban_ips[ $ip ] ); if ( $ban_ips ) { update_site_option( SECUPRESS_BAN_IP, $ban_ips ); } else { delete_site_option( SECUPRESS_BAN_IP ); } /** * Fires once a IP is unbanned. * * @since 1.0 * * @param (string) $ip The IP unbanned. * @param (array) $ban_ips The list of IPs banned (keys) and the time they were banned (values). */ do_action( 'secupress.ban.ip_unbanned', $ip, $ban_ips ); // Send a response. secupress_admin_send_message_die( array( 'message' => sprintf( __( 'The IP address %s has been unbanned.', 'secupress' ), '<code>' . esc_html( $ip ) . '</code>' ), 'code' => 'ip_unbanned', ) ); } add_action( 'admin_post_secupress-clear-ips', 'secupress_clear_ips_ajax_post_cb' ); add_action( 'wp_ajax_secupress-clear-ips', 'secupress_clear_ips_ajax_post_cb' ); /** * Unban all IP addresses. * * @since 1.0 */ function secupress_clear_ips_ajax_post_cb() { // Make all security tests. secupress_check_admin_referer( 'secupress-clear-ips' ); secupress_check_user_capability(); // Remove all IPs from the option. delete_site_option( SECUPRESS_BAN_IP ); /** * Fires once all IPs are unbanned. * * @since 1.0 */ do_action( 'secupress.ban.ips_cleared' ); // Send a response. secupress_admin_send_message_die( array( 'message' => __( 'All IP addresses have been unbanned.', 'secupress' ), 'code' => 'banned_ips_cleared', ) ); } add_action( 'admin_post_secupress_reset_settings', 'secupress_admin_post_reset_settings_post_cb' ); /** * Reset SecuPress settings or module settings. * * @since 1.4.4 Params $module & $bypass. * @since 1.0 * * @author Julio Potier * @param (string) $module Empty by default, the module to be reset if $_GET is not defined. * @param (bool) $bypass False by default, if true, the security will not be checked, already done by the caller. */ function secupress_admin_post_reset_settings_post_cb( $module = '', $bypass = false ) { if ( empty( $_GET['module'] ) && ! $module ) { secupress_admin_die(); } $module = isset( $_GET['module'] ) ? $_GET['module'] : $module; if ( ! $bypass ) { // Make all security tests. secupress_check_admin_referer( 'secupress_reset_' . $module ); secupress_check_user_capability(); } secupress_delete_module_option( $module ); /** This action is documented in inc/admin/upgrader.php */ do_action( 'secupress.first_install', $module ); if ( ! $bypass ) { secupress_add_transient_notice( __( 'Module settings reset.', 'secupress' ), 'updated', 'module-reset' ); wp_safe_redirect( esc_url_raw( secupress_admin_url( 'modules', $module ) ) ); die(); } } add_action( 'wp_ajax_sanitize_move_login_slug', 'secupress_sanitize_move_login_slug_ajax_post_cb' ); /** * Sanitize a value for a Move Login slug. * * @since 1.2.5 * @author Grégory Viguier */ function secupress_sanitize_move_login_slug_ajax_post_cb() { // Make all security tests. secupress_check_admin_referer( 'sanitize_move_login_slug' ); secupress_check_user_capability(); if ( empty( $_GET['default'] ) || ! isset( $_GET['slug'] ) ) { wp_send_json_error(); } $default = sanitize_title( $_GET['default'] ); if ( ! $default ) { wp_send_json_error(); } if ( 'login' === $default ) { $slug = sanitize_title( $_GET['slug'], '', 'display' ); // See secupress/inc/modules/users-login/settings/move-login.php. $slug = $slug ? $slug : '##-' . strtoupper( sanitize_title( __( 'Choose your login URL', 'secupress' ), '', 'display' ) ) . '-##'; } else { $slug = sanitize_title( $_GET['slug'], $default, 'display' ); } wp_send_json_success( $slug ); } add_action( 'admin_post_nopriv_secupress_unlock_admin', 'secupress_unlock_admin_ajax_post_cb' ); /** * Send an unlock email if the provided address is from an admin * * @author Julio Potier * @since 1.3.2 **/ function secupress_unlock_admin_ajax_post_cb() { $message = __( 'If this email address exists, an email will be sent to it to unlock your account.', 'secupress' ); if ( ! isset( $_POST['_wpnonce'], $_POST['email'] ) || ! is_email( $_POST['email'] ) || ! check_ajax_referer( 'secupress-unban-ip-admin', '_wpnonce' ) ) { // WPCS: CSRF ok. secupress_die( $message, __( 'Email', 'secupress' ), array( 'force_die' => true ) ); } $_CLEAN = []; $_CLEAN['email'] = is_email( $_POST['email'] ); if ( ! $_CLEAN['email'] ) { secupress_die( $message, __( 'Email', 'secupress' ), array( 'force_die' => true ) ); } $user = get_user_by( 'email', $_CLEAN['email'] ); $capa = secupress_get_capability( true, 'unlock_administrator' ); if ( ! secupress_is_user( $user ) || ! user_can( $user, $capa ) ) { secupress_die( $message, __( 'Email', 'secupress' ), array( 'force_die' => true ) ); } $url_remember = wp_login_url(); $subject = sprintf( __( '[%s] Unlock a lost user', 'secupress' ), '###SITENAME###' ); $message = sprintf( __( 'Hello %1$s, It seems you are locked out from the website ###SITENAME###. You can now follow this link to your login page (remember it now!): %2$s Have a nice day ! Regards, All at ###SITENAME### ###SITEURL###', 'secupress' ), $user->display_name, $url_remember ); $capa = secupress_get_capability(); // Do it again to get the usual capa for managing options. if ( $capa && apply_filters( 'secupress.plugins.move_login.email.deactivation_link', true ) ) { $token = md5( secupress_generate_key() ); $url_remove = add_query_arg( [ '_wpnonce' => $token, 'user_email' => $user->user_email ], admin_url( 'admin-post.php?action=secupress_deactivate_module&module=move-login' ) ); set_transient( 'secupress_unlock_admin_key-' . $user->user_email, $token, HOUR_IN_SECONDS ); $message .= "\n" . sprintf( __( "ps: you can also deactivate the Move Login module:\n%s", 'secupress' ), $url_remove . ' ' . __( '(Valid 1 hour)', 'secupress' ) ); } /** * Filter the mail subject * @param (string) $subject * @param (WP_User) $user * @since 2.2 */ $subject = apply_filters( 'secupress.mail.unlock_administrator.subject', $subject, $user ); /** * Filter the mail message * @param (string) $message * @param (WP_User) $user * @param (string) $url_remove * @param (string) $url_remember * @since 2.2 */ $message = apply_filters( 'secupress.mail.unlock_administrator.message', $message, $user, $url_remove, $url_remember ); $sent = secupress_send_mail( $_CLEAN['email'], $subject, $message ); secupress_die( $message, __( 'Email', 'secupress' ), array( 'force_die' => true ) ); } add_action( 'admin_post_nopriv_secupress_deactivate_module', 'secupress_deactivate_module_admin_post_cb' ); /** * Can deactivate a module from a link sent by secupress_unlock_admin_ajax_post_cb() * * @since 1.3.2 * @author Julio Potier **/ function secupress_deactivate_module_admin_post_cb() { $tr_key_name = 'secupress_unlock_admin_key-' . ( isset( $_GET['user_email'] ) ? $_GET['user_email'] : '' ); if ( ! isset( $_GET['_wpnonce'], $_GET['module'], $_GET['user_email'] ) || empty( $_GET['_wpnonce'] ) || ! get_transient( $tr_key_name ) || ! hash_equals( get_transient( $tr_key_name ), $_GET['_wpnonce'] ) ) { delete_transient( $tr_key_name ); wp_die( 'Something went wrong.' ); } delete_transient( $tr_key_name ); secupress_deactivate_submodule( 'users-login', array( 'move-login' ) ); wp_redirect( wp_login_url( secupress_admin_url( 'modules', 'users-login' ) ) ); die(); } add_action( 'admin_post_secupress_reset_all_settings', 'secupress_reset_all_settings_admin_post_cb' ); /** * Will reset the settings like a fresh install * * @since 1.4.4 * @author Julio Potier **/ function secupress_reset_all_settings_admin_post_cb() { if ( ! isset( $_GET['_wpnonce'] ) ) { wp_die( 'Something went wrong.' ); } secupress_check_admin_referer( 'secupress_reset_all_settings' ); secupress_check_user_capability(); $modules = secupress_get_modules(); foreach ( $modules as $key => $module ) { if ( isset( $module['with_reset_box'] ) && false === $module['with_reset_box'] ) { continue; } secupress_admin_post_reset_settings_post_cb( $key, true ); } secupress_add_transient_notice( __( 'All modules settings reset', 'secupress' ), 'updated', 'module-reset' ); wp_safe_redirect( wp_get_referer() ); die(); } add_action( 'wp_ajax_secupress_set_scan_speed', 'secupress_set_scan_speed_admin_post_cb' ); /** * Set scanner speed * * @since 1.4.4 * @author Julio Potier **/ function secupress_set_scan_speed_admin_post_cb() { $old_value = secupress_get_option( 'scan-speed', 0 ); $allowed_values = [ 'max' => 0, 'normal' => 250, 'low' => 1000 ]; $_clean = []; $_clean['text'] = isset( $allowed_values[ $_GET['value'] ] ) ? $_GET['value'] : 'max'; $_clean['value'] = isset( $allowed_values[ $_GET['value'] ] ) ? $allowed_values[ $_GET['value'] ] : 0; if ( ! isset( $_GET['_wpnonce'], $_GET['value'] ) || ! check_ajax_referer( 'secupress-set-scan-speed', '_wpnonce', false ) ) { $allowed_values = array_flip( $allowed_values ); wp_send_json_error( [ 'val' => $old_value, 'text' => $allowed_values[ $old_value ] ] ); } /** * Filter the milliseconds between scans. * * @param (int) $value Defaults values are 0, 250 (1/4 sec), 1000 (1 sec) * @since 1.4.5 * @author Julio Potier */ $value = apply_filters( 'secupress.scanner.scan-speed', $_clean['value'] ); secupress_set_option( 'scan-speed', $value ); wp_send_json_success( [ 'val' => $_clean['value'], 'text' => $_clean['text'] ] ); } // add_action( 'wp_ajax_secupress_send_deactivation_info', 'secupress_send_deactivation_info_admin_post_cb' ); /** * Send the deactivation reason on secupress.me * * @since 2.0 * @author Julio Potier * * @return (string) json **/ function secupress_send_deactivation_info_admin_post_cb() { if ( ! isset( $_GET['nonce'], $_GET['reason'] ) || ! wp_verify_nonce( $_GET['nonce'], 'deactivation-info' ) ) { wp_send_json_error(); } set_site_transient( 'secupress-deactivation-form', 1, HOUR_IN_SECONDS ); $args = [ 'timeout' => 0.01, 'blocking' => false, 'body' => esc_html( $_GET['reason'] ) ]; wp_remote_post( SECUPRESS_WEB_MAIN . 'api/reason.php', $args ); wp_send_json_success(); } add_action( 'wp_ajax_secupress_malwareScanStatus', 'secupress_get_malwarescastatus_admin_post_cb' ); /** * Return the current scanned folders * * @since 2.0 * @author Julio Potier * * @return (string) **/ function secupress_get_malwarescastatus_admin_post_cb() { $response = []; if ( ! isset( $_GET['_wpnonce'] ) || ! check_ajax_referer( 'secupress_malwareScanStatus', '_wpnonce', false ) ) { $response['malwareScanStatus'] = false; wp_send_json_error( $response ); } $response['malwareScanStatus'] = ! secupress_file_monitoring_get_instance()->is_monitoring_running(); $response['currentItems'] = get_site_transient( SECUPRESS_FULL_FILETREE ) !== false ? array_map( function( $val ) { return str_replace( ABSPATH, '/', $val ); }, get_site_transient( SECUPRESS_FULL_FILETREE ) ) : []; wp_send_json_success( $response ); } add_action( 'admin_post_secupress-regen-keys', 'secupress_regen_hash_key_admin_post_cb' ); /** * Set a new has_key, and reset the salt keys too * * @since 2.3.17 Regen the file too * @since 2.0 * @author Julio Potier **/ function secupress_regen_hash_key_admin_post_cb() { global $current_user; if ( ! secupress_is_submodule_active( 'wordpress-core', 'wp-config-constant-saltkeys' ) ) { wp_die( 'Something went wrong.' ); } if ( ! isset( $_GET['_wpnonce'] ) || ! check_ajax_referer( 'secupress-regen-keys', '_wpnonce', false ) ) { wp_die( 'Something went wrong.' ); } // Do not use secupress_get_option() here. $options = get_site_option( SECUPRESS_SETTINGS_SLUG ); $options['hash_key'] = secupress_generate_key( 64 ); secupress_update_options( $options ); // We do not need to refresh the hashes in the file, do it only if file is missing if ( ! defined( 'SECUPRESS_SALT_KEYS_MODULE_ACTIVE' ) ) { $filesystem = secupress_get_filesystem(); $alicia_keys = $filesystem->get_contents( SECUPRESS_INC_PATH . 'data/salt-keys.phps' ); $args = [ '{{PLUGIN_NAME}}' => SECUPRESS_PLUGIN_NAME, '{{HASH1}}' => wp_generate_password( 64, true, true ), '{{HASH2}}' => wp_generate_password( 64, true, true ), ]; $alicia_keys = str_replace( array_keys( $args ), $args, $alicia_keys ); secupress_create_mu_plugin( 'salt_keys', $alicia_keys, uniqid() ); } secupress_auto_login( 'Salt_Keys', null, __( 'Security keys have been successfully regenerated.', 'secupress' ) ); } add_action( 'admin_post_secupress_accept_notification', 'secupress_accept_notification_admin_post_cb' ); /** * Validate the Slack Notification * * @since 2.0 * @author Julio Potier * **/ function secupress_accept_notification_admin_post_cb() { if ( ! isset( $_GET['_wpnonce'], $_GET['type'] ) || ! check_ajax_referer( 'secupress_accept_notification-type-' . $_GET['type'] ) ) { wp_die( 'Something went wrong.' ); } secupress_set_option( 'notification-types_' . $_GET['type'], secupress_get_module_option( 'notification-types_slack', false, 'alerts' ) ); // WPCS: XSS Ok. wp_redirect( secupress_admin_url( 'modules', 'alerts#row-notification-types_slack' ) ); die(); } add_action( 'wp_ajax_dismiss-sp-pointer', 'secupress_dismiss_pointer_admin_post_cb' ); /** * Dismiss our pointers * * @since 2.0 * @author Julio Potier * * @return (string) JSON **/ function secupress_dismiss_pointer_admin_post_cb( $_pointer = '' ) { $pointer = isset( $_POST['pointer'] ) ? sanitize_key( $_POST['pointer'] ) : $_pointer; if ( ! $_pointer && ( ! $pointer || ! check_ajax_referer( 'dismiss-pointer_' . $pointer, '_ajaxnonce', false ) ) ) { wp_send_json_error(); } $dismissed = array_filter( explode( ',', (string) get_user_meta( get_current_user_id(), 'dismissed_wp_pointers', true ) ) ); if ( ! $_pointer && in_array( $pointer, $dismissed, true ) ) { wp_send_json_error(); } $dismissed[] = $pointer; $dismissed = implode( ',', $dismissed ); update_user_meta( get_current_user_id(), 'dismissed_wp_pointers', $dismissed ); if ( ! $_pointer ) { wp_send_json_success(); } } //// add_action( 'admin_post_http_log_actions', 'secupress_http_log_actions_admin_post_cb' ); function secupress_http_log_actions_admin_post_cb() { if ( ! isset( $_POST['_wpnonce'], $_POST['log_id'], $_POST['http_log'] ) || ! check_admin_referer( 'http_log_actions' . $_POST['log_id'] ) ) { die( '0' ); } $http_logs = get_option( SECUPRESS_HTTP_LOGS ); $http_logs = is_array( $http_logs ) ? $http_logs : []; $options = isset( $_POST['http_log']['options'] ) ? $_POST['http_log']['options'] : []; unset( $_POST['http_log']['options'] ); $_CLEAN = $_POST['http_log']; foreach ( $_CLEAN as $url => $values ) { if ( empty( $values['index'] ) || '1' === $values['index'] ) { unset( $_CLEAN[ $url ] ); continue; } $parsed_url = shortcode_atts( [ 'scheme' => '', 'host' => '', 'path' => '', 'query' => '' ], wp_parse_url( $url ) ); if ( empty( $parsed_url['scheme'] ) ) { unset( $_CLEAN[ $url ] ); continue; } if ( ! empty( $parsed_url['query'] ) ) { parse_str( html_entity_decode( $parsed_url['query'] ), $get_params ); if ( ! empty( $options['ignore-param'] ) ) { $get_params = array_diff_key( $get_params, array_flip( $options['ignore-param'] ) ); } ksort( $get_params ); $path_name = $parsed_url['scheme'] . '://' . untrailingslashit( $parsed_url['host'] ) . $parsed_url['path']; $query = http_build_query( $get_params ); $query = ! empty( $query ) ? '?' . $query : ''; $temp = $_CLEAN[ $url ]; unset( $_CLEAN[ $url ] ); $url = $path_name . $query; $_CLEAN[ $url ] = $temp; } if ( isset( $options ) ) { $_CLEAN[ $url ]['options'] = $options; unset( $options ); } if ( ! isset( $http_logs[ $url ]['since'] ) ) { $_CLEAN[ $url ]['since'] = time(); } if ( ! isset( $http_logs[ $url ]['hits'] ) ) { $_CLEAN[ $url ]['hits'] = 0; } // Always reset the last call on save/update. $_CLEAN[ $url ]['last'] = 0; } $http_logs = array_merge( $http_logs, $_CLEAN ); ksort( $http_logs ); update_option( SECUPRESS_HTTP_LOGS, $http_logs, false ); wp_safe_redirect( wp_get_referer() ); die(); } add_action( 'wp_ajax_secupress_check_malware_plugin', 'secupress_check_malware_plugin_admin_post_cb' ); /** * * * @since 2.2.6 * @author Julio Potier * * @return (string) JSON **/ function secupress_check_malware_plugin_admin_post_cb() { if ( ! isset( $_POST['plugin'], $_POST['muplugin'], $_POST['_ajax_nonce'] ) || ! check_ajax_referer( 'secupress_check_malware_plugin' ) ) { wp_send_json_error( 'Incorrect AJAX Request' ); } $res = ''; $yes = '<span class="dashicons dashicons-yes-alt"></span>'; $toggle = '<span class="dashicons secupress-dashicon dashicons-arrow-right-alt2"></span>'; if ( '1' === $_POST['muplugin'] ) { $plugin_file = basename( $_POST['plugin'] ); $root_path = MUPLUGINDIR; $tr_name = 'secupress-check-malware-result-mu-' . md5( $plugin_file ); $malware = secupress_check_malware( $root_path . '/' . $plugin_file, true ); if ( $malware ) { $res .= $toggle; $res .= '<code>/' . $plugin_file . '</code>'; $res .= $malware; } else { $res = $yes; } } else { $flag = false; $plugin_file = plugin_basename( $_POST['plugin'] ); $root_path = WP_PLUGIN_DIR . '/' . dirname( $plugin_file ); $plugin_files = secupress_list_files_from_folder( $root_path ); foreach( $plugin_files as $p_file ) { $tr_name = 'secupress-check-malware-result-p-' . md5( $root_path ); $malware = secupress_check_malware( str_replace( ABSPATH, '', $root_path . '/' . $p_file ) ); if ( $malware ) { $flag = true; $res .= $toggle; $res .= '<code>/' . dirname( $plugin_file ) . '/' . $p_file . '</code>'; $res .= $malware; $res .= '<br>'; } } if ( ! $flag ) { $res = $yes; } } set_transient( $tr_name, $res, DAY_IN_SECONDS ); wp_send_json_success( $res ); } add_action( 'wp_ajax_secupress_search', 'secupress_search_ajax_cb' ); /** * Used to handle search requests in modules page. * * @author Julio Potier * @since 2.6 * * @return (string) JSON */ function secupress_search_ajax_cb() { secupress_check_user_capability(); secupress_check_admin_referer( 'secupress_search', 'secupress_search_nonce' ); $search_query = isset( $_POST['secupress_module_search'] ) ? trim( $_POST['secupress_module_search'] ) : ''; if ( empty( $search_query ) || strlen( $search_query ) < 2 ) { wp_send_json_success( [] ); } $results = []; $results_by_key = []; $modules = secupress_get_modules(); $search_query = function_exists( 'mb_strtolower' ) ? mb_strtolower( $search_query ) : strtolower( $search_query ); foreach ( $modules as $module_key => $module_data ) { if ( empty( $module_data['submodules'] ) || ! is_array( $module_data['submodules'] ) ) { continue; } foreach ( $module_data['submodules'] as $submodule_key => $submodule_title ) { if ( empty( $submodule_title ) ) { continue; } $clean_title = preg_replace( '/^[>*]+/', '', $submodule_title ); $clean_title = trim( wp_strip_all_tags( $clean_title ) ); $clean_title_lower = function_exists( 'mb_strtolower' ) ? mb_strtolower( $clean_title ) : strtolower( $clean_title ); $title_words = preg_split( '/\s+/', $clean_title_lower, -1, PREG_SPLIT_NO_EMPTY ); $best_score = 0; $has_exact_match = false; foreach ( $title_words as $title_word ) { if ( $title_word === $search_query ) { $has_exact_match = true; } $has_word_match = false !== ( function_exists( 'mb_strpos' ) ? mb_strpos( $title_word, $search_query ) : strpos( $title_word, $search_query ) ); $levenshtein_score = function_exists( 'secupress_levenshtein' ) ? secupress_levenshtein( $title_word, $search_query ) : 0; $prefix_len = 0; $max_prefix_len = min( strlen( $title_word ), strlen( $search_query ) ); for ( $i = 0; $i < $max_prefix_len; $i++ ) { if ( $title_word[$i] !== $search_query[$i] ) { break; } $prefix_len++; } $prefix_score = $prefix_len >= 3 ? 0.7 : 0; $word_score = $has_word_match ? 1 : max( $levenshtein_score, $prefix_score ); if ( $word_score > $best_score ) { $best_score = $word_score; } } if ( $best_score >= 0.7 ) { $url = secupress_admin_url( 'modules', $module_key ) . '#' . $submodule_key; $result_key = $module_key . '|' . $submodule_key; if ( ! isset( $results_by_key[ $result_key ] ) || $best_score > $results_by_key[ $result_key ]['score'] ) { $results_by_key[ $result_key ] = [ 'title' => $clean_title, 'url' => $url, 'score' => $best_score, 'exact' => $has_exact_match ? 1 : 0, ]; } } } } if ( $results_by_key ) { $results = array_values( $results_by_key ); usort( $results, function( $left, $right ) { if ( $left['exact'] !== $right['exact'] ) { return ( $left['exact'] > $right['exact'] ) ? -1 : 1; } if ( $left['score'] === $right['score'] ) { return 0; } return ( $left['score'] > $right['score'] ) ? -1 : 1; } ); foreach ( $results as $index => $result ) { unset( $results[ $index ]['score'] ); unset( $results[ $index ]['exact'] ); } } wp_send_json_success( $results ); } free/admin/admin.php 0000644 00000012161 15174670627 0010376 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** --------------------------------------------------------------------------------------------- */ /** VARIOUS ===================================================================================== */ /** --------------------------------------------------------------------------------------------- */ add_filter( 'admin_page_access_denied', 'secupress_is_jarvis', 9 ); /** * Easter egg when you visit a "secupress" page with a typo in it, or just don't have access (not under white label). * * @since 1.0 * @author Tony Stark */ function secupress_is_jarvis() { if ( isset( $_GET['page'] ) && 'secupress_settings' === $_GET['page'] ) { wp_redirect( secupress_admin_url( 'modules' ) ); die(); } if ( ! secupress_is_white_label() && isset( $_GET['page'] ) && strpos( $_GET['page'], 'secupress' ) !== false ) { // Do not use SECUPRESS_PLUGIN_SLUG, we don't want that in white label. wp_die( '[J.A.R.V.I.S.] You are not authorized to access this area.<br/>[Christine Everhart] Jesus ...<br/>[Pepper Potts] That’s Jarvis, he runs the house.', 403 ); } } add_action( 'secupress.loaded', 'secupress_been_first' ); /** * Make SecuPress the first plugin loaded. * * @since 1.0 */ function secupress_been_first() { if ( ! is_admin() ) { return; } $plugin_basename = plugin_basename( __FILE__ ); if ( is_multisite() ) { $active_plugins = get_site_option( 'active_sitewide_plugins' ); if ( isset( $active_plugins[ $plugin_basename ] ) && key( $active_plugins ) !== $plugin_basename ) { $this_plugin = array( $plugin_basename => $active_plugins[ $plugin_basename ] ); unset( $active_plugins[ $plugin_basename ] ); $active_plugins = array_merge( $this_plugin, $active_plugins ); update_site_option( 'active_sitewide_plugins', $active_plugins ); } return; } $active_plugins = get_option( 'active_plugins' ); if ( isset( $active_plugins[ $plugin_basename ] ) && reset( $active_plugins ) !== $plugin_basename ) { unset( $active_plugins[ array_search( $plugin_basename, $active_plugins, true ) ] ); array_unshift( $active_plugins, $plugin_basename ); update_option( 'active_plugins', $active_plugins ); } } /** --------------------------------------------------------------------------------------------- */ /** DETECT BAD PLUGINS AND THEMES =============================================================== */ /** --------------------------------------------------------------------------------------------- */ if ( ! secupress_show_contextual_help() ) { add_filter( 'secupress.settings.help', '__return_empty_string' ); add_filter( 'secupress.settings.description', '__return_empty_string' ); } add_filter( 'pre_http_request', 'secupress_filter_remote_url', 1, 3 ); /** * Filter the URL to prevent calls to secupress.me if needed * * @since 2.0 * @author Julio Potier **/ function secupress_filter_remote_url( $val, $parsed_args, $url ) { if ( secupress_is_pro() && 32 !== strlen( secupress_get_consumer_key() ) && 0 === strpos( $url, untrailingslashit( SECUPRESS_WEB_MAIN ) ) ) { return new WP_Error(); } return $val; } add_filter( 'manage_plugins_custom_column', 'secupress_add_malware_detection_column_content', 10, 3 ); /** * Display if the not installed plugin contains malwares * * @author Julio Potier * @since 2.2.6 * * @param (string) $column_name * @param (string) $plugin_file * @param (array) $plugin_data * @return (void) **/ function secupress_add_malware_detection_column_content( $column_name, $plugin_file, $plugin_data ) { if ( 'secupress_malware_detection' !== $column_name ) { return; } if ( ! secupress_is_pro() ) { ?> <span class="secupress-get-pro-version"> <?php printf( __( 'Available in <a href="%s" target="_blank">Pro Version</a>', 'secupress' ), esc_url( secupress_admin_url( 'get-pro' ) ) ); ?> </span> <?php return; } $rescan = '<button class="button-link refreshscan" data-plugin="' . $plugin_file . '" data-muplugin="' . isset( $plugin_data['muplugin'] ) . '" type="button">' . __( 'Re-scan', 'secupress' ) . '</button>'; if ( isset( $plugin_data['muplugin'] ) ) { $tr_name = 'secupress-check-malware-result-mu-' . md5( $plugin_file ); } else { $root_path = WP_PLUGIN_DIR . '/' . dirname( $plugin_file ); $tr_name = 'secupress-check-malware-result-p-' . md5( $root_path ); } if ( false !== $res = get_transient( $tr_name ) ) { echo $res . '<p>' . $rescan . '</p>'; } else { echo '<span data-muplugin="' . isset( $plugin_data['muplugin'] ) . '" data-plugin="' . esc_attr( $plugin_file ) . '"><span class="spinner is-active" style="float:left"></span></span>'; } } // add_filter( 'manage_plugins_columns', 'secupress_add_malware_detection_column' ); // Do not uncomment, let the modules does it. /** * Only keep the "name" and "description" columns on our view. * * @author Julio Potier * @since 2.2.6 * * @param (array) $columns * @return (array) $columns **/ function secupress_add_malware_detection_column( $columns ) { $columns['secupress_malware_detection'] = __( 'Malware Detection', 'secupress' ) . ' <span class="dashicons secupress-dashicon dashicons-editor-expand"></span>'; return $columns; } free/admin/scanner-step-1-new.php 0000644 00000011620 15174670627 0012634 0 ustar 00 <div class="secupress-scans-group secupress-group-new"> <?php if ( ! $is_subsite ) { ?> <div class="secupress-sg-header secupress-flex secupress-flex-spaced"> <div class="secupress-sgh-name"> <i class="secupress-icon-secupress" aria-hidden="true"></i> <p class="secupress-sgh-title"><?php printf( esc_html__( '%sNew Items', 'secupress' ), ( SECUPRESS_PLUGIN_NAME === 'SecuPress' ? SECUPRESS_PLUGIN_NAME . ' ' . SECUPRESS_VERSION . ' ' : '' ) ); ?></p> <p class="secupress-sgh-description"><?php _e( 'These new items need to be checked: you will need to rescan your website.', 'secupress' ); ?></p> </div> <div class="secupress-sgh-actions secupress-flex secupress-flex-top"> <button class="secupress-vnormal hide-if-no-js dont-trigger-hide trigger-hide-first" type="button" data-trigger="slidetoggle" data-target="secupress-group-content-new"> <i class="secupress-icon-angle-up" aria-hidden="true"></i> <span class="screen-reader-text"><?php _e( 'Show/Hide panel', 'secupress' ); ?></span> </button> </div> </div><!-- .secupress-sg-header --> <?php } ?> <div id="secupress-group-content-new" class="secupress-sg-content"> <?php foreach ( $new_scans as $option_name => $class_name_part ) { $class_name = 'SecuPress_Scan_' . $class_name_part; if ( ! class_exists( $class_name ) && file_exists( secupress_class_path( 'scan', $class_name_part ) ) ) { secupress_require_class( 'scan', $class_name_part ); } $current_test = $class_name::get_instance(); $referer = urlencode( esc_url_raw( self_admin_url( 'admin.php?page=' . SECUPRESS_PLUGIN_SLUG . '_scanners' . ( $is_subsite ? '' : '#' . $class_name_part ) ) ) ); // Scan. $scanner = isset( $scanners[ $option_name ] ) ? $scanners[ $option_name ] : array(); $scan_status = ! empty( $scanner['status'] ) ? $scanner['status'] : 'notscannedyet'; $scan_nonce_url = 'secupress_scanner_' . $class_name_part . ( $is_subsite ? '-' . $site_id : '' ); $scan_nonce_url = wp_nonce_url( admin_url( 'admin-post.php?action=secupress_scanner&test=' . $class_name_part . '&_wp_http_referer=' . $referer . ( $is_subsite ? '&for-current-site=1&site=' . $site_id : '' ) ), $scan_nonce_url ); $scan_message = $current_test->title; if ( ! empty( $scanner['msgs'] ) ) { $scan_message = secupress_format_message( $scanner['msgs'], $class_name_part ); } // Row css class. $row_css_class = 'secupress-item-' . $class_name_part; $row_css_class .= ' status-' . sanitize_html_class( $scan_status ); $row_css_class .= isset( $autoscans[ $class_name_part ] ) ? ' autoscan' : ''; ?> <div class="secupress-item-all <?php echo $row_css_class; ?>" id="<?php echo $class_name_part; ?>"> <div class="secupress-flex"> <p class="secupress-item-status"> <span class="secupress-label"><?php echo secupress_status( $scan_status ); ?></span> </p> <p class="secupress-item-title"><?php echo $scan_message; ?></p> <p class="secupress-row-actions"> <a class="secupress-button secupress-button-mini secupress-scanit hide-if-js" href="<?php echo esc_url( $scan_nonce_url ); ?>"> <span class="icon" aria-hidden="true"> <i class="secupress-icon-refresh"></i> </span> <span class="text"> <?php _ex( 'Scan', 'verb', 'secupress' ); ?> </span> </a><br class="hide-if-js"> <?php /** * Things changed: * data-trigger added * data-target instead of data-test * data-target === .secupress-item-details' ID */ if ( apply_filters( 'secupress.settings.help', true ) ) { ?> <button data-trigger="slidetoggle" data-target="details-<?php echo $class_name_part; ?>" class="secupress-details link-like hide-if-no-js" type="button"> <span class="secupress-toggle-button"> <span aria-hidden="true" class="icon"> <i class="secupress-icon-info-disk"></i> </span> <span class="text"><?php _e( 'Learn more', 'secupress' ); ?></span> </span> <span class="secupress-toggle-button hidden" aria-hidden="true"> <span aria-hidden="true" class="icon"> <i class="secupress-icon-cross"></i> </span> <span class="text"><?php _e( 'Close' ); ?></span> </span> </button> <?php } ?> </p> </div><!-- .secupress-flex --> <div class="secupress-item-details hide-if-js" id="details-<?php echo $class_name_part; ?>"> <div class="secupress-flex"> <span class="secupress-details-icon"> <i class="secupress-icon-i" aria-hidden="true"></i> </span> <p class="details-content"><?php echo wp_kses( $current_test->more, $allowed_tags ); ?></p> <span class="secupress-placeholder"></span> </div> </div><!-- .secupress-item-details --> </div><!-- .secupress-item-all --> <?php } // Eo foreach $new_scans. $flag_first_iteration = false; ?> </div><!-- .secupress-sg-content --> </div> <!-- .secupress-scans-group --> <?php free/admin/scanner-step-4.php 0000644 00000034031 15174670627 0012051 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); $modules = secupress_get_modules(); $scanned_items = secupress_get_scan_results(); $nb_good_scans = 0; $nb_bad_scans = 0; $nb_warning_scans = 0; $grade = secupress_get_scanner_counts( 'grade' ); $old_report = secupress_get_old_report(); $old_grade = $old_report['grade']; $this_good_scans = array(); foreach ( $scanned_items as $class_name_part => $details ) { if ( isset( $old_report['report'][ $class_name_part ] ) && 'good' === $details['status'] && 'good' !== $old_report['report'][ $class_name_part ]['status'] ) { $this_good_scans[ $class_name_part ] = $details; if ( 'bad' === $old_report['report'][ $class_name_part ]['status'] ) { ++$nb_bad_scans; } else { ++$nb_warning_scans; } } if ( 'good' === $details['status'] ) { ++$nb_good_scans; } } ?> <div id="secupress-tests" class="secupress-tests secupress-is-finish-report secupress-box-shadow"> <div class="secupress-summary-header secupress-section-dark"> <div class="secupress-flex"> <div class="secupress-col-logo"> <?php echo secupress_get_logo( array( 'width' => 81 ) ); ?> </div> <div class="secupress-col-summary-text"> <?php // Display a "bravo" message only if there is something fixed. if ( $nb_good_scans ) { ?> <p class="secupress-text-medium secupress-mb0"><?php printf( __( 'Bravo! You fixed %1$d of %2$d.', 'secupress' ), $nb_good_scans, count( $scanned_items ) ); ?></p> <?php } else { ?> <p class="secupress-text-medium secupress-mb0"><?php printf( _x( 'Nothing fixed yet, %d left.', 'always plural form', 'secupress' ), count( $scanned_items ) ); ?></p> <?php } if ( secupress_show_grade_system() ) { ?> <p> <?php // Display a "grade ent from to" message only if it's better. if ( $old_grade === $grade ) { printf( __( 'Your grade is still %s. ', 'secupress' ), $grade ); } else { printf( __( 'Your grade went from %1$s to %2$s. ', 'secupress' ), $old_grade, $grade ); } // Display the pre-message to show what has just been fixed. if ( $nb_bad_scans || $nb_warning_scans ) { _e( 'You fixed: ', 'secupress' ); } ?> </p> <?php } ?> <div class="secupress-flex secupress-flex-spaced"> <div> <?php if ( $nb_bad_scans || $nb_warning_scans ) { ?> <ul class="secupress-chart-legend"> <?php if ( $nb_bad_scans ) { ?> <li class="status-bad"> <span class="secupress-carret"></span> <?php printf( __( '%d Bad', 'secupress' ), $nb_bad_scans ); ?> <span class="secupress-count-bad"></span> </li> <?php } if ( $nb_warning_scans ) { ?> <li class="status-warning"> <span class="secupress-carret"></span> <?php printf( __( '%d Warning', 'secupress' ), $nb_warning_scans ); ?> <span class="secupress-count-warning"></span> </li> <?php } ?> </ul> <?php } ?> </div> <?php if ( $nb_bad_scans || $nb_warning_scans ) { ?> <p> <button class="secupress-button secupress-button-ghost secupress-button-mini hide-is-no-js" type="button" data-target="secupress-summaries" data-trigger="slidetoggle" title="<?php esc_attr_e( 'Show/hide the details of scanned items in your report', 'secupress' ); ?>"> <span class="icon"> <i class="secupress-icon-angle-down" aria-hidden="true"></i> </span> <span class="text" aria-hidden="true"> <span class="hidden-when-activated"> <?php _e( 'See all fixed issues', 'secupress' ); ?> </span> <span class="visible-when-activated"> <?php _e( 'Hide all fixed issues', 'secupress' ); ?> </span> </span> </button> </p> <?php } ?> </div> </div> </div> </div><!-- .secupress-summary-header --> <div id="secupress-summaries" class="secupress-summaries hide-if-js"> <?php foreach ( $secupress_tests as $module_name => $class_name_parts ) { $i = 0; $module_title = ! empty( $modules[ $module_name ]['title'] ) ? $modules[ $module_name ]['title'] : ''; $module_summary = ! empty( $modules[ $module_name ]['summaries']['small'] ) ? $modules[ $module_name ]['summaries']['small'] : ''; $class_name_parts = array_combine( array_map( 'strtolower', $class_name_parts ), $class_name_parts ); foreach ( $class_name_parts as $option_name => $class_name_part ) { if ( ! file_exists( secupress_class_path( 'scan', $class_name_part ) ) ) { unset( $class_name_parts[ $option_name ] ); continue; } secupress_require_class( 'scan', $class_name_part ); } // For this priority, order the scans by status: 'good', 'warning', 'good', 'new'. $class_name_parts = array_intersect_key( $class_name_parts, $good_scans ); $class_name_parts = array_intersect_key( $class_name_parts, $this_good_scans ); if ( ! $class_name_parts ) { continue; } ?> <div class="secupress-scans-group secupress-group-<?php echo $module_name; ?>"> <div class="secupress-sg-header secupress-flex secupress-flex-spaced"> <div class="secupress-sgh-name"> <i class="secupress-icon-user-login" aria-hidden="true"></i> <p class="secupress-sgh-title"><?php echo $module_title; ?></p> <p class="secupress-sgh-description"><?php echo $module_summary; ?></p> </div> <div class="secupress-sgh-actions secupress-flex secupress-flex-top"> <button class="secupress-vnormal hide-if-no-js dont-trigger-hide trigger-hide-first" type="button" data-trigger="slidetoggle" data-target="secupress-group-content-<?php echo $module_name; ?>"> <i class="secupress-icon-angle-up" aria-hidden="true"></i> <span class="screen-reader-text"><?php _e( 'Show/hide panel', 'secupress' ); ?></span> </button> </div> </div><!-- .secupress-sg-header --> <div id="secupress-group-content-<?php echo $module_name; ?>" class="secupress-sg-content"> <?php foreach ( $class_name_parts as $option_name => $class_name_part ) { ++$i; $class_name = 'SecuPress_Scan_' . $class_name_part; $current_test = $class_name::get_instance(); $referer = urlencode( esc_url_raw( self_admin_url( 'admin.php?page=' . SECUPRESS_PLUGIN_SLUG . '_scanners#' . $class_name_part ) ) ); $is_fixable = true === $current_test->is_fixable() || 'pro' === $current_test->is_fixable() && secupress_is_pro(); // Scan. $scanner = isset( $scanners[ $option_name ] ) ? $scanners[ $option_name ] : array(); $scan_status = ! empty( $scanner['status'] ) ? $scanner['status'] : 'notscannedyet'; $scan_nonce_url = 'secupress_scanner_' . $class_name_part; $scan_nonce_url = wp_nonce_url( admin_url( 'admin-post.php?action=secupress_scanner&test=' . $class_name_part . '&_wp_http_referer=' . $referer ), $scan_nonce_url ); $scan_message = '¯'; if ( ! empty( $scanner['msgs'] ) ) { $scan_message = secupress_format_message( $scanner['msgs'], $class_name_part ); } // Fix. $fix = ! empty( $fixes[ $option_name ] ) ? $fixes[ $option_name ] : array(); $fix_status_text = ! empty( $fix['status'] ) && 'good' !== $fix['status'] ? secupress_status( $fix['status'] ) : ''; $fix_nonce_url = 'secupress_fixit_' . $class_name_part; $fix_nonce_url = wp_nonce_url( admin_url( 'admin-post.php?action=secupress_fixit&test=' . $class_name_part . '&_wp_http_referer=' . $referer ), $fix_nonce_url ); $fix_message = ''; if ( ! empty( $fix['msgs'] ) && 'good' !== $scan_status ) { $scan_message = secupress_format_message( $fix['msgs'], $class_name_part ); } // Row css class. $row_css_class = ' status-' . sanitize_html_class( $scan_status ); $row_css_class .= isset( $autoscans[ $class_name_part ] ) ? ' autoscan' : ''; $row_css_class .= $is_fixable ? ' fixable' : ' not-fixable'; $row_css_class .= ! empty( $fix['has_action'] ) ? ' status-hasaction' : ''; $row_css_class .= ! empty( $fix['status'] ) && empty( $fix['has_action'] ) ? ' has-fix-status' : ' no-fix-status'; switch ( $scan_status ) { case 'bad' : $icon_slug = 'cross-o'; $scan_status_word = esc_html__( 'Not fixed', 'secupress' ); break; case 'warning' : $icon_slug = 'exclamation-o'; $scan_status_word = esc_html__( 'Error', 'secupress' ); break; case 'pending' : $icon_slug = 'clock-o-2'; $scan_status_word = esc_html__( 'Pending', 'secupress' ); break; default : $icon_slug = 'check'; $scan_status_word = esc_html__( 'Fixed', 'secupress' ); } ?> <div class="secupress-item-all secupress-item-<?php echo $class_name_part; ?> status-all <?php echo $row_css_class; ?>" id="<?php echo $class_name_part; ?>"> <div class="secupress-flex"> <p class="secupress-item-status secupress-status-mini"> <span class="secupress-dot-<?php echo $scan_status; ?>"></span> </p> <p class="secupress-item-title"><?php echo $scan_message; ?></p> <p class="secupress-row-actions"> <span class="secupress-status secupress-status-<?php echo sanitize_html_class( $scan_status ); ?>"> <i class="secupress-icon-<?php echo $icon_slug; ?>" aria-hidden="true"></i> <?php echo $scan_status_word; ?></span> </p> </div><!-- .secupress-flex --> </div><!-- .secupress-item-all --> <?php } ?> </div><!-- .secupress-sg-content --> </div><!-- .secupress-scans-group --> <?php } ?> <div class="secupress-step-content-footer secupress-flex secupress-flex-top secupress-flex-spaced" id="secupress-step-content-footer"> <?php $export_pdf_btn = '<span class="icon"> <i class="secupress-icon-file-pdf-o" aria-hidden="true"></i> </span> <span class="text"> ' . __( 'Export as PDF', 'secupress' ) . ' </span>'; ?> <p> <?php if ( secupress_is_pro() ) { ?> <a href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=secupress_export_pdf' ), 'secupress_export_pdf' ) ); ?>" title="<?php esc_attr_e( 'Export this report as PDF file.', 'secupress' ); ?>" class="secupress-button shadow"> <?php echo $export_pdf_btn; ?> </a> <?php } else { ?> <a href="<?php echo esc_url( secupress_admin_url( 'get-pro' ) ) ?>" type="button" title="<?php esc_attr_e( 'Get the Pro Version to export this report as PDF file.', 'secupress' ); ?>" target="_blank" class="secupress-button disabled shadow"> <?php echo $export_pdf_btn; ?> </a> <br> <span class="secupress-get-pro-version"> <?php printf( __( 'Available in <a href="%s" target="_blank">Pro Version</a>', 'secupress' ), esc_url( secupress_admin_url( 'get-pro' ) ) ); ?> </span> <?php } ?> </p> </div><!-- .secupress-step-content-footer --> </div><!-- .secupress-summaries --> <div class="secupress-go-farther"> <div class="secupress-flex"> <div class="secupress-col"> <p class="secupress-farther-title"><?php _e( 'Want to go farther?', 'secupress' ); ?></p> <p class="secupress-farther-desc"><?php _e( 'Perfect the security of your website with our dedicated modules.', 'secupress' ); ?></p> </div> <div class="secupress-col secupress-col-action"> <a href="<?php echo esc_url( secupress_admin_url( 'modules' ) ); ?>" class="secupress-rich-link secupress-current"> <span class="secupress-label-with-icon"> <i aria-hidden="true" class="secupress-icon-cogs rounded"></i> <span class="secupress-upper"><?php printf( /** Translators: %s is the plugin name. */ __( '%s modules', 'secupress' ), SECUPRESS_PLUGIN_NAME ); ?></span> <span class="secupress-description"><?php esc_html_e( 'Fine-tune your security', 'secupress' ); ?></span> </span> </a> </div> </div> <div class="secupress-flex"> <div class="secupress-col"> <p class="secupress-farther-title"><?php esc_html_e( 'Manage your recurring scans', 'secupress' ); ?></p> <p class="secupress-farther-desc"><?php sprintf( esc_html__( 'Let %s scan your website when you are away by using scheduled scans.', 'secupress' ), SECUPRESS_PLUGIN_NAME ); ?></p> </div> <div class="secupress-col secupress-col-action"> <a href="<?php echo esc_url( secupress_admin_url( 'modules' ) ); ?>&module=schedules" class="secupress-rich-link secupress-current"> <span class="secupress-label-with-icon"> <i aria-hidden="true" class="secupress-icon-calendar rounded"></i> <span class="secupress-upper"><?php esc_html_e( 'Schedule Scans', 'secupress' ); ?></span> <span class="secupress-description"><?php esc_html_e( 'Schedule your recurring scans', 'secupress' ); ?></span> </span> </a> </div> </div> </div><!-- .secupress-go-farther --> </div><!-- .secupress-tests --> <?php if ( ! secupress_is_pro() ) { ?> <div class="secupress-pro-summary secupress-box-shadow"> <div class="secupress-summary-header secupress-section-dark"> <div class="secupress-flex"> <div class="secupress-col-logo"> <?php echo secupress_get_logo( array( 'width' => 81 ) ); ?> </div> <div class="secupress-col-summary-text secupress-flex secupress-flex-spaced"> <p class="secupress-text-medium secupress-mb0"> <?php _e( 'Get a better score and unlock all features', 'secupress' ); ?> </p> <p class="secupress-p1"> <a href="<?php echo esc_url( secupress_admin_url( 'get-pro' ) ); ?>" class="secupress-button secupress-button-tertiary secupress-button-getpro"> <span class="icon"> <i class="secupress-icon-secupress-simple" aria-hidden="true"></i> </span> <span class="text"> <?php _e( 'Get Pro', 'secupress' ); ?> </span> </a> </p> </div> </div> </div><!-- .secupress-summary-header --> <?php secupress_print_pro_advantages(); ?> </div><!-- .secupress-pro-summary --> <?php } ?> free/admin/notices.php 0000644 00000044413 15174670627 0010757 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); add_action( 'current_screen', 'secupress_http_block_external_notice' ); /** * This notice is displayed when external HTTP requests are blocked via the WP_HTTP_BLOCK_EXTERNAL constant * * @since 1.0 * @author Julio Potier */ function secupress_http_block_external_notice() { global $current_screen; if ( ! current_user_can( secupress_get_capability() ) ) { return; } $is_accessible = defined( 'WP_ACCESSIBLE_HOSTS' ) && strpos( WP_ACCESSIBLE_HOSTS, '*.secupress.me' ) !== false; if ( $is_accessible || ! defined( 'WP_HTTP_BLOCK_EXTERNAL' ) || ( isset( $current_screen ) && 'toplevel_page_' . SECUPRESS_PLUGIN_SLUG . '_scanners' !== $current_screen->base && SECUPRESS_PLUGIN_SLUG . '_page_' . SECUPRESS_PLUGIN_SLUG . '_modules' !== $current_screen->base && SECUPRESS_PLUGIN_SLUG . '_page_' . SECUPRESS_PLUGIN_SLUG . '_settings' !== $current_screen->base && SECUPRESS_PLUGIN_SLUG . '_page_' . SECUPRESS_PLUGIN_SLUG . '_logs' !== $current_screen->base ) || SecuPress_Admin_Notices::is_dismissed( 'http-block-external' ) ) { return; } $message = '<div>'; $message .= '<p><strong>' . __( 'External HTTP requests are blocked!', 'secupress' ) . '</strong></p>'; $message .= '<p>' . sprintf( __( 'You defined the %1$s constant in the %2$s to block all external HTTP requests.', 'secupress' ), secupress_code_me( 'WP_HTTP_BLOCK_EXTERNAL' ), secupress_code_me( secupress_get_wpconfig_filename() ) ) . '</p>'; $message .= '<p>'; $message .= sprintf( __( 'To make %s work well, you have to either remove the PHP constant, or add the following code in your %s file.', 'secupress' ), SECUPRESS_PLUGIN_NAME, secupress_code_me( secupress_get_wpconfig_filename() ) ) . '<br/>'; $message .= __( 'Click on the field and press Ctrl+A or Cmd+A to select all.', 'secupress' ); $message .= '</p>'; $message .= '<p><textarea readonly="readonly" class="large-text readonly" rows="1">define( \'WP_ACCESSIBLE_HOSTS\', \'*.secupress.me\' );</textarea></p>'; $message .= '</div>'; secupress_add_notice( $message, 'error', 'http-block-external' ); } add_action( 'admin_init', 'secupress_plugins_to_deactivate' ); /** * This warning is displayed when some plugins may conflict with SecuPress. * * @since 1.0 */ function secupress_plugins_to_deactivate() { if ( ! current_user_can( secupress_get_capability() ) ) { return; } $plugins_to_deactivate = [ // Forced deactivation 'o2s-wp-tiger/o2s-wp-tiger.php' => true, // 2.0.1 ]; add_filter( 'all_plugins', function( $all_plugins ) use ( $plugins_to_deactivate ) { $all_plugins = array_diff_key( $all_plugins, $plugins_to_deactivate ); return $all_plugins; }); $plugins_to_deactivate = array_filter( array_keys( $plugins_to_deactivate ), 'is_plugin_active' ); if ( $plugins_to_deactivate ) { $message = _n( 'The following plugin has been deactivated because we already included its features:', 'The following plugins has been deactivated because we already included its features:', count( $plugins_to_deactivate ), 'secupress' ); $message .= '</p><ul>'; foreach ( $plugins_to_deactivate as $plugin ) { $plugin_data = get_plugin_data( WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . $plugin ); $context = isset( $_GET['plugin_status'] ) ? '&plugin_status=' . $_GET['plugin_status'] : ''; $page = isset( $_GET['pages'] ) ? '&paged=' . $_GET['paged'] : ''; $search = isset( $_GET['s'] ) ? '&s=' . $_GET['s'] : ''; $url = wp_nonce_url( 'plugins.php?action=deactivate&plugin=' . $plugin . $context . $page . $search, 'deactivate-plugin_' . $plugin ); $message .= '<li>• ' . $plugin_data['Name'] . '</span></li>'; deactivate_plugins( $plugin, true ); } $message .= '</ul>'; secupress_add_notice( $message, 'error', 'deactivate-plugin' ); } $plugins_to_warn = []; // deactivation not mandatory, dismissable /* 'all-in-one-wp-security-and-firewall/wp-security.php', // repo 'better-wp-security/better-wp-security.php', // Solid Security Free repo 'ithemes-security-pro/ithemes-security-pro.php', // Solid Security Pro 'bulletproof-security/bulletproof-security.php', // repo 'getastra/astra-security.php', // repo 'gotmls/index.php', // repo 'ninjafirewall/ninjafirewall.php', // repo 'malcare-security/malcare.php', // repo 'security-ninja/security-ninja.php', // repo 'security-ninja-pro/security-ninja.php', // not sure 'security-ninja-pro/security-ninja-pro.php', // not sure 'sucuri-scanner/sucuri.php', // repo 'patchstack/patchstack.php', // repo 'wordfence/wordfence.php', // repo 'wp-cerber/wp-cerber.php', // premium only, deleted from repo 2022 'wp-defender/wp-defender.php', // repo 'wp-simple-firewall/icwp-wpsf.php', // repo 'user-name-security/user-name-security.php', // repo 'sf-author-url-control/sf-author-url-control.php', // repo 'really-simple-ssl/really-simple-ssl.php', // repo 'really-simple-ssl-pro/really-simple-ssl-pro.php', ]; */ $plugins_to_warn = array_filter( $plugins_to_warn, 'is_plugin_active' ); if ( $plugins_to_warn ) { $hash = md5( serialize( $plugins_to_warn ) ); if ( secupress_notice_is_dismissed( 'warn-plugin-' . $hash ) ) { return; } $message = sprintf( _n( 'The following plugin may cause conflicts with %s:', 'The following plugins may cause conflicts with %s:', count( $plugins_to_warn ), 'secupress' ), SECUPRESS_PLUGIN_NAME ); $message .= '</p><ul>'; foreach ( $plugins_to_warn as $plugin ) { $plugin_data = get_plugin_data( WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . $plugin ); $context = isset( $_GET['plugin_status'] ) ? '&plugin_status=' . $_GET['plugin_status'] : ''; $page = isset( $_GET['pages'] ) ? '&paged=' . $_GET['paged'] : ''; $search = isset( $_GET['s'] ) ? '&s=' . $_GET['s'] : ''; $url = wp_nonce_url( 'plugins.php?action=deactivate&plugin=' . $plugin . $context . $page . $search, 'deactivate-plugin_' . $plugin ); $message .= '<li>• ' . $plugin_data['Name'] . '</span> <a href="' . esc_url( $url ) . '" class="button-secondary alignright">' . _x( 'Deactivate', 'plugin', 'secupress' ) . '</a></li>'; } $message .= '</ul>'; secupress_add_transient_notice( $message, 'warning', SECUPRESS_MAJOR_VERSION . '-warn-plugin-' . $hash ); } } add_action( 'admin_init', 'secupress_add_packed_plugins_notice' ); /** * Display a notice if the standalone version of a plugin packed in SecuPress is used. * * @since 1.0 */ function secupress_add_packed_plugins_notice() { if ( ! current_user_can( secupress_get_capability() ) ) { return; } /** * Filter the list of plugins packed in SecuPress. * * @since 1.0 * * @param (array) $plugins A list of plugin paths, relative to the plugins folder. The "file name" of the packed plugin is used as key. * Example: array( 'move-login' => 'sf-move-login/sf-move-login.php' ) */ $plugins = apply_filters( 'secupress.packed-plugins', array() ); $plugins = array_filter( $plugins, 'is_plugin_active' ); if ( ! $plugins || secupress_notice_is_dismissed( 'deactivate-packed-plugins' ) ) { return; } $message = '<p>'; $message .= sprintf( /** Translators: 1 is the plugin name */ __( 'The features of the following plugins are included in %1$s. You can deactivate the plugins now and enable these features later in %1$s:', 'secupress' ), '<strong>' . SECUPRESS_PLUGIN_NAME . '</strong>' ); $message .= '</p><ul>'; foreach ( $plugins as $plugin ) { $plugin_data = get_plugin_data( WP_PLUGIN_DIR . DIRECTORY_SEPARATOR . $plugin ); $message .= '<li>' . $plugin_data['Name'] . '</span> <a href="' . esc_url( wp_nonce_url( admin_url( 'plugins.php?action=deactivate&plugin=' . urlencode( $plugin ) ), 'deactivate-plugin_' . $plugin ) ) . '" class="button-secondary alignright">' . _x( 'Deactivate', 'plugin', 'secupress' ) . '</a></li>'; } $message .= '</ul>'; secupress_add_notice( $message, 'error', 'deactivate-packed-plugins' ); } add_action( 'activate_plugin', 'secupress_reset_packed_plugins_notice_on_plugins_activation' ); /** * When the standalone version of a plugin packed in SecuPress is activated, reinit the notice. * * @since 1.0 * * @param (string) $plugin The plugin path, relative to the plugins folder. */ function secupress_reset_packed_plugins_notice_on_plugins_activation( $plugin ) { if ( ! current_user_can( secupress_get_capability() ) ) { return; } /** This action is documented in inc/admin/notices.php */ $plugins = apply_filters( 'secupress.packed-plugins', array() ); if ( ! $plugins ) { return; } $plugins = array_flip( $plugins ); if ( isset( $plugins[ $plugin ] ) ) { secupress_reinit_notice( 'deactivate-packed-plugins' ); } } add_action( 'secupress.modules.activate_submodule', 'secupress_deactivate_standalone_plugin_on_packed_plugin_activation' ); /** * When a plugin packed in SecuPress is activated, deactivate the standalone version. * * @since 1.0 * * @param (string) $plugin The name of the packed plugin. */ function secupress_deactivate_standalone_plugin_on_packed_plugin_activation( $plugin ) { /** This action is documented in inc/admin/notices.php */ $plugins = apply_filters( 'secupress.plugins.packed-plugins', [] ); if ( has_filter( 'secupress.plugins.packed-plugins' ) ) { _deprecated_hook( 'secupress.plugins.packed-plugins', '2.2.6', 'secupress.packed-plugins' ); } $plugins = apply_filters( 'secupress.packed-plugins', $plugins ); if ( isset( $plugins[ $plugin ] ) && secupress_is_plugin_active( $plugins[ $plugin ] ) ) { deactivate_plugins( $plugins[ $plugin ] ); } } add_action( 'admin_init', 'secupress_warning_module_activity' ); /** * These warnings are displayed when a module has been activated/deactivated. * * @since 1.0 */ function secupress_warning_module_activity() { if ( wp_doing_ajax() ) { return; } if ( ! current_user_can( secupress_get_capability() ) ) { return; } $current_user_id = get_current_user_id(); $activated_modules = secupress_get_site_transient( 'secupress_module_activation_' . $current_user_id ); $deactivated_modules = secupress_get_site_transient( 'secupress_module_deactivation_' . $current_user_id ); if ( false !== $activated_modules ) { $message = _n( 'This module has been activated:', 'These modules have been activated:', count( $activated_modules ), 'secupress' ); $message .= sprintf( '</p><ul><li>%s</li></ul>', implode( '</li><li>', $activated_modules ) ); secupress_add_notice( $message ); secupress_delete_site_transient( 'secupress_module_activation_' . $current_user_id ); } if ( false !== $deactivated_modules ) { $message = _n( 'This module has been deactivated:', 'These modules have been deactivated:', count( $deactivated_modules ), 'secupress' ); $message .= sprintf( '</p><ul><li>%s</li></ul>', implode( '</li><li>', $deactivated_modules ) ); secupress_add_notice( $message ); secupress_delete_site_transient( 'secupress_module_deactivation_' . $current_user_id ); } } add_action( 'all_admin_notices', 'secupress_warning_no_oneclick_scan_yet', 50 ); /** * This warning is displayed if no "One-Click Scan" has been performed yet. * * @since 1.0 */ function secupress_warning_no_oneclick_scan_yet() { $screen_id = get_current_screen(); $screen_id = $screen_id && ! empty( $screen_id->id ) ? $screen_id->id : false; if ( ! ( SECUPRESS_PLUGIN_SLUG . '_page_' . SECUPRESS_PLUGIN_SLUG . '_settings' === $screen_id || ( 'plugins' === $screen_id && ! is_multisite() ) || 'plugins-network' === $screen_id ) ) { return; } if ( secupress_notice_is_dismissed( 'oneclick-scan' ) || ! current_user_can( secupress_get_capability() ) ) { return; } $times = array_filter( (array) get_site_option( SECUPRESS_SCAN_TIMES ) ); $referer = urlencode( esc_url_raw( secupress_get_current_url( 'raw' ) ) ); if ( $times ) { return; } ?> <div class="secupress-section-dark secupress-notice secupress-flex"> <div class="secupress-col-1-4 secupress-col-logo secupress-text-center"> <div class="secupress-logo-block"> <div class="secupress-lb-logo"> <?php echo secupress_get_logo( array( 'width' => '84' ) ); ?> </div> </div> </div> <div class="secupress-col-2-4 secupress-col-text"> <p class="secupress-text-medium"><?php printf( __( '%s is activated, let’s improve the security of your website!', 'secupress' ), SECUPRESS_PLUGIN_NAME ); ?></p> <p><?php esc_html_e( 'Scan website for security issues, right now.', 'secupress' ); ?></p> </div> <div class="secupress-col-1-4 secupress-col-cta"> <a class="secupress-button secupress-button-primary secupress-button-scan" href="<?php echo esc_url( wp_nonce_url( secupress_admin_url( 'scanners' ), 'first_oneclick-scan' ) ) . '&oneclick-scan=1'; ?>"> <span class="icon"> <i class="secupress-icon-radar" aria-hidden="true"></i> </span> <span class="text"> <?php _e( 'Scan website', 'secupress' ); ?> </span> </a> <a class="secupress-close-notice" href="<?php echo wp_nonce_url( admin_url( 'admin-post.php?action=secupress_dismiss-notice¬ice_id=oneclick-scan&_wp_http_referer=' . $referer ), 'secupress-notices' ); ?>"> <i class="secupress-icon-squared-cross" aria-hidden="true"></i> <span class="screen-reader-text"><?php _ex( 'Close', 'verb', 'secupress' ); ?></span> </a> </div> </div><!-- .secupress-section-dark --> <?php secupress_enqueue_notices_styles(); } add_action( 'in_plugin_update_message-' . plugin_basename( SECUPRESS_FILE ), 'secupress_updates_message', 10, 2 ); /** * Display a message below our plugins to display the next update information if needed * * @since 1.1.1 * @author Julio Potier * * @param (array) $plugin_data Contains the old plugin data from EDD or repository. * @param (array) $new_plugin_data Contains the new plugin data from EDD or repository. */ function secupress_updates_message( $plugin_data, $new_plugin_data ) { // Get next version. if ( isset( $new_plugin_data->new_version ) ) { $remote_version = $new_plugin_data->new_version; } if ( ! isset( $remote_version ) ) { return; } $body = get_transient( 'secupress_updates_message' ); if ( ! isset( $body[ $remote_version ] ) ) { $url = 'https://plugins.svn.wordpress.org/secupress/trunk/readme.txt'; $response = wp_remote_get( $url ); if ( is_wp_error( $response ) || 200 !== wp_remote_retrieve_response_code( $response ) ) { return; } $body = wp_remote_retrieve_body( $response ); set_transient( 'secupress_updates_message' , array( $remote_version => $body ) ); } else { $body = $body[ $remote_version ]; } // Find the Notes for this version. $regexp = '#== Upgrade Notice ==.*= ' . preg_quote( $remote_version ) . ' =(.*)=#Us'; if ( preg_match( $regexp, $body, $matches ) ) { $notes = (array) preg_split( '#[\r\n]+#', trim( $matches[1] ) ); $date = str_replace( '* ', '', wp_kses_post( array_shift( $notes ) ) ); echo '<div>'; /** Translators: %1$s is the version number, %2$s is a date. */ echo '<strong>' . sprintf( __( 'Please read these special notes for this update, version %1$s - %2$s', 'secupress' ), $remote_version, $date ) . '</strong>'; echo '<ul style="list-style:square;margin-left:20px;line-height:1em">'; foreach ( $notes as $note ) { echo '<li>' . str_replace( '* ', '', wp_kses_post( $note ) ) . '</li>'; } echo '</ul>'; echo '</div>'; } } add_action( 'admin_init', 'secupress_active_plugins_error' ); /** * If the constant SECUPRESS_ACTIVE_PLUGINS_ERROR is set, we got a problem. * * @since 2.3.11 * @author Julio Potier **/ function secupress_active_plugins_error() { if ( wp_doing_ajax() ) { return; } if ( ! current_user_can( secupress_get_capability() ) ) { return; } if ( ! defined( 'SECUPRESS_ACTIVE_PLUGINS_ERROR' ) || ! SECUPRESS_ACTIVE_PLUGINS_ERROR ) { return; } $message = sprintf( __( 'There is an issue with the %s module. It will not be active until the problem is fixed.<br>Go to %sModule page%s or %sread the documentation%s.', 'secupress' ), secupress_tag_me( __( 'Plugin Actions', 'secupress' ), 'strong' ), sprintf( '<a href="%s">', esc_url( secupress_admin_url( 'modules', 'plugins-themes#row-plugins_actions' ) ) ), '</a>', sprintf( '<a href="%s">', esc_url( __( 'https://docs.secupress.me/article/233-plugin-actions', 'secupress' ) ) ), '</a>', ); secupress_add_notice( $message, 'error', 'active-plugins-error' ); } add_action( 'admin_init', 'secupress_active_plugins_network_error' ); /** * If the constant SECUPRESS_ACTIVE_PLUGINS_NETWORK_ERROR is set, we got a problem. * * @since 2.3.13 * @author Julio Potier **/ function secupress_active_plugins_network_error() { if ( wp_doing_ajax() ) { return; } if ( ! current_user_can( secupress_get_capability() ) ) { return; } if ( ! is_multisite() ) { return; } if ( ! defined( 'SECUPRESS_ACTIVE_PLUGINS_NETWORK_ERROR' ) || ! SECUPRESS_ACTIVE_PLUGINS_NETWORK_ERROR ) { return; } $message = sprintf( __( 'There is an issue with the %s module. It will not be active until the problem is fixed.<br>Go to %sModule page%s or %sread the documentation%s.', 'secupress' ), secupress_tag_me( __( 'Plugin Actions', 'secupress' ), 'strong' ), sprintf( '<a href="%s">', esc_url( secupress_admin_url( 'modules', 'plugins-themes#row-plugins_actions' ) ) ), '</a>', sprintf( '<a href="%s">', esc_url( __( 'https://docs.secupress.me/article/233-plugin-actions', 'secupress' ) ) ), '</a>', ); secupress_add_notice( $message, 'error', 'active-plugins-error' ); } add_action( 'admin_notices', 'secupress_check_default_login_slug_notice' ); /** * Display a notice if the login slug is still set to the default value "login". * * @since 2.5 * @author Julio Potier */ function secupress_check_default_login_slug_notice() { if ( ! current_user_can( secupress_get_capability() ) ) { return; } if ( ! secupress_is_submodule_active( 'users-login', 'move-login' ) ) { return; } $login_slug = secupress_get_module_option( 'move-login_slug-login', 'login', 'users-login' ); if ( 'login' === $login_slug ) { $settings_url = secupress_admin_url( 'modules', 'users-login' ); $message = sprintf( '<p>' . __( 'The login page slug is still set to its default value "%1$s". Please configure a custom slug in the %2$ssettings%3$s to secure your login page.', 'secupress' ) . '</p>', '<code>login</code>', sprintf( '<a href="%s">', esc_url( $settings_url ) ), '</a>' ); secupress_add_notice( $message, 'warning', 'default-login-slug' ); } } free/admin/options.php 0000644 00000005153 15174670627 0011004 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** --------------------------------------------------------------------------------------------- */ /** MODULES OPTIONS ============================================================================= */ /** --------------------------------------------------------------------------------------------- */ add_action( 'admin_init', 'secupress_register_all_settings' ); /** * Register all modules settings. * * @since 1.0 */ function secupress_register_all_settings() { $modules = secupress_get_modules(); if ( $modules ) { foreach ( $modules as $key => $module_data ) { secupress_register_setting( $key ); } } } add_action( 'update_option_home', 'secupress_scan_https_on_option_update', 10, 2 ); add_action( 'update_option_siteurl', 'secupress_scan_https_on_option_update', 10, 2 ); /** * Runs the HTTPS scanner on URL update * * @since 2.0 * @author Julio Potier **/ function secupress_scan_https_on_option_update( $old_value, $new_value ) { $old_value = parse_url( $old_value, PHP_URL_SCHEME ); $new_value = parse_url( $new_value, PHP_URL_SCHEME ); if ( $old_value !== $new_value ) { secupress_scanit( 'HTTPS' ); } } add_action( 'update_option_default_role', 'secupress_scan_subscription_on_option_update', 10, 2 ); /** * Runs the HTTPS scanner on URL update * * @since 2.0 * @author Julio Potier **/ function secupress_scan_subscription_on_option_update() { secupress_scanit( 'Subscription' ); } add_action( 'upgrader_process_complete', 'secupress_scan_update_plugins_themes_core', 10, 2 ); /** * Runs the update scanner on plugin, theme or core update * * @since 2.0 * @author Julio Potier **/ function secupress_scan_update_plugins_themes_core( $dummy, $hook_extra ) { if ( ! isset( $hook_extra['type'] ) ) { return; } switch ( $hook_extra['type'] ) { case 'plugin': secupress_scanit( 'Plugins_Update' ); break; case 'theme': secupress_scanit( 'Themes_Update' ); break; case 'core': secupress_scanit( 'Core_Update' ); break; } } register_activation_hook( 'woocommerce/woocommerce.php', 'secupress_scan_woocommerce_on_activation' ); /** * Runs the update scanner on Woocommerce activation * * @since 2.0 * @author Julio Potier **/ function secupress_scan_woocommerce_on_activation() { secupress_scanit( 'Woocommerce_Discloses' ); } register_activation_hook( 'sitepress-multilingual-cms/sitepress.php', 'secupress_scan_wpml_on_activation' ); /** * Runs the update scanner on WPML activation * * @since 2.0 * @author Julio Potier **/ function secupress_scan_wpml_on_activation() { secupress_scanit( 'Wpml_Discloses' ); } free/admin/multisite/centralize-blog-options.php 0000644 00000027176 15174670627 0016113 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** --------------------------------------------------------------------------------------------- */ /** ACTIVE PLUGINS AND THEMES =================================================================== */ /** --------------------------------------------------------------------------------------------- */ add_action( 'add_option_active_plugins', 'secupress_update_active_plugins_centralized_blog_option', 20, 2 ); add_action( 'update_option_active_plugins', 'secupress_update_active_plugins_centralized_blog_option', 20, 2 ); /** * Use a site option to store the active plugins of each site. * Each time a blog option is added or modified, we store the new value in our network option. * * @since 1.0 * * @param (mixed) $do_not_use The old option value or the name of the option (depending on the hook). * @param (mixed) $value The value of the option. */ function secupress_update_active_plugins_centralized_blog_option( $do_not_use, $value ) { $site_id = get_current_blog_id(); $plugins = get_site_option( 'secupress_active_plugins' ); // Don't go further until the first complete filling is done. if ( ! is_array( $plugins ) ) { return; } $value = $value ? array_fill_keys( $value, 1 ) : array(); $plugins[ $site_id ] = $value; update_site_option( 'secupress_active_plugins', $plugins ); } add_action( 'add_option_stylesheet', 'secupress_update_active_themes_centralized_blog_option', 20, 2 ); add_action( 'update_option_stylesheet', 'secupress_update_active_themes_centralized_blog_option', 20, 2 ); /** * Use a site option to store the active themes of each site. * Each time a blog option is added or modified, we store the new value in our network option. * * @since 1.0 * * @param (mixed) $do_not_use The old option value or the name of the option (depending on the hook). * @param (mixed) $value The value of the option. */ function secupress_update_active_themes_centralized_blog_option( $do_not_use, $value ) { $site_id = get_current_blog_id(); $themes = get_site_option( 'secupress_active_themes' ); // Don't go further until the first complete filling is done. if ( ! is_array( $themes ) ) { return; } $themes[ $site_id ] = $value; update_site_option( 'secupress_active_themes', $themes ); } add_action( 'add_option_default_role', 'secupress_update_default_role_centralized_blog_option', 20, 2 ); add_action( 'update_option_default_role', 'secupress_update_default_role_centralized_blog_option', 20, 2 ); /** * Use a site option to store the default user role of each site. * Each time a blog option is added or modified, we store the new value in our network option. * * @since 1.0 * * @param (mixed) $do_not_use The old option value or the name of the option (depending on the hook). * @param (mixed) $value The value of the option. */ function secupress_update_default_role_centralized_blog_option( $do_not_use, $value ) { $site_id = get_current_blog_id(); $roles = get_site_option( 'secupress_default_role' ); // Don't go further until the first complete filling is done. if ( ! is_array( $roles ) ) { return; } $roles[ $site_id ] = $value; update_site_option( 'secupress_default_role', $roles ); } add_action( 'delete_blog', 'secupress_delete_blog_from_centralized_blog_options', 20 ); /** * When a blog is deleted, remove the corresponding row from the site options. * * @since 1.0 * * @param (int) $blog_id The blog ID. */ function secupress_delete_blog_from_centralized_blog_options( $blog_id ) { $blog_id = (int) $blog_id; // Plugins. $plugins = get_site_option( 'secupress_active_plugins' ); if ( is_array( $plugins ) && isset( $plugins[ $blog_id ] ) ) { unset( $plugins[ $blog_id ] ); update_site_option( 'secupress_active_plugins', $plugins ); } // Themes. $themes = get_site_option( 'secupress_active_themes' ); if ( is_array( $themes ) && isset( $themes[ $blog_id ] ) ) { unset( $themes[ $blog_id ] ); update_site_option( 'secupress_active_themes', $themes ); } // Default user role. $themes = get_site_option( 'secupress_default_role' ); if ( is_array( $themes ) && isset( $themes[ $blog_id ] ) ) { unset( $themes[ $blog_id ] ); update_site_option( 'secupress_default_role', $themes ); } } /** --------------------------------------------------------------------------------------------- */ /** FILL IN THE FIRST VALUES ==================================================================== */ /** --------------------------------------------------------------------------------------------- */ add_action( 'load-toplevel_page_' . SECUPRESS_PLUGIN_SLUG . '_scanners', 'secupress_add_centralized_blog_options' ); /** * When the user reaches the scans page, display a message if our options need more results. * * @since 1.0 */ function secupress_add_centralized_blog_options() { if ( ! secupress_fill_centralized_blog_options() ) { return; } $href = urlencode( esc_url_raw( secupress_get_current_url( 'raw' ) ) ); $href = admin_url( 'admin-post.php?action=secupress-centralize-blog-options&_wp_http_referer=' . $href ); $href = wp_nonce_url( $href, 'secupress-centralize-blog-options' ); $message = sprintf( /** Translators: %s is a "click here" link. */ __( 'Your network is quite big. Before doing anything, some data must be set. Please %s.', 'secupress' ), '<a href="' . esc_url( $href ) . '" class="secupress-centralize-blog-options">' . __( 'click here', 'secupress' ) . '</a>' ); secupress_add_notice( $message, 'error', false ); } add_action( 'wp_ajax_secupress-centralize-blog-options', 'secupress_add_centralized_blog_options_admin_ajax_callback' ); /** * Add more results when the user clicks the link. * This is used when JS is enabled in the user's browser. * * @since 1.0 */ function secupress_add_centralized_blog_options_admin_ajax_callback() { global $wpdb; secupress_check_admin_referer( 'secupress-centralize-blog-options' ); secupress_check_user_capability(); if ( ! ( $count = secupress_fill_centralized_blog_options() ) ) { wp_send_json_success( false ); } $total = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT( blog_id ) FROM $wpdb->blogs WHERE site_id = %d", $wpdb->siteid ) ); $percent = ceil( $count * 100 / max( $total, 1 ) ); wp_send_json_success( $percent ); } add_action( 'admin_post_secupress-centralize-blog-options', 'secupress_add_centralized_blog_options_admin_post_callback' ); /** * Add more results when the user clicks the link. * This is used when JS is disabled in the user's browser: we display a small window with auto-refresh. * * @since 1.0 */ function secupress_add_centralized_blog_options_admin_post_callback() { global $wpdb; secupress_check_admin_referer( 'secupress-centralize-blog-options' ); secupress_check_user_capability(); if ( ! ( $count = secupress_fill_centralized_blog_options() ) ) { wp_safe_redirect( esc_url_raw( wp_get_referer() ) ); die(); } $total = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT( blog_id ) FROM $wpdb->blogs WHERE site_id = %d", $wpdb->siteid ) ); $percent = ceil( $count * 100 / max( $total, 1 ) ); $href = urlencode( esc_url_raw( wp_get_referer() ) ); $href = admin_url( 'admin-post.php?action=secupress-centralize-blog-options&_wp_http_referer=' . $href ); $href = wp_nonce_url( $href, 'secupress-centralize-blog-options' ); ob_start(); ?> <div class="wrap"> <p> <?php printf( /** Translators: %s is a "click here" link. */ __( 'If this page does not refresh automatically in 2 seconds, please %s.', 'secupress' ), /** For `wp_get_referer()` see the param `_wp_http_referer` in `secupress_add_centralized_blog_options()`. */ '<a href="' . esc_url( $href ) . '" class="secupress-centralize-blog-options">' . __( 'click here', 'secupress' ) . '</a>' ); ?> </p> <div class="progress-wrap"><div style="width:<?php echo $percent; ?>%" class="progress"><?php echo $percent; ?>%</div></div> </div> <?php $title = __( 'Setting new data…', 'secupress' ); $content = ob_get_contents(); $args = array( 'head' => '<meta http-equiv="refresh" content="1" />' ); ob_clean(); secupress_action_page( $title, $content, $args ); } /** --------------------------------------------------------------------------------------------- */ /** TOOLS ======================================================================================= */ /** --------------------------------------------------------------------------------------------- */ /** * First complete filling. * If the network has more than 250 sites, the options will be filled piece by piece. * * @since 1.0 * * @return (int|bool) Until there are no more results to add, return the number of sites set so far. Return false otherwise. */ function secupress_fill_centralized_blog_options() { global $wpdb; $plugins = get_site_option( 'secupress_active_plugins' ); // Don't go further if the first complete filling is already done and we don't need more results. if ( is_array( $plugins ) && empty( $plugins['offset'] ) ) { return false; } $themes = get_site_option( 'secupress_active_themes' ); $roles = get_site_option( 'secupress_default_role' ); $plugins = is_array( $plugins ) ? $plugins : array(); $themes = is_array( $themes ) ? $themes : array(); $roles = is_array( $roles ) ? $roles : array(); // Set the query boundaries. $offset = ! empty( $plugins['offset'] ) ? absint( $plugins['offset'] ) : 0; /** * Filter query step: the number of blogs from where we'll be fetch data. * * @since 1.0 * * @param (int) $step Default is 250. */ $step = apply_filters( 'secupress.multisite.fill_centralized_blog_options_step', 250 ); $step = absint( $step ); $limit = $offset * $step . ', ' . $step; $blogs = $wpdb->get_col( $wpdb->prepare( "SELECT blog_id FROM $wpdb->blogs WHERE site_id = %d LIMIT $limit", $wpdb->siteid ) ); // WPCS: unprepared SQL ok. // Nothing? Bail out. if ( ! $blogs ) { if ( isset( $plugins['offset'] ) ) { unset( $plugins['offset'] ); update_site_option( 'secupress_active_plugins', $plugins ); } return false; } foreach ( $blogs as $blog_id ) { $blog_id = (int) $blog_id; $table_prefix = $wpdb->get_blog_prefix( $blog_id ); $blog_actives = $wpdb->get_results( "SELECT option_name, option_value FROM {$table_prefix}options WHERE option_name = 'active_plugins' OR option_name = 'stylesheet' OR option_name = 'default_role'", OBJECT_K ); // WPCS: unprepared SQL ok. // Plugins. $plugins[ $blog_id ] = ! empty( $blog_actives['active_plugins']->option_value ) ? unserialize( $blog_actives['active_plugins']->option_value ) : array(); if ( $plugins[ $blog_id ] && is_array( $plugins[ $blog_id ] ) ) { $plugins[ $blog_id ] = array_fill_keys( $plugins[ $blog_id ], 1 ); } // Themes. $themes[ $blog_id ] = ! empty( $blog_actives['stylesheet']->option_value ) ? $blog_actives['stylesheet']->option_value : ''; // Default user role. $roles[ $blog_id ] = ! empty( $blog_actives['default_role']->option_value ) ? $blog_actives['default_role']->option_value : ''; } // We need more results (or we are "unlucky"). if ( count( $blogs ) === $step ) { // We temporarely store the last offset in the "active plugins" option. $plugins['offset'] = ( ++$offset ); // Update our options. update_site_option( 'secupress_active_plugins', $plugins ); update_site_option( 'secupress_active_themes', $themes ); update_site_option( 'secupress_default_role', $roles ); // Return the number of sites set so far. return count( $plugins ) - 1; } // Done! unset( $plugins['offset'] ); update_site_option( 'secupress_active_plugins', $plugins ); update_site_option( 'secupress_active_themes', $themes ); update_site_option( 'secupress_default_role', $roles ); return false; } free/admin/multisite/options.php 0000644 00000001755 15174670627 0013027 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Get scans et fixes results of sub-sites, organized by test and site ID. * It's a kind of `secupress_get_scan_results()` + `secupress_get_fix_results()` in one function, and for sub-sites. * The "scans et fixes of subsites" are related to the fixes that can't be done from the network admin if we are in a multisite installation. * * @since 1.0 * @since 1.3 Use multiple options instead of 1 option and multiple transients. * @author Grégory Viguier * * @return (array) The results, like: * array( * test_name_lower => array( * site_id => array( * 'scan' => array( * 'status' => 'bad', * 'msgs' => array( 202 => array( params ) ) * ), * 'fix' => array( * 'status' => 'cantfix', * 'msgs' => array( 303 => array( params ) ) * ) * ) * ) * ) */ function secupress_get_results_for_ms_scanner_fixes() { return SecuPress_Scanner_Results::get_sub_sites_results(); } free/admin/multisite/settings.php 0000644 00000021141 15174670627 0013163 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** --------------------------------------------------------------------------------------------- */ /** MULTISITE SETTINGS API ====================================================================== */ /** --------------------------------------------------------------------------------------------- */ add_filter( 'secupress_whitelist_network_options', 'secupress_network_option_update_filter' ); /** * Whitelist network options added with `secupress_register_setting()`. * * @since 1.0 * * @param (array) $options Other whitelisted options. * * @return (array) */ function secupress_network_option_update_filter( $options ) { $whitelist = secupress_cache_data( 'new_whitelist_network_options' ); if ( is_array( $whitelist ) ) { if ( function_exists( 'add_allowed_options' ) ) { // WP 5.5 $options = add_allowed_options( $whitelist, $options ); } else { $options = add_option_whitelist( $whitelist, $options ); } } return $options; } /** --------------------------------------------------------------------------------------------- */ /** SAVE SETTINGS ON FORM SUBMIT ================================================================ */ /** --------------------------------------------------------------------------------------------- */ add_action( 'admin_post_update', 'secupress_update_network_option_on_submit' ); /** * `options.php` does not handle network options. Let's use admin-post.php for multisite installations. * * @since 1.0 */ function secupress_update_network_option_on_submit() { $option_groups = array( 'secupress_global_settings' => 1 ); $modules = secupress_get_modules(); foreach ( $modules as $module => $atts ) { $option_groups[ "secupress_{$module}_settings" ] = 1; } if ( ! isset( $_POST['option_page'], $option_groups[ $_POST['option_page'] ] ) ) { // WPCS: CSRF ok. return; } $option_group = $_POST['option_page']; // WPCS: CSRF ok. secupress_check_admin_referer( $option_group . '-options' ); secupress_check_user_capability(); /** * Add network options to whitelist. * * @since 1.0 * * @param (array) $whitelist_options Network option names, grouped by option groups. By default an empty array. */ $whitelist_options = apply_filters( 'secupress_whitelist_network_options', array() ); if ( ! isset( $whitelist_options[ $option_group ] ) ) { wp_die( __( '<strong>Error</strong>: options page not found.', 'secupress' ) ); } $options = $whitelist_options[ $option_group ]; if ( $options ) { foreach ( $options as $option ) { $option = trim( $option ); $value = null; if ( isset( $_POST[ $option ] ) ) { $value = $_POST[ $option ]; if ( ! is_array( $value ) ) { $value = trim( $value ); } $value = wp_unslash( $value ); } update_site_option( $option, $value ); } } /** * Handle settings errors and return to options page. */ // If no settings errors were registered add a general 'updated' message. if ( ! count( secupress_get_settings_errors() ) ) { secupress_add_settings_error( 'general', 'settings_updated', __( 'Settings saved.' ), 'updated' ); } set_transient( 'settings_errors', secupress_get_settings_errors(), 30 ); /** * Redirect back to the settings page that was submitted. */ $goback = add_query_arg( 'settings-updated', 'true', wp_get_referer() ); wp_safe_redirect( esc_url_raw( $goback ) ); exit; } /** --------------------------------------------------------------------------------------------- */ /** ADMIN MENU + NOTICE ========================================================================= */ /** --------------------------------------------------------------------------------------------- */ add_action( 'admin_menu', 'secupress_create_subsite_menu' ); /** * Create the plugin menu item in sites. * Also display an admin notice. * * @since 1.0 */ function secupress_create_subsite_menu() { global $pagenow; if ( is_network_admin() || is_user_admin() || ! current_user_can( secupress_get_capability( true ) ) ) { return; } $site_id = get_current_blog_id(); $sites = secupress_get_results_for_ms_scanner_fixes(); $menu = false; if ( ! $sites ) { return; } foreach ( $sites as $site_data ) { if ( isset( $site_data[ $site_id ] ) ) { $menu = true; break; } } if ( ! $menu ) { return; } $cap = secupress_get_capability( true ); // Menu item. add_menu_page( SECUPRESS_PLUGIN_NAME, 'secupress', $cap, SECUPRESS_PLUGIN_SLUG . '_scanners', 'secupress_subsite_scanners', 'dashicons-shield-alt' ); // Display a notice for Administrators. if ( 'admin.php' !== $pagenow || empty( $_GET['page'] ) || SECUPRESS_PLUGIN_SLUG . '_scanners' !== $_GET['page'] ) { /** Translators: 1 is an URL, 2 is the plugin name. */ $message = sprintf( __( 'Some security issues must be fixed, please visit <a href="%1$s">%2$s’s page</a>.', 'secupress' ), esc_url( admin_url( 'admin.php?page=' . SECUPRESS_PLUGIN_SLUG . '_scanners' ) ), '<strong>' . SECUPRESS_PLUGIN_NAME . '</strong>' ); secupress_add_notice( $message, null, 'subsite-security-issues' ); } else { // The user is on the plugin page: make sure to not display the notice. secupress_dismiss_notice( 'subsite-security-issues' ); } } add_filter( 'secupress.notices.dismiss_capability', 'secupress_dismiss_multisite_notice_capability', 10, 2 ); /** * Our "security issues" notice must be shown to the site's Administrators: change the capability for the callback. * * @since 1.0 * * @param (string) $capacity Capability or user role. * @param (string) $notice_id The notice Identifier. * * @return (string) Capability or user role. */ function secupress_dismiss_multisite_notice_capability( $capacity, $notice_id ) { return 'subsite-security-issues' === $notice_id ? secupress_get_capability( true ) : $capacity; } add_action( 'secupress.multisite.empty_results_for_ms_scanner_fixes', 'secupress_remove_subsite_security_issues_notice_meta' ); /** * When all the site's fixes are done, remove the "dismissed notice" value from the users meta. * That way, the notice can be shown again later if needed (more fixes to do). * * @since 1.0 */ function secupress_remove_subsite_security_issues_notice_meta() { global $wpdb; // Get all Administrators that have dismissed our notice. $users = get_users( array( 'meta_key' => $wpdb->get_blog_prefix() . SecuPress_Admin_Notices::META_NAME, 'meta_value' => 'subsite-security-issues', 'meta_compare' => 'LIKE', 'fields' => 'ID', ) ); if ( ! $users ) { return; } // Remove the value from the user meta. foreach ( $users as $user_id ) { SecuPress_Admin_Notices::reinit( 'subsite-security-issues', $user_id ); } } /** --------------------------------------------------------------------------------------------- */ /** SCANS PAGE ================================================================================== */ /** --------------------------------------------------------------------------------------------- */ /** * Scanners page. * * @since 1.0 */ function secupress_subsite_scanners() { ?> <div class="wrap"> <?php secupress_admin_heading( __( 'Scanners', 'secupress' ) ); ?> <div class="secupress-wrapper"> <?php secupress_scanners_template(); ?> </div> </div> <?php } /** --------------------------------------------------------------------------------------------- */ /** ACCESSING THE SETTINGS PAGE WHEN IT'S NOT AVAILABLE ========================================= */ /** --------------------------------------------------------------------------------------------- */ add_action( 'admin_page_access_denied', 'secupress_settings_page_access_denied_message' ); /** * On each site when all fixes are done, the settings page is not available anymore. * If the user refreshes the page, a "You do not have sufficient permissions to access this page" message will be shown: we need to display a better message. * * @since 1.0 */ function secupress_settings_page_access_denied_message() { global $pagenow; if ( is_network_admin() || is_user_admin() || 'admin.php' !== $pagenow || empty( $_GET['page'] ) || SECUPRESS_PLUGIN_SLUG . '_scanners' !== $_GET['page'] ) { return; } if ( ! current_user_can( secupress_get_capability( true ) ) ) { return; } /** Translators: %s is a link to the dashboard. */ $message = __( 'Since there are no other fixes to be done, this page does not exist anymore.<br/>Go back to %s.', 'secupress' ); $link = '<a href="' . esc_url( admin_url() ) . '">' . __( 'Dashboard' ) . '</a>'; $title = __( 'Back to the Dashboard', 'secupress' ); // HTTP code 403: "Forbidden". secupress_die( sprintf( $message, $link ), $title, array( 'response' => 403, 'force_die' => true ) ); } free/admin/scanner-step-1.php 0000644 00000016460 15174670627 0012054 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); $scanned_items = secupress_get_scan_results(); $scanned_items = $scanned_items ? array_flip( array_keys( $scanned_items ) ) : array(); $secupress_tests_keys = array_flip( array_keys( SecuPress_Scanner_Results::get_scanners() ) ); $new_scans = array_diff_key( $secupress_tests_keys, $scanned_items ); $modules = secupress_get_modules(); $is_there_something_new = $new_scans ? reset( $new_scans ) !== false : false; $flag_first_iteration = true; // Build the "new scans" array. if ( $new_scans ) { foreach ( $new_scans as $key => $new_scan ) { $new_scans[ $key ] = str_replace( ' ', '_', ucwords( str_replace( '_', ' ', $key ) ) ); } } ?> <div id="secupress-tests" class="secupress-tests"> <?php foreach ( $secupress_tests as $module_name => $class_name_parts ) { $class_name_parts = array_combine( array_map( 'strtolower', $class_name_parts ), $class_name_parts ); if ( ! $is_subsite ) { foreach ( $class_name_parts as $option_name => $class_name_part ) { if ( ! file_exists( secupress_class_path( 'scan', $class_name_part ) ) ) { unset( $class_name_parts[ $option_name ] ); continue; } secupress_require_class( 'scan', $class_name_part ); } if ( $scanned_items ) { // For this module, order the scans by status: 'good', 'warning', 'bad', 'new'. $this_module_good_scans = array_intersect_key( $class_name_parts, $good_scans ); $this_module_bad_scans = array_intersect_key( $class_name_parts, $bad_scans ); $this_module_warning_scans = array_intersect_key( $class_name_parts, $warning_scans ); $class_name_parts = array_merge( $this_module_good_scans, $this_module_warning_scans, $this_module_bad_scans ); unset( $this_module_bad_scans, $this_module_warning_scans, $this_module_good_scans ); } } else { foreach ( $class_name_parts as $option_name => $class_name_part ) { // Display only scanners where we have a scan result or a fix to be done. if ( empty( $scanners[ $option_name ] ) && empty( $fixes[ $option_name ] ) || ! file_exists( secupress_class_path( 'scan', $option_name ) ) ) { unset( $class_name_parts[ $option_name ] ); continue; } secupress_require_class( 'scan', $class_name_part ); } } if ( $flag_first_iteration ) { ?> <div class="secupress-step-content-header secupress-flex secupress-flex-spaced"> <?php if ( $is_there_something_new && $scanned_items ) { $page_title = __( 'Discover the new security items to check.', 'secupress' ); } else { $page_title = __( 'List of the security items already analyzed', 'secupress' ); } ?> <p class="secupress-step-title"> <?php echo $page_title; ?> </p> <p class="secupress-rescan-actions"> <span class="screen-reader-text"><?php _e( 'Doubts? Try a new scan.', 'secupress' ); ?></span> <button class="secupress-button secupress-button-primary secupress-button-scan" type="button" data-nonce="<?php echo esc_attr( wp_create_nonce( 'secupress-update-oneclick-scan-date' ) ); ?>"> <span class="icon" aria-hidden="true"> <i class="secupress-icon-radar"></i> </span> <span class="text"> <?php _e( 'Scan website', 'secupress' ); ?> </span> <span class="secupress-progressbar-val" style="width:2%;"> <span class="secupress-progress-val-txt" aria-hidden="true">2 %</span> </span> </button> <?php if ( ! has_filter( 'secupress.scanner.scan-speed' ) ) { ?> <button class="hide-if-no-js secupress-button secupress-button-primary" id="secupress-button-scan-speed" type="button" data-nonce="<?php echo esc_attr( wp_create_nonce( 'secupress-set-scan-speed' ) ); ?>"> <span class="dashicons dashicons-arrow-down" aria-hidden="true"> </span> </button> <?php $allowed_values = [ 0 => 'max', 250 => 'normal', 1000 => 'low' ]; $value = secupress_get_option( 'scan-speed', 0 ); $value = isset( $allowed_values[ $value ] ) ? $value : 0; $value = apply_filters( 'secupress.scanner.scan-speed', $value ); ?> <div class="hidden" id="secupress-scan-speed"> <ul> <li><label><input type="radio" name="secupress-scan-speed" value="max" <?php checked( $value, 0 ); ?>> <?php _ex( 'Max Speed (def.)', 'scanner', 'secupress' ); ?></label></li> <li><label><input type="radio" name="secupress-scan-speed" value="normal" <?php checked( $value, 250 ); ?>> <?php _ex( 'Normal Speed', 'scanner', 'secupress' ); ?></label></li> <li><label><input type="radio" name="secupress-scan-speed" value="low" <?php checked( $value, 1000 ); ?>> <?php _ex( 'Low Speed', 'scanner', 'secupress' ); ?></label></li> <span class="dashicons dashicons-editor-help"></span> <a href="<?php _e( 'https://docs.secupress.me/article/156-whats-this-speed-thing', 'secupress' ); ?>" target="_blank"><?php _e( 'What’s this speed thing?', 'secupress' ); ?></a> </ul> </div> <?php } ?> </p> <p> <a href="<?php echo secupress_admin_url( 'scanners' ); ?>&step=2" class="secupress-button secupress-button-tertiary shadow"> <span class="icon"> <i class="secupress-icon-wrench" aria-hidden="true"></i> </span> <span class="text"><?php _e( 'Next step', 'secupress' ); ?></span> </a> </p> </div><!-- .secupress-step-content-header --> <?php if ( $is_there_something_new && $scanned_items ) { require_once( SECUPRESS_INC_PATH . 'admin/scanner-step-1-new.php' ); } } $is_there_something_new = false; require( SECUPRESS_INC_PATH . 'admin/scanner-step-1-all.php' ); $flag_first_iteration = false; } // Eo foreach $secupress_tests. ?> </div><!-- .secupress-tests --> <div class="secupress-step-content-footer secupress-flex secupress-flex-top secupress-flex-spaced" id="secupress-step-content-footer"> <p> <?php if ( secupress_is_pro() ) { ?> <a href="<?php echo esc_url( wp_nonce_url( admin_url( 'admin-post.php?action=secupress_export_pdf' ), 'secupress_export_pdf' ) ); ?>" title="<?php esc_attr_e( 'Export this report as PDF file.', 'secupress' ); ?>" class="secupress-button shadow"> <span class="icon"> <i class="secupress-icon-file-pdf-o" aria-hidden="true"></i> </span> <span class="text"> <?php _e( 'Export as PDF', 'secupress' ); ?> </span> </a> <?php } else { ?> <a href="<?php echo esc_url( secupress_admin_url( 'get-pro' ) ) ?>" title="<?php esc_attr_e( 'Get the Pro Version to export this report as PDF file.', 'secupress' ); ?>" target="_blank" class="secupress-button disabled shadow"> <span class="icon"> <i class="secupress-icon-file-pdf-o" aria-hidden="true"></i> </span> <span class="text"> <?php _e( 'Export as PDF', 'secupress' ); ?> </span> </a> <br> <span class="secupress-get-pro-version"> <?php printf( __( 'Available in <a href="%s" target="_blank">Pro Version</a>', 'secupress' ), esc_url( secupress_admin_url( 'get-pro' ) ) ); ?> </span> <?php } ?> </p> <p> <a href="<?php echo secupress_admin_url( 'scanners' ); ?>&step=2" class="secupress-button secupress-button-tertiary shadow"> <span class="icon"> <i class="secupress-icon-wrench" aria-hidden="true"></i> </span> <span class="text"><?php _e( 'Next step', 'secupress' ); ?></span> </a> </p> </div> free/admin/settings.php 0000644 00000132444 15174670627 0011155 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** --------------------------------------------------------------------------------------------- */ /** CSS, JS, FOOTER ============================================================================= */ /** --------------------------------------------------------------------------------------------- */ add_action( 'doing_dark_mode', 'secupress_add_settings_scripts_for_dark_mode', 11 ); /** * Add some CSS for Dark Mode * * @since 1.4.7 * */ function secupress_add_settings_scripts_for_dark_mode() { $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'; $version = $suffix ? SECUPRESS_VERSION : time(); // SecuPress Dark Mode wp_enqueue_style( 'secupress-dark-mode', SECUPRESS_ADMIN_CSS_URL . 'secupress-dark-mode' . $suffix . '.css', array( 'secupress-wordpress-css' ), $version ); } /* add_action( 'admin_footer-plugins.php', 'secupress_add_deactivation_form' ); add_action( 'admin_footer-plugins-network.php', 'secupress_add_deactivation_form' ); // Removed in 2.1, for now. */ /** * Onclude the modal form for deactivation feedback, only if transient is not set * * @since 2.0 * @author Julio Potier **/ function secupress_add_deactivation_form() { $tr = get_site_transient( 'secupress-deactivation-form' ); if ( ! $tr && ! secupress_is_white_label() && ( ! function_exists( 'wp_get_environment_type' ) || 'production' === wp_get_environment_type() ) ) { include( SECUPRESS_ADMIN_PATH . 'modal.php' ); } } add_action( 'admin_enqueue_scripts', 'secupress_add_settings_scripts', 10 ); /** * Add some CSS and JS to our settings pages. * * @since 1.0 * * @param (string) $hook_suffix The current admin page. */ function secupress_add_settings_scripts( $hook_suffix ) { $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'; $version = $suffix ? SECUPRESS_VERSION : time(); $css_depts = array(); $js_depts = array( 'jquery' ); // Deactivation Modal removed in 2.1 for now // if ( ! function_exists( 'wp_get_environment_type' ) || 'production' === wp_get_environment_type() ) { // if ( 'plugins.php' === $hook_suffix || 'plugins-network.php' === $hook_suffix ) { // wp_enqueue_style( 'secupress-modal', SECUPRESS_ADMIN_CSS_URL . 'secupress-modal' . $suffix . '.css', null, SECUPRESS_VERSION ); // wp_enqueue_script( 'secupress-modal', SECUPRESS_ADMIN_JS_URL . 'secupress-modal' . $suffix . '.js', null, SECUPRESS_VERSION, true ); // } // } // Sweet Alert. if ( SECUPRESS_PLUGIN_SLUG . '_page_' . SECUPRESS_PLUGIN_SLUG . '_modules' === $hook_suffix || 'toplevel_page_' . SECUPRESS_PLUGIN_SLUG . '_scanners' === $hook_suffix ) { // CSS. $css_depts = array( 'wpmedia-css-sweetalert2' ); wp_enqueue_style( 'wpmedia-css-sweetalert2', SECUPRESS_ADMIN_CSS_URL . 'sweetalert2' . $suffix . '.css', array(), '1.3.4' ); // JS. $js_depts = array( 'jquery', 'wpmedia-js-sweetalert2' ); wp_enqueue_script( 'wpmedia-js-sweetalert2', SECUPRESS_ADMIN_JS_URL . 'sweetalert2' . $suffix . '.js', array(), '1.3.4', true ); } // WordPress Common CSS. wp_enqueue_style( 'secupress-wordpress-css', SECUPRESS_ADMIN_CSS_URL . 'secupress-wordpress' . $suffix . '.css', $css_depts, $version ); // WordPress Common JS. wp_enqueue_script( 'secupress-wordpress-js', SECUPRESS_ADMIN_JS_URL . 'secupress-wordpress' . $suffix . '.js', $js_depts, $version, true ); $localize_wp = array( 'isPro' => (int) secupress_is_pro(), 'confirmText' => __( 'OK', 'secupress' ), 'cancelText' => _x( 'Cancel', 'verb', 'secupress' ), ); wp_localize_script( 'secupress-wordpress-js', 'SecuPressi18n', $localize_wp ); // Dashboard widget. if ( 'index.php' === $hook_suffix && current_user_can( secupress_get_capability() ) ) { $chart_months = get_user_meta( get_current_user_id(), 'secupress_attacks_widget_chart_months', true ); $chart_months = ! $chart_months ? 6 : (int) $chart_months; if ( $chart_months > 0 ) { wp_enqueue_script( 'secupress-chartjs', SECUPRESS_ADMIN_JS_URL . 'chart' . $suffix . '.js', array(), '1.0.2.1', true ); wp_enqueue_script( 'secupress-widget-js', SECUPRESS_ADMIN_JS_URL . 'secupress-widget' . $suffix . '.js', array( 'jquery', 'secupress-chartjs' ), $version, true ); // Prepare and localize chart data $chart_data = secupress_prepare_widget_chart_data(); wp_localize_script( 'secupress-widget-js', 'SecuPressi18nWidget', array( 'chartData' => $chart_data, ) ); } } $pages = array( 'toplevel_page_' . SECUPRESS_PLUGIN_SLUG . '_scanners' => 1, SECUPRESS_PLUGIN_SLUG . '_page_' . SECUPRESS_PLUGIN_SLUG . '_modules' => 1, SECUPRESS_PLUGIN_SLUG . '_page_' . SECUPRESS_PLUGIN_SLUG . '_logs' => 1, ); SecuPress_Admin_Pointers::enqueue_scripts( $hook_suffix ); if ( ! isset( $pages[ $hook_suffix ] ) ) { return; } // SecuPress Common CSS. wp_enqueue_style( 'secupress-common-css', SECUPRESS_ADMIN_CSS_URL . 'secupress-common' . $suffix . '.css', array( 'secupress-wordpress-css' ), $version ); // WordPress Common JS. wp_enqueue_script( 'secupress-common-js', SECUPRESS_ADMIN_JS_URL . 'secupress-common' . $suffix . '.js', array( 'secupress-wordpress-js' ), $version, true ); wp_localize_script( 'secupress-common-js', 'SecuPressi18nCommon', array( 'confirmText' => __( 'OK', 'secupress' ), 'cancelText' => _x( 'Cancel', 'verb', 'secupress' ), 'closeText' => _x( 'Close', 'verb', 'secupress' ), ) ); // Settings page. if ( SECUPRESS_PLUGIN_SLUG . '_page_' . SECUPRESS_PLUGIN_SLUG . '_settings' === $hook_suffix ) { // CSS. wp_enqueue_style( 'secupress-settings-css', SECUPRESS_ADMIN_CSS_URL . 'secupress-settings' . $suffix . '.css', array( 'secupress-common-css' ), $version ); } // Modules page. elseif ( SECUPRESS_PLUGIN_SLUG . '_page_' . SECUPRESS_PLUGIN_SLUG . '_modules' === $hook_suffix ) { // CSS. wp_enqueue_style( 'secupress-modules-css', SECUPRESS_ADMIN_CSS_URL . 'secupress-modules' . $suffix . '.css', array( 'secupress-common-css' ), $version ); // JS. wp_enqueue_script( 'secupress-modules-js', SECUPRESS_ADMIN_JS_URL . 'secupress-modules' . $suffix . '.js', array( 'secupress-common-js' ), $version, true ); $already_scanned = array_filter( (array) get_site_option( SECUPRESS_SCAN_TIMES ) ) ? 1 : 0; $file_monitoring_running = 'off'; $move_login_nonce = null; if ( ! empty( $_GET['module'] ) ) { if ( 'file-system' === $_GET['module'] && function_exists( 'secupress_file_monitoring_get_instance' ) ) { $file_monitoring_running = secupress_file_monitoring_get_instance()->is_monitoring_running() ? 'on' : 'off'; if ( 'on' === $file_monitoring_running ) { echo '<meta http-equiv="refresh" content="30;url=' . secupress_admin_url( 'modules', 'file-system' ) . '" />'; } } elseif ( 'users-login' === $_GET['module'] ) { $move_login_nonce = wp_create_nonce( 'sanitize_move_login_slug' ); } } wp_localize_script( 'secupress-modules-js', 'SecuPressi18nModules', array( // Roles. 'selectOneRoleMinimum' => __( 'Select as least 1 role', 'secupress' ), // Firewall. 'selectOneOptMinimum' => __( 'Select as least 1 option', 'secupress' ), // Generic. 'confirmTitle' => __( 'Are you sure?', 'secupress' ), 'confirmText' => __( 'OK', 'secupress' ), 'cancelText' => _x( 'Cancel', 'verb', 'secupress' ), 'error' => __( 'Error', 'secupress' ), 'unknownError' => __( 'Unknown error.', 'secupress' ), 'delete' => _x( 'Delete', 'verb', 'secupress' ), 'done' => __( 'Done!', 'secupress' ), // Backups. 'confirmDeleteBackups' => __( 'You are about to delete all your backups.', 'secupress' ), 'yesDeleteAll' => __( 'Yes, delete all backups', 'secupress' ), 'deleteAllImpossible' => __( 'Impossible to delete all backups.', 'secupress' ), 'deletingAllText' => __( 'Deleting all backups…', 'secupress' ), 'deletedAllText' => __( 'All backups deleted', 'secupress' ), // Backup. 'confirmDeleteBackup' => __( 'You are about to delete a backup.', 'secupress' ), 'yesDeleteOne' => __( 'Yes, delete this backup', 'secupress' ), 'deleteOneImpossible' => __( 'Impossible to delete this backup.', 'secupress' ), 'deletingOneText' => __( 'Deleting Backup…', 'secupress' ), 'deletedOneText' => __( 'Backup deleted', 'secupress' ), // Backup actions. 'backupImpossible' => __( 'Impossible to backup.', 'secupress' ), 'backupingText' => __( 'Backuping…', 'secupress' ), 'backupedText' => __( 'Backup done', 'secupress' ), // Ban/Whitelist IPs. 'noBannedIPs' => __( 'Empty disallowed IP list.', 'secupress' ), 'noWhitelistIPs' => __( 'Empty allowed IP list.', 'secupress' ), 'IPnotFound' => __( 'IP not found.', 'secupress' ), 'IPremoved' => __( 'IP removed.', 'secupress' ), 'searchResults' => _x( 'See search result below.', 'adjective', 'secupress' ), 'searchReset' => _x( 'Search reset.', 'adjective', 'secupress' ), // First scan. 'alreadyScanned' => $already_scanned, 'firstScanTitle' => __( 'Before setting modules,<br>launch your first scan.', 'secupress' ), 'firstScanText' => __( 'It’s an automatic process that will help you secure your website.', 'secupress' ), 'firstScanButton' => __( 'Scan my website', 'secupress' ), 'firstScanURL' => esc_url( wp_nonce_url( secupress_admin_url( 'scanners' ), 'first_oneclick-scan' ) ) . '&oneclick-scan=1', 'firstScanImage' => SECUPRESS_ADMIN_IMAGES_URL . 'icon-radar.png', // Expand Textareas. 'expandTextOpen' => __( 'Show More', 'secupress' ), 'expandTextClose' => _x( 'Close', 'verb', 'secupress' ), // Malware Scan. 'malwareScanStatus' => $file_monitoring_running, 'malwareScanError' => '<span class="dashicons dashicons-dismiss"></span> ' . __( 'AJAX Security Error: Please reload the page manually.', 'secupress' ), 'MalwareScanURI' => secupress_admin_url( 'modules', 'file-system' ), 'malwareUpdateOK' => __( 'Malware databases updated successfully.', 'secupress' ), 'malwareUpdateKO' => __( 'Impossible to update the malware databases. Check your license status.', 'secupress' ), // Move Login. 'moveLoginNonce' => $move_login_nonce, // Module Search. 'searchNonce' => wp_create_nonce( 'secupress_search' ), 'version' => SECUPRESS_VERSION, // Misc. 'resetDefault' => __( 'This will reset the setting values to default for this module.', 'secupress' ), 'regenKeys' => sprintf( __( 'This will change the %d security keys for your installation.<br>You may need to sign back in.', 'secupress' ), 10 ), // Delete files. 'confirmDeleteFiles' => __( 'Are you sure you want to delete the selected files?<br>This action cannot be undone.', 'secupress-pro' ), 'yesDeleteFiles' => __( 'Yes, delete files', 'secupress-pro' ), ) ); } // Scanners page. elseif ( 'toplevel_page_' . SECUPRESS_PLUGIN_SLUG . '_scanners' === $hook_suffix ) { // CSS. wp_enqueue_style( 'secupress-scanner-css', SECUPRESS_ADMIN_CSS_URL . 'secupress-scanner' . $suffix . '.css', array( 'secupress-common-css' ), $version ); // JS. $depts = array( 'secupress-common-js' ); $is_main = is_network_admin() || ! is_multisite(); if ( $is_main ) { $depts[] = 'secupress-chartjs'; $counts = secupress_get_scanner_counts(); wp_enqueue_script( 'secupress-chartjs', SECUPRESS_ADMIN_JS_URL . 'chart' . $suffix . '.js', array(), '1.0.2.1', true ); wp_localize_script( 'secupress-chartjs', 'SecuPressi18nChart', array( 'good' => array( 'value' => $counts['good'], 'text' => _x( 'Good', 'scan result', 'secupress' ) ), 'warning' => array( 'value' => $counts['warning'], 'text' => _x( 'Warning', 'scan result', 'secupress' ) ), 'bad' => array( 'value' => $counts['bad'], 'text' => _x( 'Bad', 'scan result', 'secupress' ) ), 'notscannedyet' => array( 'value' => $counts['notscannedyet'], 'text' => _x( 'Not Scanned Yet', 'scan result', 'secupress' ) ), ) ); } wp_enqueue_script( 'secupress-scanner-js', SECUPRESS_ADMIN_JS_URL . 'secupress-scanner' . $suffix . '.js', $depts, $version, true ); $localize = array( 'pluginSlug' => SECUPRESS_PLUGIN_SLUG, 'step' => $is_main ? secupress_get_scanner_pagination() : 0, 'confirmText' => __( 'OK', 'secupress' ), 'cancelText' => _x( 'Cancel', 'verb', 'secupress' ), 'error' => __( 'Error', 'secupress' ), 'fixed' => __( 'Fixed', 'secupress' ), 'fixedPartial' => __( 'Partially fixed', 'secupress' ), 'notFixed' => __( 'Not fixed', 'secupress' ), 'fixit' => __( 'Fix it', 'secupress' ), 'oneManualFix' => __( 'One fix requires your intervention.', 'secupress' ), 'fixInProgress' => __( 'Fix in progress…', 'secupress' ), 'someManualFixes' => __( 'Some fixes require your intervention.', 'secupress' ), 'spinnerUrl' => admin_url( 'images/wpspin_light-2x.gif' ), 'reScan' => _x( 'Scan', 'verb', 'secupress' ), 'scanDetails' => _x( 'Scan Details', 'noun', 'secupress' ), 'fixDetails' => _x( 'Fix Details', 'verb', 'secupress' ), 'firstScanURL' => esc_url( wp_nonce_url( secupress_admin_url( 'scanners' ), 'first_oneclick-scan' ) ) . '&oneclick-scan=1', 'a11y' => array( 'scanEnded' => __( 'Security scan just finished.', 'secupress' ), 'bulkFixStart' => __( 'Currently fixing…', 'secupress' ) . ' ' . __( 'Please wait until fixing is complete.', 'secupress' ), ), 'comingSoon' => __( 'Coming Soon', 'secupress' ), 'docNotReady' => __( 'The documentation is actually under construction, thank you for your patience.', 'secupress' ), 'offset' => (int) apply_filters( 'secupress.scanner.scan-speed', (int) secupress_get_option( 'scan-speed', 0 ) ), ); if ( $is_main ) { $localize['i18nNonce'] = wp_create_nonce( 'secupress-get-scan-counters' ); } if ( ! empty( $_GET['oneclick-scan'] ) && ! empty( $_GET['_wpnonce'] ) && wp_verify_nonce( $_GET['_wpnonce'], 'first_oneclick-scan' ) && current_user_can( secupress_get_capability() ) ) { $localize['firstOneClickScan'] = 1; $_SERVER['REQUEST_URI'] = remove_query_arg( array( '_wpnonce', 'oneclick-scan' ) ); } wp_localize_script( 'secupress-scanner-js', 'SecuPressi18nScanner', $localize ); } // Logs page. elseif ( SECUPRESS_PLUGIN_SLUG . '_page_' . SECUPRESS_PLUGIN_SLUG . '_logs' === $hook_suffix ) { // CSS. wp_enqueue_style( 'secupress-logs-css', SECUPRESS_ADMIN_CSS_URL . 'secupress-logs' . $suffix . '.css', array( 'secupress-common-css' ), $version ); wp_enqueue_style( 'secupress-modules-css', SECUPRESS_ADMIN_CSS_URL . 'secupress-modules' . $suffix . '.css', array( 'secupress-common-css' ), $version ); wp_enqueue_script( 'secupress-logs-js', SECUPRESS_ADMIN_JS_URL . 'secupress-logs' . $suffix . '.js', array( 'jquery-ui-slider' ), $version ); $localize = [ 'steps' => secupress_get_http_logs_limits() ]; wp_localize_script( 'secupress-logs-js', 'SecuPressi18nLogs', $localize ); add_thickbox(); } } /** --------------------------------------------------------------------------------------------- */ /** PLUGINS LIST ================================================================================ */ /** --------------------------------------------------------------------------------------------- */ add_filter( ( is_multisite() ? 'network_admin_' : '' ) . 'plugin_action_links_' . plugin_basename( SECUPRESS_FILE ), 'secupress_settings_action_links' ); /** * Add links to the plugin row. * * @since 2.2.6 Add links for multisite, FINALLY! * @since 2.0 Add my license link * @since 1.0 * * @param (array) $actions An array of links. * * @return (array) The array of links + our links. */ function secupress_settings_action_links( $actions ) { if ( ! secupress_is_white_label() ) { array_unshift( $actions, sprintf( '<a href="%s">%s</a>', esc_url( trailingslashit( set_url_scheme( SECUPRESS_WEB_MAIN, 'https' ) ) . _x( 'support', 'link to website (Only FR or EN!)', 'secupress' ) ), _x( 'Support', 'noon', 'secupress' ) ) ); array_unshift( $actions, sprintf( '<a href="%s">%s</a>', esc_url( __( 'https://docs.secupress.me/', 'secupress' ) ), __( 'Docs', 'secupress' ) ) ); } if ( secupress_has_pro() && ! secupress_is_pro() ) { // Pro installed but not yet licence activated. array_unshift( $actions, sprintf( '<a href="%s">%s</a>', esc_url( secupress_admin_url( 'modules#module-secupress_display_apikey_options' ) ), '<b style="font-variant:small-caps">' . __( 'Add my license', 'secupress' ) . '</b>' ) ); } else { array_unshift( $actions, sprintf( '<a href="%s">%s</a>', esc_url( secupress_admin_url( 'modules' ) ), __( 'Settings' ) ) ); // Let WP i18n here. } return $actions; } /** --------------------------------------------------------------------------------------------- */ /** ADMIN MENU ================================================================================== */ /** --------------------------------------------------------------------------------------------- */ add_action( ( is_multisite() ? 'network_' : '' ) . 'admin_menu', 'secupress_create_menus' ); /** * Create the plugin menu and submenus. * * @since 1.0 */ function secupress_create_menus() { global $menu, $submenu; // Add a counter of scans with bad result. $cap = secupress_get_capability(); if ( ! current_user_can( $cap ) ) { return; } $count = sprintf( ' <span class="update-plugins count-%1$d"><span class="update-count">%1$d</span></span>', secupress_get_scanner_counts( 'bad' ) ); // Main menu item. add_menu_page( SECUPRESS_PLUGIN_NAME, SECUPRESS_PLUGIN_NAME, $cap, SECUPRESS_PLUGIN_SLUG . '_scanners', 'secupress_scanners', 'dashicons-shield-alt' ); // Sub-menus. add_submenu_page( SECUPRESS_PLUGIN_SLUG . '_scanners', __( 'Scanners', 'secupress' ), __( 'Scanners', 'secupress' ) . $count, $cap, SECUPRESS_PLUGIN_SLUG . '_scanners', 'secupress_scanners' ); add_submenu_page( SECUPRESS_PLUGIN_SLUG . '_scanners', __( 'Modules', 'secupress' ), __( 'Modules', 'secupress' ), $cap, SECUPRESS_PLUGIN_SLUG . '_modules', 'secupress_modules' ); if ( ! secupress_is_white_label() ) { $title = __( 'More Security', 'secupress' ); if ( secupress_has_pro() ) { $title = __( 'Add my license', 'secupress' ); } if ( ! secupress_is_pro() ) { add_submenu_page( SECUPRESS_PLUGIN_SLUG . '_scanners', $title, $title, $cap, '__return_false', '__return_false' ); } } // Fix `add_menu_page()` nonsense. end( $menu ); $key = key( $menu ); $menu[ $key ][0] = SECUPRESS_PLUGIN_NAME . $count; // Fix `add_submenu_page()` URL. if ( ! secupress_is_pro() ) { end( $submenu ); $key = key( $submenu ); $url = secupress_has_pro() ? esc_url( secupress_admin_url( 'modules' ) . '#module-secupress_display_apikey_options' ) : esc_url( secupress_admin_url( 'get-pro' ) ); $submenu[ $key ][ count( $submenu[ $key ] ) -1 ] = array( $title, $cap, $url, $title ); } } /** --------------------------------------------------------------------------------------------- */ /** SETTINGS PAGES ============================================================================== */ /** --------------------------------------------------------------------------------------------- */ /** * Settings page. * * @since 1.0 */ function secupress_global_settings() { if ( ! class_exists( 'SecuPress_Settings' ) ) { secupress_require_class( 'settings' ); } $class_name = 'SecuPress_Settings_Global'; if ( ! class_exists( $class_name ) ) { secupress_require_class( 'settings', 'global' ); } if ( secupress_is_pro() ) { $class_name = 'SecuPress_Pro_Settings_Global'; if ( ! class_exists( $class_name ) ) { secupress_pro_require_class( 'settings', 'global' ); } } $class_name::get_instance()->print_page(); } /** * Modules page. * * @since 1.0 */ function secupress_modules() { if ( ! class_exists( 'SecuPress_Settings' ) ) { secupress_require_class( 'settings' ); } if ( ! class_exists( 'SecuPress_Settings_Modules' ) ) { secupress_require_class( 'settings', 'modules' ); } SecuPress_Settings_Modules::get_instance()->print_page(); } /** * Scanners page. * * @since 1.0 */ function secupress_scanners() { $counts = secupress_get_scanner_counts(); $items = array_filter( (array) get_site_option( SECUPRESS_SCAN_TIMES ) ); $reports = array(); $last_report = '—'; $time_offset = get_option( 'gmt_offset' ) * HOUR_IN_SECONDS; $use_grade = secupress_show_grade_system(); if ( $items ) { $last_percent = -1; foreach ( $items as $item ) { $reports[] = secupress_formate_latest_scans_list_item( $item, $last_percent ); $last_percent = $item['percent']; } $last_report = end( $items ); $last_report = date_i18n( _x( 'M dS, Y \a\t h:ia', 'Latest scans', 'secupress' ), $last_report['time'] + $time_offset ); } if ( isset( $_GET['step'] ) && '1' === $_GET['step'] ) { secupress_set_old_report(); } $currently_scanning_text = ' <span aria-hidden="true" class="secupress-second-title">' . __( 'Currently scanning', 'secupress' ) . '</span> <span class="secupress-scanned-items"> ' . sprintf( __( '%1$s / %2$s points' , 'secupress' ), '<span class="secupress-scanned-current">0</span>', '<span class="secupress-scanned-total">1</span>' ) . ' </span>'; ?> <div class="wrap"> <?php secupress_admin_heading( __( 'Scanners', 'secupress' ) ); ?> <div class="secupress-wrapper"> <div class="secupress-section-dark secupress-scanners-header<?php echo $reports ? '' : ' secupress-not-scanned-yet'; ?>"> <div class="secupress-heading secupress-flex secupress-wrap"> <div class="secupress-logo-block secupress-flex"> <div class="secupress-lb-logo"> <?php echo secupress_get_logo( array( 'width' => 59 ) ); ?> </div> <div class="secupress-lb-name"> <p class="secupress-lb-title"> <?php echo secupress_get_logo_word( array( 'width' => 98, 'height' => 23 ) ); ?> </p> </div> </div> <?php if ( ! $reports ) { ?> <div class="secupress-col-text"> <p class="secupress-text-medium"><?php _e( 'First scan', 'secupress' ); ?></p> <p><?php _e( 'Here’s how it’s going to work', 'secupress' ); ?></p> </div> <?php } ?> <p class="secupress-label-with-icon secupress-last-scan-result<?php if ( ! $use_grade ) { echo ' hidden'; } ?>"> <i class="secupress-icon-secupress" aria-hidden="true"></i> <span class="secupress-upper"><?php _ex( 'Scan results', 'noon', 'secupress' ); ?></span> <span class="secupress-primary"><?php echo $last_report; ?></span> </p> <p class="secupress-text-end hide-if-no-js"> <a href="#secupress-more-info" class="secupress-link-icon secupress-open-moreinfo<?php echo $reports ? '' : ' secupress-activated dont-trigger-hide'; ?>" data-trigger="slidedown" data-target="secupress-more-info"> <span class="icon" aria-hidden="true"> <i class="secupress-icon-info"></i> </span> <span class="text"> <?php _e( 'How does it work?', 'secupress' ); ?> </span> </a> </p> </div><!-- .secupress-heading --> <?php if ( ( secupress_get_scanner_pagination() === 1 || secupress_get_scanner_pagination() === 4 ) ) { ?> <div class="secupress-scan-header-main secupress-flex"> <?php if ( $use_grade ) { ?> <div id="sp-tab-scans" class="secupress-tabs-contents secupress-flex"> <div id="secupress-scan" class="secupress-tab-content" role="tabpanel" aria-labelledby="secupress-l-scan"> <div class="secupress-flex secupress-chart"> <div class="secupress-chart-container"> <canvas class="secupress-chartjs" id="status_chart" width="180" height="180"></canvas> <div class="secupress-score"><?php echo $counts['letter']; ?></div> </div> <div class="secupress-chart-legends-n-note"> <div class="secupress-scan-infos"> <p class="secupress-score-text secupress-text-big secupress-m0"> <?php echo $counts['text']; ?> </p> <p class="secupress-score secupress-score-subtext secupress-m0"><?php echo $counts['subtext']; ?></p> </div> <ul class="secupress-chart-legend hide-if-no-js"> <li class="status-good" data-status="good"> <span class="secupress-carret"></span> <?php _ex( 'Good', 'scan result', 'secupress' ); ?> <span class="secupress-count-good"></span> </li> <?php if ( $counts['warning'] > 0 ) : ?> <li class="status-warning" data-status="warning"> <span class="secupress-carret"></span> <?php _ex( 'Pending', 'scan result', 'secupress' ); ?> <span class="secupress-count-warning"></span> </li> <?php endif; ?> <li class="status-bad" data-status="bad"> <span class="secupress-carret"></span> <?php _ex( 'Bad', 'scan result', 'secupress' ); ?> <span class="secupress-count-bad"></span> </li> <?php if ( $counts['notscannedyet'] > 0 ) : ?> <li class="status-notscannedyet" data-status="notscannedyet"> <span class="secupress-carret"></span> <?php _ex( 'New Scan', 'scan result', 'secupress' ); ?> <span class="secupress-count-notscannedyet"></span> </li> <?php endif; ?> </ul><!-- .secupress-chart-legend --> <?php if ( ! secupress_is_white_label() ) { ?> <div id="tweeterA" class="hidden"> <p> <q> <?php /** Translators: %s is the plugin name */ $quote = sprintf( __( 'Wow! My website just got an %s grade for security using @SecuPress, what about yours?', 'secupress' ), secupress_get_scanner_counts( 'grade' ) ); // echo and not _e() because we need the quote later again. echo $quote; ?> </q> </p> <a class="secupress-button secupress-button-mini" target="_blank" title="<?php esc_attr_e( 'Open in a new window.', 'secupress' ); ?>" href="https://twitter.com/intent/tweet?url=<?php echo rawurlencode( 'https://secupress.me' ); ?>&text=<?php echo rawurlencode( html_entity_decode( $quote ) ); ?>"> <span class="icon" aria-hidden="true"><span class="dashicons dashicons-twitter"></span></span> <span class="text"><?php esc_html_e( 'Tweet this', 'secupress' ); ?></span> </a> </div><!-- #tweeterA --> <?php } ?> </div><!-- .secupress-chart-legends-n-note --> </div><!-- .secupress-chart.secupress-flex --> </div><!-- .secupress-tab-content --> <div id="secupress-latest" class="secupress-tab-content hide-if-js" role="tabpanel" aria-labelledby="secupress-l-latest"> <h3 class="secupress-text-medium hide-if-js"><?php _e( 'Your last scans', 'secupress' ); ?></h3> <div class="secupress-latest-list"> <ul class="secupress-reports-list"> <?php if ( (bool) $reports ) { echo implode( "\n", $reports ); } else { echo '<li class="secupress-empty"><em>' . __( 'You have no other reports for now.', 'secupress' ) . "</em></li>\n"; } ?> </ul> </div><!-- .secupress-latest-list --> </div><!-- .secupress-tab-content --> <div id="secupress-schedule" class="secupress-tab-content hide-if-js" role="tabpanel" aria-labelledby="secupress-l-schedule"> <p class="secupress-text-medium"> <?php _e( 'Schedule your security analysis', 'secupress' ); ?> </p> <p><?php _e( 'Stay updated on the security of your website. With our automatic scans, there is no need to log in to your WordPress admin to run a scan.', 'secupress' ); ?></p> <?php if ( secupress_is_pro() ) : $last_schedule = secupress_get_last_scheduled_scan(); $last_schedule = $last_schedule ? date_i18n( _x( 'Y-m-d \a\t h:ia', 'Schedule date', 'secupress' ), $last_schedule ) : '—'; $next_schedule = secupress_get_next_scheduled_scan(); $next_schedule = $next_schedule ? date_i18n( _x( 'Y-m-d \a\t h:ia', 'Schedule date', 'secupress' ), $next_schedule ) : '—'; ?> <div class="secupress-schedules-infos is-pro"> <p class="secupress-schedule-last-one"> <i class="secupress-icon-clock-o" aria-hidden="true"></i> <span><?php printf( __( 'Last automatic scan: %s', 'secupress' ), $last_schedule ); ?></span> </p> <p class="secupress-schedule-next-one"> <i class="secupress-icon-clock-o" aria-hidden="true"></i> <span><?php printf( __( 'Next automatic scan: %s', 'secupress' ), $next_schedule ); ?></span> </p> <p class="secupress-cta"> <a href="<?php echo esc_url( secupress_admin_url( 'modules', 'schedules' ) ); ?>#module-scanners" class="secupress-button secupress-button-primary" target="_blank"><?php _e( 'Schedule your next analysis', 'secupress' ); ?></a> </p> </div><!-- .secupress-schedules-infos --> <?php else : ?> <div class="secupress-schedules-infos"> <p class="secupress-schedule-last-one"> <i class="secupress-icon-clock-o" aria-hidden="true"></i> <span><?php printf( __( 'Last automatic scan: %s', 'secupress' ), '—' ); ?></span> </p> <p class="secupress-schedule-next-one"> <i class="secupress-icon-clock-o" aria-hidden="true"></i> <span><?php printf( __( 'Next automatic scan: %s', 'secupress' ), '—' ); ?></span> </p> <p class="secupress-cta"> <a href="<?php echo esc_url( secupress_admin_url( 'modules', 'schedules' ) ); ?>#module-scanners" class="secupress-button secupress-button-tertiary" target="_blank"><?php _e( 'Schedule your next analysis', 'secupress' ); ?></a> </p> <p class="secupress-cta-detail"><?php _e( 'Available in the PRO version', 'secupress' ); ?></p> </div><!-- .secupress-schedules-infos --> <?php endif; ?> </div><!-- .secupress-tab-content --> </div><!-- .secupress-tabs-contents --> <?php } ?> <div class="secupress-tabs-controls <?php if ( ! $use_grade ) { echo 'secupress-inline-block '; } ?>hide-if-no-js"> <ul class="secupress-tabs secupress-tabs-controls-list" role="tablist" data-content="#sp-tab-scans"> <li role="presentation"<?php if ( ! $use_grade ) { echo 'class="hidden"'; } ?>> <a id="secupress-l-latest" href="#secupress-latest" role="tab" aria-selected="false" aria-controls="secupress-latest"> <span class="secupress-label-with-icon"> <i class="secupress-icon-back rounded" aria-hidden="true"></i> <span class="secupress-upper"><?php _e( 'Latest scans', 'secupress' ); ?></span> <span class="secupress-description"><?php _e( 'View your previous scans', 'secupress' ); ?></span> </span> </a> </li> <?php $schedule_scan_url = $use_grade ? '#secupress-schedule' : secupress_admin_url( 'modules', 'schedules#module-scanners' ); ?> <li role="presentation"> <a id="secupress-l-schedule" href="<?php echo $schedule_scan_url; ?>" role="tab" aria-selected="false" aria-controls="secupress-schedule"> <span class="secupress-label-with-icon"> <i class="secupress-icon-calendar rounded" aria-hidden="true"></i> <span class="secupress-upper"><?php _e( 'Schedule Scans', 'secupress' ); ?></span> <span class="secupress-description"><?php _e( 'Manage your recurring scans', 'secupress' ); ?></span> </span> </a> </li> <li role="presentation"<?php if ( $use_grade ) { echo 'class="hi dden"'; } ?>> <a id="secupress-l-scan" href="#secupress-scan" role="tab" aria-selected="false" aria-controls="secupress-scan" class="secupress-current"> <span class="secupress-label-with-icon"> <i class="secupress-icon-secupress" aria-hidden="true"></i> <span class="secupress-upper"><?php esc_html_e( 'Scan results', 'secupress' ); ?></span> <span class="secupress-primary"><?php echo $last_report; ?></span> </span> </a> </li> </ul> <div class="secupress-rescan-progress-infos"> <h3> <i class="secupress-icon-secupress" aria-hidden="true"></i><br> <?php echo $currently_scanning_text; ?> </h3> </div> </div> </div><!-- .secupress-scan-header-main --> <?php } if ( ! $reports ) { ?> <div class="secupress-introduce-first-scan secupress-text-center"> <h3> <i class="secupress-icon-secupress" aria-hidden="true"></i><br> <span class="secupress-init-title"><?php _e( 'Click to launch first scan', 'secupress' ); ?></span> <?php echo $currently_scanning_text; ?> </h3> <p class="secupress-start-one-click-scan"> <button class="secupress-button secupress-button-primary secupress-button-scan" type="button" data-nonce="<?php echo esc_attr( wp_create_nonce( 'secupress-update-oneclick-scan-date' ) ); ?>"> <span class="icon" aria-hidden="true"> <i class="secupress-icon-radar"></i> </span> <span class="text"> <?php _e( 'Scan my website', 'secupress' ); ?> </span> <span class="secupress-progressbar-val" style="width:2%;"> <span class="secupress-progress-val-txt">2 %</span> </span> </button> </p> </div><!-- .secupress-introduce-first-scan --> <?php } ?> <div class="secupress-scanner-steps"> <?php /** * SecuPress Steps work this way: * - current step with li.secupress-current * - passed step(s) with li.secupress-past * - that's all */ $steps = [ '1' => [ 'title' => esc_html__( 'Security Report', 'secupress' ) ], '2' => [ 'title' => esc_html__( 'Auto-Fix', 'secupress' ) ], '3' => [ 'title' => esc_html__( 'Manual Operations', 'secupress' ) ], '4' => [ 'title' => esc_html__( 'Resolution Report', 'secupress' ) ], ]; $step = secupress_get_scanner_pagination(); $steps[2]['state'] = ''; $steps[3]['state'] = ''; $steps[4]['state'] = ''; switch ( $step ) { case 1: $steps[1]['state'] = ' secupress-current'; break; case 2: $steps[1]['state'] = ' secupress-past'; $steps[2]['state'] = ' secupress-current'; break; case 3: $steps[1]['state'] = ' secupress-past'; $steps[2]['state'] = ' secupress-past'; $steps[3]['state'] = ' secupress-current'; break; case 4: $steps[1]['state'] = ' secupress-past'; $steps[2]['state'] = ' secupress-past'; $steps[3]['state'] = ' secupress-past'; $steps[4]['state'] = ' secupress-current'; break; } $current_step_class = 'secupress-is-step-' . $step; unset( $step ); ?> <ol class="secupress-flex secupress-counter <?php echo esc_attr( $current_step_class ); ?>"> <?php foreach ( $steps as $i => $step ) { ?> <li class="secupress-col-1-3 secupress-counter-put secupress-flex<?php echo $step['state']; ?>" aria-labelledby="sp-step-<?php echo $i; ?>-l" aria-describedby="sp-step-<?php echo $i; ?>-d"> <span class="secupress-step-name" id="sp-step-<?php echo $i; ?>-l"><?php echo $step['title']; ?></span> <?php if ( 3 === $i ) { ?> <span class="secupress-step-name alt" aria-hidden="true"><?php echo $steps[4]['title']; ?></span> <?php } ?> </li> <?php } ?> </ol> <div id="secupress-more-info" class="<?php echo $reports ? ' hide-if-js' : ' secupress-open'; ?>"> <div class="secupress-flex secupress-flex-top"> <div class="secupress-col-1-4 step1"> <div class="secupress-blob"> <div class="secupress-blob-icon" aria-hidden="true"> <i class="secupress-icon-radar"></i> </div> <p class="secupress-blob-title"><?php _e( 'Site Health', 'secupress' ); ?></p> <div class="secupress-blob-content" id="sp-step-1-d"> <p><?php _e( 'Start to check all security items with the Scan your website button.', 'secupress' ); ?></p> </div> </div> </div><!-- .secupress-col-1-4 --> <div class="secupress-col-1-4 step2"> <div class="secupress-blob"> <div class="secupress-blob-icon" aria-hidden="true"> <i class="secupress-icon-autofix"></i> </div> <p class="secupress-blob-title"><?php _e( 'Auto-Fix', 'secupress' ) ?></p> <div class="secupress-blob-content" id="sp-step-2-d"> <p><?php _e( 'Launch the auto-fix on selected issues.', 'secupress' ); ?></p> </div> </div> </div><!-- .secupress-col-1-4 --> <div class="secupress-col-1-4 step3"> <div class="secupress-blob"> <div class="secupress-blob-icon" aria-hidden="true"> <i class="secupress-icon-manuals"></i> </div> <p class="secupress-blob-title"><?php _e( 'Manual Operations', 'secupress' ) ?></p> <div class="secupress-blob-content" id="sp-step-3-d"> <p><?php esc_html_e( 'Go further and take a look at the items you have to fix with specific operations.', 'secupress' ); ?></p> </div> </div> </div><!-- .secupress-col-1-4 --> <div class="secupress-col-1-4 step4"> <div class="secupress-blob"> <div class="secupress-blob-icon" aria-hidden="true"> <i class="secupress-icon-pad-check"></i> </div> <p class="secupress-blob-title"><?php esc_html_e( 'Resolution Report', 'secupress' ); ?></p> <div class="secupress-blob-content" id="sp-step-4-d"> <p><?php esc_html_e( 'Get the new site health report for your website.', 'secupress' ); ?></p> </div> </div><!-- .secupress-blob --> </div><!-- .secupress-col-1-4 --> </div><!-- .secupress-flex --> <p class="secupress-text-end secupress-m0"> <a href="#secupress-more-info" class="secupress-link-icon secupress-secupress-icon-right secupress-close-moreinfo<?php echo $reports ? '' : ' dont-trigger-hide'; ?>" data-trigger="slideup" data-target="secupress-more-info"> <span class="icon" aria-hidden="true"> <i class="secupress-icon-cross"></i> </span> <span class="text"> <?php _e( 'I’ve got it!', 'secupress' ); ?> </span> </a> </p> </div><!-- #secupress-more-info --> </div><!-- .secupress-scanner-steps --> </div><!-- .secupress-section-dark --> <div class="secupress-scanner-main-content secupress-section-gray secupress-bordered"> <div class="secupress-step-content-container"> <?php secupress_scanners_template(); ?> </div><!-- .secupress-step-content-container--> </div> <?php wp_nonce_field( 'secupress_score', 'secupress_score', false ); ?> </div> </div><!-- .wrap --> <?php } /** --------------------------------------------------------------------------------------------- */ /** TEMPLATE TAGS =============================================================================== */ /** --------------------------------------------------------------------------------------------- */ /** * Print the settings page title. * * @since 1.0 * * @param (string) $title The title. */ function secupress_admin_heading( $title = '' ) { printf( '<h1 class="secupress-page-title screen-reader-text">%1$s <sup>%2$s</sup> %3$s</h1>', SECUPRESS_PLUGIN_NAME, SECUPRESS_VERSION, $title ); } /** * Print the dark header of settings pages * * @since 1.0 * @author Geoffrey * * @param (array) $titles The title and subtitle. */ function secupress_settings_heading( $titles = array() ) { $title = ! empty( $titles['title'] ) ? $titles['title'] : ''; $subtitle = ! empty( $titles['subtitle'] ) ? $titles['subtitle'] : ''; ?> <div class="secupress-section-dark secupress-settings-header secupress-header-mini secupress-flex"> <div class="secupress-col-1-3 secupress-col-logo secupress-text-center"> <div class="secupress-logo-block secupress-flex"> <div class="secupress-lb-logo"> <?php echo secupress_get_logo( array( 'width' => 131 ) ); ?> </div> <div class="secupress-lb-name"> <p class="secupress-lb-title"> <?php echo secupress_get_logo_word( array( 'width' => 100, 'height' => 24 ) ); ?> </p> </div> </div> </div> <div class="secupress-col-1-3 secupress-col-text"> <p class="secupress-text-medium"><?php echo $title; ?></p> <?php if ( $subtitle ) { ?> <p><?php echo $subtitle; ?></p> <?php } ?> </div> <?php if ( ! secupress_is_white_label() ) { ?> <div class="secupress-col-1-3 secupress-col-rateus secupress-text-end"> <p class="secupress-rateus"> <strong><?php _e( 'Do you like this plugin?', 'secupress' ) ?></strong> <br> <?php printf( __( 'Please take a few seconds to rate us on %1$sWordPress.org%2$s', 'secupress' ), '<a target="_blank" title="' . esc_attr__( 'Open in a new window.', 'secupress' ) . '" href="' . SECUPRESS_RATE_URL . '">', '</a>' ); ?> </p> <p class="secupress-rateus-link"> <a target="_blank" title="<?php esc_attr_e( 'Open in a new window.', 'secupress' ); ?>" href="<?php echo SECUPRESS_RATE_URL; ?>"> <i class="secupress-icon-star" aria-hidden="true"></i> <i class="secupress-icon-star" aria-hidden="true"></i> <i class="secupress-icon-star" aria-hidden="true"></i> <i class="secupress-icon-star" aria-hidden="true"></i> <i class="secupress-icon-star" aria-hidden="true"></i> <span class="screen-reader-text"><?php echo _x( 'Give us five stars', 'hidden text', 'secupress' ); ?></span> </a> </p> </div> <?php } ?> </div> <?php } /** * Print the scanners page content. * * @since 1.0 */ function secupress_scanners_template() { secupress_require_class( 'scan' ); $is_subsite = is_multisite() && ! is_network_admin(); // Allowed tags in "Learn more" contents. $allowed_tags = array( 'a' => array( 'href' => array(), 'title' => array(), 'target' => array() ), 'abbr' => array( 'title' => array() ), 'code' => array(), 'em' => array(), 'strong' => array(), 'ul' => array(), 'ol' => array(), 'li' => array(), 'p' => array(), 'pre' => array( 'class' => array() ), 'br' => array(), 'h2' => array(), 'h3' => array(), ); // Auto-scans: scans that will be executed on page load. $autoscans = SecuPress_Scan::get_and_delete_autoscans(); if ( ! $is_subsite ) { $secupress_tests = secupress_get_scanners(); $scanners = secupress_get_scan_results(); $fixes = secupress_get_fix_results(); // Store the scans in 3 variables. They will be used to order the scans by status: 'bad', 'warning', 'notscannedyet', 'good'. $bad_scans = array(); $warning_scans = array(); $good_scans = array(); if ( ! empty( $scanners ) ) { foreach ( $scanners as $class_name_part => $details ) { if ( 'bad' === $details['status'] ) { $bad_scans[ $class_name_part ] = $details['status']; } elseif ( 'warning' === $details['status'] ) { $warning_scans[ $class_name_part ] = $details['status']; } elseif ( 'good' === $details['status'] ) { $good_scans[ $class_name_part ] = $details['status']; } } } } else { $secupress_tests = array( secupress_get_tests_for_ms_scanner_fixes() ); $sites = secupress_get_results_for_ms_scanner_fixes(); $site_id = get_current_blog_id(); $scanners = array(); $fixes = array(); foreach ( $sites as $test => $site_data ) { if ( ! empty( $site_data[ $site_id ] ) ) { $scanners[ $test ] = ! empty( $site_data[ $site_id ]['scan'] ) ? $site_data[ $site_id ]['scan'] : array(); $fixes[ $test ] = ! empty( $site_data[ $site_id ]['fix'] ) ? $site_data[ $site_id ]['fix'] : array(); } } } $step = secupress_get_scanner_pagination(); switch ( $step ) { case 4 : require_once( SECUPRESS_INC_PATH . 'admin/scanner-step-4.php' ); break; case 3 : require_once( SECUPRESS_INC_PATH . 'admin/scanner-step-3.php' ); break; case 2 : require_once( SECUPRESS_INC_PATH . 'admin/scanner-step-2.php' ); break; case 1 : default: require_once( SECUPRESS_INC_PATH . 'admin/scanner-step-1.php' ); } } /** * Print a box with title. * * @since 1.0 * * @param (array) $args An array containing the box title, content and id. */ function secupress_sidebox( $args ) { $args = wp_parse_args( $args, array( 'id' => '', 'title' => 'Missing', 'content' => 'Missing', ) ); echo '<div class="secupress-postbox postbox" id="' . $args['id'] . '">'; echo '<h3 class="hndle"><span><b>' . $args['title'] . '</b></span></h3>'; echo'<div class="inside">' . $args['content'] . '</div>'; echo "</div>\n"; } /** * Will return the current scanner step number. * * @since 1.0 * @author Julio Potier * * @return (int) Returns 1 if first scan never done. */ function secupress_get_scanner_pagination() { $scans = array_filter( (array) get_site_option( SECUPRESS_SCAN_TIMES ) ); if ( empty( $_GET['step'] ) || ! is_numeric( $_GET['step'] ) || empty( $scans ) || 0 > $_GET['step'] ) { $step = 1; } else { $step = (int) $_GET['step']; if ( $step > 4 ) { secupress_is_jarvis(); } } return $step; } free/functions/files.php 0000644 00000117335 15174670627 0011341 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Get WP Direct filesystem object. * * @since 1.3 Don't use the global Filesystem anymore, to make sure to use "direct" (some things don't work over "ftp"). * @since 1.0 * @author Grégory Viguier * * @return `$wp_filesystem` object. */ function secupress_get_filesystem() { static $filesystem; if ( $filesystem ) { return $filesystem; } require_once( ABSPATH . 'wp-admin/includes/class-wp-filesystem-base.php' ); require_once( ABSPATH . 'wp-admin/includes/class-wp-filesystem-direct.php' ); $filesystem = new WP_Filesystem_Direct( new StdClass() ); // WPCS: override ok. // Set the permission constants if not already set. if ( ! defined( 'FS_CHMOD_DIR' ) ) { define( 'FS_CHMOD_DIR', ( @fileperms( ABSPATH ) & 0777 | 0755 ) ); } if ( ! defined( 'FS_CHMOD_FILE' ) ) { define( 'FS_CHMOD_FILE', ( @fileperms( ABSPATH . 'index.php' ) & 0777 | 0644 ) ); } return $filesystem; } /** * Remove a single file or a folder recursively. * * @since 1.0 * @author Grégory Viguier * * @param (string) $dir File/Directory to delete. * @param (array) $dirs_to_preserve Dirs that should not be deleted. Default: array(). */ function secupress_rrmdir( $dir, $dirs_to_preserve = array() ) { $dir = rtrim( $dir, '/' ); /** * Fires after a file/directory cache was deleted. * * @since 1.0 * * @param (string) $dir File/Directory to delete. * @param (array) $dirs_to_preserve Directories that should not be deleted. */ do_action( 'secupress.before_rrmdir', $dir, $dirs_to_preserve ); $filesystem = secupress_get_filesystem(); if ( ! $filesystem->is_dir( $dir ) ) { $filesystem->delete( $dir ); return; }; if ( $dirs = glob( $dir . '/*', GLOB_NOSORT ) ) { $keys = array(); foreach ( $dirs_to_preserve as $dir_to_preserve ) { $matches = preg_grep( "#^$dir_to_preserve$#" , $dirs ); $keys[] = reset( $matches ); } $dirs = array_diff( $dirs, array_filter( $keys ) ); foreach ( $dirs as $dir ) { if ( $filesystem->is_dir( $dir ) ) { secupress_rrmdir( $dir, $dirs_to_preserve ); } else { $filesystem->delete( $dir ); } } } $filesystem->delete( $dir ); /** * Fires before a file/directory cache was deleted. * * @since 1.0 * * @param (string) $dir File/Directory to delete. * @param (array) $dirs_to_preserve Dirs that should not be deleted. */ do_action( 'secupress.after_rrmdir', $dir, $dirs_to_preserve ); } /** * Directory creation based on WordPress Filesystem. * * @since 1.0 * * @param (string) $dir The path of directory will be created. * * @return (bool) */ function secupress_mkdir( $dir ) { $filesystem = secupress_get_filesystem(); return $filesystem->mkdir( $dir ); } /** * Recursive directory creation based on full path. * * @since 1.0 * @author Grégory Viguier * * @see wp_mkdir_p() in `/wp-includes/functions.php`. * * @param (string) $target A folder path. * * @return True on success. */ function secupress_mkdir_p( $target ) { $target = wp_normalize_path( $target ); $filesystem = secupress_get_filesystem(); // Safe mode fails with a trailing slash under certain PHP versions. $target = rtrim( $target, '/' ); if ( empty( $target ) ) { $target = '/'; } if ( $filesystem->exists( $target ) ) { return $filesystem->is_dir( $target ); } // Attempting to create the directory may clutter up our display. if ( $filesystem->mkdir( $target ) ) { return true; } elseif ( $filesystem->is_dir( dirname( $target ) ) ) { return false; } // If the above failed, attempt to create the parent node, then try again. if ( '/' !== $target && secupress_mkdir_p( dirname( $target ) ) ) { return secupress_mkdir_p( $target ); } return false; } /** * Tell if a file located in the home folder is writable. * If the file does not exist, tell if the home folder is writable. * * @since 1.0 * @author Grégory Viguier * * @param (string) $file File name. * * @return (bool) */ function secupress_root_file_is_writable( $file ) { static $home_path; if ( ! isset( $home_path ) ) { $home_path = secupress_get_home_path(); } return wp_is_writable( $home_path . $file ) || ! file_exists( $home_path . $file ) && wp_is_writable( $home_path ); } /** * Try to find the correct `wp-config.php` file, support one level up in filetree. * * @since 2.0 Add filter secupress.wpconfig_path to target another file with your constants * @author Julio Potier * @since 1.0 * @author Grégory Viguier * * @hook secupress.wpconfig_filename * @param (string) $context Can be use for filtering * @return (string|bool) The path of `wp-config.php` file or false. */ function secupress_find_wpconfig_path( $context = '' ) { $config_file = ABSPATH . 'wp-config.php'; $config_file_alt = dirname( ABSPATH ) . '/wp-config.php'; if ( file_exists( $config_file ) ) { /** * Filter the wp-config.php file path * * @param (string) The default file path for wp-config.php * @since 2.0 * @author Julio Potier */ return apply_filters( 'secupress.wpconfig_path', $config_file, 'main', $context ); } if ( @file_exists( $config_file_alt ) && ! file_exists( dirname( ABSPATH ) . '/wp-settings.php' ) ) { /** * Filter the wp-config.php file path * * @param (string) The default file path for wp-config.php * @since 2.0 * @author Julio Potier */ return apply_filters( 'secupress.wpconfig_path', $config_file_alt, 'alt', $context ); } // No writable file found. return false; } /** * Allow interface to display a custom filename for wp-config.php * * @since 2.0 * @author Julio Potier * * @see secupress_find_wpconfig_path() * * @hook secupress.wpconfig_filename * @return (string) The custom wpconfig.php **/ function secupress_get_wpconfig_filename( $context = 'filename' ) { $filename = str_replace( ABSPATH, '', secupress_find_wpconfig_path( $context ) ); /** * Filter the wp-config.php filename (custom or not) * * @param (string) The default filename from the real file path * @since 2.0 * @author Julio Potier */ return apply_filters( 'secupress.wpconfig_filename', $filename, $context ); } /** * Tell if the `wp-config.php` file is writable. * * @since 1.2.4 Return null if the file can't be located. * @since 1.2.2 * @author Grégory Viguier * * @return (string|bool|null) The path of `wp-config.php` file if writable, false if not writable, null if the file doesn't exist. */ function secupress_is_wpconfig_writable( $context = '' ) { $wpconfig_filepath = secupress_find_wpconfig_path( $context ); if ( ! $wpconfig_filepath ) { return null; } return wp_is_writable( $wpconfig_filepath ) ? $wpconfig_filepath : false; } /** * Check if a given muplugin is present in mu-plugins/ folder * * @since 2.3.7 * @author Julio Potier * * @param (string) $filename_part * * @return (bool) **/ function secupress_muplugin_exists( $filename_part ) { static $filename_parts; if ( ! file_exists( WPMU_PLUGIN_DIR ) ) { return false; } if ( ! isset( $filename_parts ) ) { $filename_parts = []; } $found_files = glob( WPMU_PLUGIN_DIR . "/{_secupress_{$filename_part}*,(secupress_{$filename_part}*}.php", GLOB_BRACE ); $filename_parts[ $filename_part ] = ! empty( $found_files ); return $filename_parts[ $filename_part ]; } /** * Check if a given marker is present in wpconfig file * * @since 2.3.7 * @author Julio Potier * * @param (string) $marker * * @return (bool) **/ function secupress_marker_exists_in_wpconfig( $marker ) { static $file_content = ''; $wpconfig_filepath = secupress_is_wpconfig_writable(); if ( ! $wpconfig_filepath ) { return false; } $filesystem = secupress_get_filesystem(); if ( ! $file_content ) { $file_content = $filesystem->get_contents( $wpconfig_filepath ); } return preg_match( "@[\t ]*?# BEGIN SecuPress {$marker}\s.*# END SecuPress\s*?@sU", $file_content ); } /** * Comment a constant definition in the `wp-config.php` file (or any other file). * If `$marker` is provided, our definition will be also removed. * * @since 2.0 Change the return values to let a possible TRUE if these are WP defaults + remove $new_value param (unused and not the purpose) * @author Julio Potier * @since 1.2.2 * @author Grégory Viguier * * @param (string) $constant Name of the constant. * @param (string) $wpconfig_filepath Path to the `wp-config.php` file. * @param (string) $marker Name of the marker used to define the constant ourself. * * @return (bool) */ function secupress_comment_constant( $constant, $wpconfig_filepath = false, $marker = false ) { static $file_content = ''; if ( ! $wpconfig_filepath ) { $wpconfig_filepath = secupress_is_wpconfig_writable(); if ( ! $wpconfig_filepath ) { return false; } } $filesystem = secupress_get_filesystem(); if ( ! $file_content ) { $file_content = $filesystem->get_contents( $wpconfig_filepath ); } if ( $marker && preg_match( "@[\t ]*?# BEGIN SecuPress {$marker}\s.*# END SecuPress\s*?@sU", $file_content ) ) { // Remove the constant we could have previously set. return secupress_replace_content( $wpconfig_filepath, "@[\t ]*?# BEGIN SecuPress {$marker}\s.*# END SecuPress\s*?@sU", '' ); } // Comment old value. if ( preg_match( "@^[\t ]*define\s*\(\s*(?:'{$constant}'|\"{$constant}\")\s*,(?:.*);.*\s*$@mU", $file_content ) ) { return secupress_replace_content( $wpconfig_filepath, "@^[\t ]*define\s*\(\s*(?:'{$constant}'|\"{$constant}\")\s*,(?:.*);.*\s*$@mU", '/** Commented by SecuPress. */ /** $0 */' ); } // Nothing has been replaced because there is nothing to replace, aka, these are WordPress default values, still overridable // ps: if these constants are set elsewhere… well, my bad :) return true; } /** * Uncomment a constant definition in the `wp-config.php` file (or any other file). * If `$marker` is provided, our definition will be also removed. * * @since 1.2.2 * @author Grégory Viguier * * @param (string) $constant Name of the constant. * @param (string) $wpconfig_filepath Path to the `wp-config.php` file. * @param (string) $marker Name of the marker used to define the constant ourself. * * @return (bool) True if the constant definition has been successfully uncommented. False if: * - the constant is already defined somewhere, * - or the file is not writable, * - or the comment was not found in the file, * - or it couldn't be uncommented. * So basically, this information is totally useless, deal with it. */ function secupress_uncomment_constant( $constant, $wpconfig_filepath = false, $marker = false ) { if ( ! $wpconfig_filepath ) { $wpconfig_filepath = secupress_is_wpconfig_writable(); if ( ! $wpconfig_filepath ) { return false; } } if ( $marker ) { // Remove the constant we could have previously set. $replaced = secupress_replace_content( $wpconfig_filepath, "@[\t ]*?# BEGIN SecuPress {$marker}\s.*# END SecuPress\s*?@sU", '' ); if ( defined( $constant ) && ! $replaced ) { /** * If the constant is defined and "our" has not been removed (because it didn't exist), that means it's defined somewhere else. * In that case, we must not uncomment the previous value or it will be defined twice. */ return false; } } // Uncomment old value. $constant = "(define\s*\(\s*(?:'$constant'|\"$constant\")\s*,(?:.*))"; $p = "@^[\t ]*/\*+\s*Commented by SecuPress\.*\s*\*/\s*?(?:/\*+\s*{$constant}\s*\*/|/+\s*{$constant})\s*$@mU"; return secupress_replace_content( $wpconfig_filepath, $p, '$1' ); } /** * Get plugins dir path. * * @since 1.0 * @author Grégory Viguier * @return (string) */ function secupress_get_plugins_path() { static $plugins_dir; if ( ! isset( $plugins_dir ) ) { $plugins_dir = realpath( WP_PLUGIN_DIR ) . DIRECTORY_SEPARATOR; } return $plugins_dir; } /** * Get themes dir path. * * @since 1.0 * * @return (string) */ function secupress_get_themes_path() { static $themes_dir; if ( ! isset( $themes_dir ) ) { $wp_filesystem = secupress_get_filesystem(); // WPCS: override ok. $themes_dir = realpath( $wp_filesystem->wp_themes_dir() ) . DIRECTORY_SEPARATOR; } return $themes_dir; } /** * Tell if a plugin is symlinked. * * @since 1.0 * @author Grégory Viguier * * @param (string) $plugin_file Plugin main file path, relative to the plugins folder. * * @return (bool) True if the plugin is symlinked. */ function secupress_is_plugin_symlinked( $plugin_file ) { $plugins_dir = secupress_get_plugins_path(); $plugin_path = realpath( $plugins_dir . $plugin_file ); return ! ( $plugin_path && 0 === strpos( $plugin_path, $plugins_dir ) ); } /** * Tell if a theme is symlinked. * * @since 1.0 * @author Grégory Viguier * * @param (string) $theme_slug Theme dir name. * * @return (bool) True if the theme is symlinked. */ function secupress_is_theme_symlinked( $theme_slug ) { $themes_dir = secupress_get_themes_path(); $theme_path = realpath( $themes_dir . $theme_slug ); return ! ( $theme_path && 0 === strpos( $theme_path, $themes_dir ) ); } /** * File creation based on WordPress Filesystem. * * @since 2.2.6 $file_content is now an array + clearstatcache() usage * @author Julio Potier * @since 1.0 * @author Grégory Viguier * * @param (string) $file The path of file. * @param (string) $new_content The content that will be added to the file. * @param (array) $args (optional) * marker (string): An additional suffix string to add to the "SecuPress" marker, Default ''. * put (string): (prepend|append|replace): Prepend or append content in the file, Default 'prepend'. * text (string): When (prepend|append) is used for "put", you can speficy a text to find, it will be pre/appended around this text. * @return (bool) */ function secupress_put_contents( $file, $new_content = '', $args = array() ) { $args = wp_parse_args( $args, array( 'marker' => '', 'put' => 'prepend', 'text' => '', 'keep_old' => false, ) ); $filesystem = secupress_get_filesystem(); $comment_char = pathinfo( $file, PATHINFO_EXTENSION ) !== 'ini' ? '#' : ';'; // Get the whole content of file and remove old marker content. if ( file_exists( $file ) ) { $pattern = '/' . $comment_char . ' BEGIN SecuPress ' . $args['marker'] . '(.*)' . $comment_char . ' END SecuPress\s*?/isU'; $file_content[ $file ] = file_get_contents( $file ); if ( $args['keep_old'] ) { preg_match( $pattern, $file_content[ $file ], $keep_old ); } $file_content[ $file ] = preg_replace( $pattern, '', $file_content[ $file ] ); } if ( ! empty( $new_content ) ) { $content = $comment_char . ' BEGIN SecuPress ' . $args['marker'] . PHP_EOL; if ( $args['keep_old'] && isset( $keep_old[1] ) ) { $content .= trim( $keep_old[1] ) . "\n"; } $content .= trim( $new_content ) . PHP_EOL; $content .= $comment_char . ' END SecuPress' . PHP_EOL . PHP_EOL; if ( '' !== $args['text'] && strpos( $file_content[ $file ], $args['text'] ) !== false ) { if ( 'append' === $args['put'] ) { $content = str_replace( $args['text'], $args['text'] . PHP_EOL . $content, $file_content[ $file ] ); } elseif ( 'prepend' === $args['put'] ) { $content = str_replace( $args['text'], $content . PHP_EOL . $args['text'], $file_content[ $file ] ); } } else { if ( 'append' === $args['put'] ) { $content = $file_content[ $file ] . PHP_EOL . $content; } elseif ( 'prepend' === $args['put'] ) { $content = $content . $file_content[ $file ]; } } $file_content[ $file ] = $content; } $return = $filesystem->put_contents( $file, $file_content[ $file ], FS_CHMOD_FILE ); clearstatcache( true, $file ); return $return; } /** * File creation based on WordPress Filesystem. * * @since 2.2.6 clearstatcache() usage * @author Julio potier * @since 1.3 Use a sandbox for the `wp-config.php` file. * @since 1.0 * @author Grégory Viguier * * @param (string) $file The path of file will be created. * @param (string) $old_content The content to be replaced from the file (preg_replace). * @param (string) $new_content The new content (preg_replace). * * @return (bool) */ function secupress_replace_content( $file, $old_content, $new_content, $skip_sandbox = false ) { static $file_content = ''; if ( ! file_exists( $file ) ) { return false; } $filesystem = secupress_get_filesystem(); if ( ! $file_content ) { $file_content = $filesystem->get_contents( $file ); } $new_content = preg_replace( $old_content, $new_content, $file_content ); if ( null === $new_content || $new_content === $file_content ) { return false; } $file_content = $new_content; $filename = preg_quote( secupress_get_wpconfig_filename() ); $skip_sandbox = ( ! defined( 'SECUPRESS_NO_SANDBOX' ) || ! SECUPRESS_NO_SANDBOX ) && $skip_sandbox; if ( ! $skip_sandbox && false !== preg_match( '@/$filename$@', $file ) && true !== secupress_wpconfig_success_in_sandbox( $new_content ) ) { return false; } $return = $filesystem->put_contents( $file, $new_content, FS_CHMOD_FILE ); clearstatcache( true, $file ); return $return; } /** * A sandbox for doing crazy things with `wp-config.php`. * Create a folder containing a `index.php` file with the provided content. * Then, make a request to the `index.php` file to test if a server error is triggered. * * @author Julio Potier * @since 2.0 Add secupress.use_sandbox filter * * @author Grégory Viguier * @since 1.3 * * @param (string) $content The content to put in the `wp-config.php` file. * * @return (object|bool) Return true if the server does not trigger an error 500, false otherwise. * Return a WP_Error object if the sandbox creation fails or if the HTTP request fails. */ function secupress_wpconfig_success_in_sandbox( $content ) { /** * Allows to bypass the sandbox * @since 2.0 * @param (bool) true by default, false to use it. * @param (string) A context. */ if ( false === apply_filters( 'secupress.use_sandbox', true, 'wp-config' ) ) { return true; } $wp_filesystem = secupress_get_filesystem(); $file_name = 'index.php'; $folder_name = 'secupress-sandbox-' . uniqid(); $folder_path = ABSPATH . $folder_name; // Remove any `require_once()` and friends. $content = preg_replace( '@(require|include)(_once)?[\s(][^;]+;@', '$foo = "foo";', $content ); // Define `ABSPATH` and add `error_reporting()`. $content = preg_replace( '@^<\?php@', '<?php error_reporting( -1 );', trim( $content ) ); // Print a placeholder when the file is requested. $content .= "\necho 'SANDBOX OK';"; // Create folder. if ( ! $wp_filesystem->mkdir( $folder_path ) ) { return new WP_Error( 'dir_creation_failed', __( 'The temporary directory could not be created.', 'secupress' ) ); } // Create `index.php` file with our content. if ( ! $wp_filesystem->put_contents( $folder_path . '/' . $file_name, $content, FS_CHMOD_FILE ) ) { $wp_filesystem->delete( $folder_path, true ); return new WP_Error( 'file_creation_failed', __( 'The temporary file could not be created.', 'secupress' ) ); } /** This filter is documented in inc/classes/scanners/class-secupress-scan.php. */ $timeout = apply_filters( 'secupress.remote_timeout', 30 ); $origin = 'wp-config-sandbox'; $request_args = array( 'redirection' => 0, 'timeout' => $timeout, 'local' => true, 'sslverify' => false, 'user-agent' => SECUPRESS_PLUGIN_NAME . '/' . SECUPRESS_VERSION, 'cookies' => $_COOKIE, 'headers' => array( 'X-SecuPress-Origin' => $origin, ), ); /** This filter is documented in inc/classes/scanners/class-secupress-scan.php. */ $request_args = apply_filters( 'secupress.scan.default_request_args', $request_args, $origin ); // Try to reach `index.php`. $request_url = $folder_name . '/' . $file_name . '?' . md5( $folder_name . 's' ) . '=' . md5( $folder_name . 'p' ); $response = wp_remote_get( site_url( $request_url ), $request_args ); // Now we can get rid of the files. $wp_filesystem->delete( $folder_path, true ); // HTTP requests are probably blocked. if ( is_wp_error( $response ) ) { return $response; } // Finally, the answer we were looking for. return 500 !== wp_remote_retrieve_response_code( $response ) && false !== strpos( wp_remote_retrieve_body( $response ), 'SANDBOX OK' ); } /** * From WP Core `async_upgrade()` but using `Automatic_Upgrader_Skin` instead of `Language_Pack_Upgrader_Skin` to have a silent upgrade. * * @since 1.0 * @author Grégory Viguier * * @return (void) */ function secupress_async_upgrades() { // Nothing to do? $language_updates = wp_get_translation_updates(); if ( ! $language_updates ) { return; } // Avoid messing with VCS installs, at least for now. // Noted: this is not the ideal way to accomplish this. $check_vcs = new WP_Automatic_Updater; if ( $check_vcs->is_vcs_checkout( WP_CONTENT_DIR ) ) { return; } foreach ( $language_updates as $key => $language_update ) { $update = ! empty( $language_update->autoupdate ); /** This filter is documented in wp-admin/includes/class-wp-upgrader.php */ $update = apply_filters( 'async_update_translation', $update, $language_update ); if ( ! $update ) { unset( $language_updates[ $key ] ); } } if ( empty( $language_updates ) ) { return; } $skin = new Automatic_Upgrader_Skin(); $lp_upgrader = new Language_Pack_Upgrader( $skin ); $lp_upgrader->bulk_upgrade( $language_updates ); } /** * Return all possible matches for a muplugin filename * * @since 2.3.16 $prefix param + basename usage * @since 2.0 * @author Julio Potier * * @param (string) $filename A part of the filename you are looking for * @param (string) $prefix * @return (array) Empty if no file found. **/ function secupress_find_mu_plugin( $filename, $prefix = 'secupress_' ) { $mus = wp_get_mu_plugins(); foreach ( $mus as $i => $mu ) { if ( false === strpos( basename( $mu ), $prefix . $filename ) ) { unset( $mus[ $i ] ); } } return array_values( $mus ); } /** * Creates a MU-PLUGIN. * * @since 2.3.16 3rd param $suffix * @since 2.2.6 New filename pattern * @author Julio Potier * @since 1.0 * @author Grégory Viguier * * @param (string) $filename_part The file name part in `(secupress_{$filename_part}).php`. * @param (string) $contents The file content. * @param (int) $suffix A filename suffix if needed, do not concat into the filename! Usually a uniqid() * * @return (bool) True on success. */ function secupress_create_mu_plugin( $filename_part, $contents, $suffix = '' ) { $suffix = $suffix ? '_' . $suffix : ''; $filesystem = secupress_get_filesystem(); if ( ! file_exists( WPMU_PLUGIN_DIR ) ) { $filesystem->mkdir( WPMU_PLUGIN_DIR ); } if ( secupress_find_mu_plugin( $filename_part ) || ! file_exists( WPMU_PLUGIN_DIR ) ) { return false; } // Delete all previous files $filenames = [ WPMU_PLUGIN_DIR . "/_secupress_{$filename_part}", WPMU_PLUGIN_DIR . "/(secupress_{$filename_part}", // The good one since 2.3 ]; foreach( $filenames as $filename ) { $files = secupress_find_mu_plugin( $filename_part ); if ( $files ) { array_map( 'secupress_delete_mu_plugin', $files ); } } $filename = WPMU_PLUGIN_DIR . "/(secupress_{$filename_part}{$suffix}).php"; $done = $filesystem->put_contents( $filename, $contents ); if ( defined( 'SECUPRESS_INSTALLED_MUPLUGINS' ) ) { $mus = get_option( SECUPRESS_INSTALLED_MUPLUGINS, [] ); if ( $done && $mus ) { $mus[ basename( $filename ) ] = get_plugin_data( $filename ); update_option( SECUPRESS_INSTALLED_MUPLUGINS, $mus ); } } return $done; } /** * Creates a DROPIN-PLUGIN. * * @since 2.2.6 * @author Julio Potier * * @param (string) $filename_part The file name part in `(secupress_{$filename_part}).php`. * @param (string) $contents The file content. * * @return (bool) True on success. */ function secupress_create_dropin_plugin( $filename, $contents ) { $filesystem = secupress_get_filesystem(); $filename = WP_CONTENT_DIR . "/{$filename}.php"; if ( file_exists( $filename ) ) { $filesystem->delete( $filename ); } if ( ! file_exists( WP_CONTENT_DIR ) ) { $filesystem->mkdir( WP_CONTENT_DIR ); } if ( file_exists( $filename ) || ! file_exists( WP_CONTENT_DIR ) ) { return false; } $filesystem->put_contents( $filename, $contents ); } /** * Delete a MU-PLUGIN. * * @since 2.2.6 * @author Julio Potier * * @param (string) $filename The filename or file part in `(secupress_{$filename}).php`. * * @return (bool) True on success. */ function secupress_delete_mu_plugin( $filename ) { if ( ! $filename ) { return false; } $filesystem = secupress_get_filesystem(); $filename = basename( $filename ); $filename = str_replace( [ WPMU_PLUGIN_DIR, '_secupress-', '_secupress_', '(secupress_', ').php', '.php' ], '', $filename ); $oldfile = WPMU_PLUGIN_DIR . "/_secupress-{$filename}.php"; if ( file_exists( $oldfile ) ) { $filesystem->delete( $oldfile ); } $oldfile = WPMU_PLUGIN_DIR . "/_secupress_{$filename}.php"; if ( file_exists( $oldfile ) ) { $filesystem->delete( $oldfile ); } $filename = str_replace( '-', '_', $filename ); $filename = WPMU_PLUGIN_DIR . "/(secupress_{$filename}).php"; if ( file_exists( $filename ) ) { $deleted = $filesystem->delete( $filename ); } if ( ! defined( 'SECUPRESS_INSTALLED_MUPLUGINS' ) ) { return; } $mus = get_option( SECUPRESS_INSTALLED_MUPLUGINS, [] ); if ( empty( $mus ) ) { return false; } unset( $mus[ basename( $filename ) ] ); update_option( SECUPRESS_INSTALLED_MUPLUGINS, $mus ); return $deleted; } /** * Deletes a DROPIN-PLUGIN. * * @since 2.2.6 * @author Julio Potier * * @param (string) $filename The filename or file part in `(secupress_{$filename}).php`. * * @return (bool) True on success. */ function secupress_delete_dropin_plugin( $filename ) { $filesystem = secupress_get_filesystem(); $filename = WP_CONTENT_DIR . "/{$filename}.php"; $dropins = _get_dropins(); if ( isset( $dropins[ basename( $filename ) ] ) && file_exists( $filename ) ) { $filesystem->delete( $filename ); } } /** * Checks the mu-plugins directory and retrieve all mu-plugin files with any plugin data. * * WordPress only includes mu-plugin files in the base mu-plugins directory (wp-content/mu-plugins). * * @since 2.3.17 * @return array[] Array of arrays of mu-plugin data, keyed by plugin file name. See get_plugin_data(). */ function secupress_get_mu_plugins() { $wp_plugins = array(); $plugin_files = array(); if ( ! is_dir( WPMU_PLUGIN_DIR ) ) { return $wp_plugins; } // Files in wp-content/mu-plugins directory. $plugins_dir = @opendir( WPMU_PLUGIN_DIR ); if ( $plugins_dir ) { while ( ( $file = readdir( $plugins_dir ) ) !== false ) { if ( str_ends_with( $file, '.php' ) ) { $plugin_files[] = $file; } } } else { return $wp_plugins; } closedir( $plugins_dir ); if ( empty( $plugin_files ) ) { return $wp_plugins; } foreach ( $plugin_files as $plugin_file ) { if ( ! is_readable( WPMU_PLUGIN_DIR . "/$plugin_file" ) ) { continue; } // Do not apply markup/translate as it will be cached. $plugin_data = get_plugin_data( WPMU_PLUGIN_DIR . "/$plugin_file", false, false ); if ( empty( $plugin_data['Name'] ) ) { $plugin_data['Name'] = $plugin_file; } $wp_plugins[ $plugin_file ] = $plugin_data; } /* REMOVE THIS F NONSENSE if ( isset( $wp_plugins['index.php'] ) && filesize( WPMU_PLUGIN_DIR . '/index.php' ) <= 30 ) { // Silence is golden. unset( $wp_plugins['index.php'] ); } */ // We dont need that here, perf. // uasort( $wp_plugins, '_sort_uname_callback' ); return $wp_plugins; } /** * Format a path with no heading slash and a trailing slash. * If the path is empty, it returns an empty string, not a lonely slash. * Example: foo/bar/ * * @since 1.0 * @author Grégory Viguier * * @param (string) $slug A path. * * @return (string) The path with no heading slash and a trailing slash. */ function secupress_trailingslash_only( $slug ) { return ! is_null( $slug ) ? ltrim( trim( $slug, '/' ) . '/', '/' ) : ''; } /** * A better `get_home_path()`, without the bugs on old versions. * @see https://core.trac.wordpress.org/ticket/25767 * * @since 1.0 * @author Grégory Viguier * * @return (string) The home path. */ function secupress_get_home_path() { $home = set_url_scheme( get_option( 'home' ), 'http' ); $siteurl = set_url_scheme( get_option( 'siteurl' ), 'http' ); if ( ! empty( $home ) && 0 !== strcasecmp( $home, $siteurl ) ) { $wp_path_rel_to_home = str_ireplace( $home, '', $siteurl ); /* $siteurl - $home */ $pos = strripos( str_replace( '\\', '/', $_SERVER['SCRIPT_FILENAME'] ), trailingslashit( $wp_path_rel_to_home ) ); $home_path = substr( $_SERVER['SCRIPT_FILENAME'], 0, $pos ); $home_path = trailingslashit( $home_path ); } else { $home_path = ABSPATH; } return wp_normalize_path( $home_path ); } /** * Is WP a MultiSite and a subfolder install? * * @since 1.0 * @author Grégory Viguier * * @return (bool). */ function secupress_is_subfolder_install() { global $wpdb; static $subfolder_install; if ( ! isset( $subfolder_install ) ) { if ( is_multisite() ) { $subfolder_install = ! is_subdomain_install(); } elseif ( ! is_null( $wpdb->sitemeta ) ) { $subfolder_install = ! $wpdb->get_var( "SELECT meta_value FROM $wpdb->sitemeta WHERE site_id = 1 AND meta_key = 'subdomain_install'" ); } else { $subfolder_install = false; } } return $subfolder_install; } /** * Has WP its own directory? * * @since 1.0 * @author Grégory Viguier * * @see http://codex.wordpress.org/Giving_WordPress_Its_Own_Directory * * @return (string) The directory containing WP. */ function secupress_get_wp_directory() { static $wp_siteurl_subdir; if ( isset( $wp_siteurl_subdir ) ) { return $wp_siteurl_subdir; } $wp_siteurl_subdir = ''; $home = set_url_scheme( rtrim( get_option( 'home' ), '/' ), 'http' ); $siteurl = set_url_scheme( rtrim( get_option( 'siteurl' ), '/' ), 'http' ); if ( ! empty( $home ) && 0 !== strcasecmp( $home, $siteurl ) ) { $wp_siteurl_subdir = str_ireplace( $home, '', $siteurl ); /* $siteurl - $home */ $wp_siteurl_subdir = secupress_trailingslash_only( $wp_siteurl_subdir ); } else { $parsed_url_path = parse_url( $home, PHP_URL_PATH ); if ( '/' !== $parsed_url_path ) { $wp_siteurl_subdir = secupress_trailingslash_only( $parsed_url_path ); } } /** * Filter the returned guessed wp sudbir * * @since 2.4 * @author Julio Potier * * @param (string) $wp_siteurl_subdir * @param (string) $home * @param (string) $siteurl * * @return (string) **/ $wp_siteurl_subdir = apply_filters( 'secupress.get_wp_directory', $wp_siteurl_subdir, $home, $siteurl ); return $wp_siteurl_subdir; } /** * Get infos for the rewrite rules. * The main concern is about directories. * * @since 1.0 * @author Grégory Viguier * * @return (array) An array containing the following keys: * 'base' => Rewrite base, or "home directory". * 'wp_dir' => WP directory. * 'site_dir' => Directory containing the WordPress files. * 'is_sub' => Is it a subfolder install? (Multisite). * 'site_from' => Regex for first part of the rewrite rule (WP files). * 'site_to' => First part of the rewrited address (WP files). * In case of MultiSite with sub-folders, this is not really where the files are: WP rewrites the admin URL for example, which is based on the "site URL". * 'home_from' => Regex for first part of the rewrite rule (home URL). * 'home_to' => First part of the rewrited address (home URL). */ function secupress_get_rewrite_bases() { global $is_apache, $is_nginx, $is_iis7; static $bases; if ( isset( $bases ) ) { return $bases; } $base = wp_parse_url( trailingslashit( get_option( 'home' ) ) ); $base = $base['path']; $wp_dir = secupress_get_wp_directory(); // WP in its own directory. $is_sub = secupress_is_subfolder_install(); // MultiSite by sub-folders. $site_dir = $base . ltrim( $wp_dir, '/' ); $bases = array( 'base' => $base, // '/' or '/sub-dir/'. 'wp_dir' => $wp_dir, // '' or '/wp-dir/'. 'site_dir' => $site_dir, // '/', '/wp-dir/', '/sub-dir/', or '/sub-dir/wp-dir/'. 'is_sub' => $is_sub, // True or false. ); // Apache. if ( $is_apache ) { /** * In the `*_from` fields, we don't add `$base` because we use `RewriteBase $base` in the rewrite rules. * In the `*_to` fields, `$base` is optional, but WP adds it so we do the same for concistancy. */ if ( $is_sub ) { // MultiSite by sub-folders. return ( $bases = array_merge( $bases, array( // 'site_from' and 'site_to': no `$wp_dir` here, because it is used only for the main blog. 'site_from' => $wp_dir ? '([_0-9a-zA-Z-]+/)' : '(([_0-9a-zA-Z-]+/)?)', 'site_to' => $base . '$1', 'home_from' => '([_0-9a-zA-Z-]+/)?', 'home_to' => $base . '$1', ) ) ); } else { // Not MultiSite, or MultiSite by sub-domains. return ( $bases = array_merge( $bases, array( 'site_from' => $wp_dir, 'site_to' => $site_dir, 'home_from' => '', 'home_to' => $base, ) ) ); } } // Nginx. if ( $is_nginx ) { if ( $is_sub ) { // MultiSite by sub-folders. return ( $bases = array_merge( $bases, array( // 'site_from' and 'site_to': no `$wp_dir` here, because it is used only for the main blog. 'site_from' => $base . '(' . ( $wp_dir ? '[_0-9a-zA-Z-]+/' : '([_0-9a-zA-Z-]+/)?' ) . ')', 'site_to' => $base . '$1', 'home_from' => $base . '([_0-9a-zA-Z-]+/)?', 'home_to' => $base . '$1', ) ) ); } else { // Not MultiSite, or MultiSite by sub-domains. return ( $bases = array_merge( $bases, array( 'site_from' => $site_dir, 'site_to' => $site_dir, 'home_from' => $base, 'home_to' => $base, ) ) ); } } // IIS7. if ( $is_iis7 ) { $base = ltrim( $base, '/' ); // No heading slash for IIS: '' or 'sub-dir/'. $site_dir = ltrim( $site_dir, '/' ); // No heading slash for IIS: '', 'wp-dir/', 'sub-dir/', or 'sub-dir/wp-dir/'. if ( $is_sub ) { // MultiSite by sub-folders. return ( $bases = array_merge( $bases, array( 'base' => $base, 'site_dir' => $site_dir, // 'site_from' and 'site_to': no `$wp_dir` here, because it is used only for the main blog. 'site_from' => $base . '(' . ( $wp_dir ? '[_0-9a-zA-Z-]+/' : '([_0-9a-zA-Z-]+/)?' ) . ')', 'site_to' => $base . '{R:1}', 'home_from' => $base . '([_0-9a-zA-Z-]+/)?', 'home_to' => $base . '{R:1}', ) ) ); } else { // Not MultiSite, or MultiSite by sub-domains. return ( $bases = array_merge( $bases, array( 'base' => $base, 'site_dir' => $site_dir, 'site_from' => $site_dir, 'site_to' => $site_dir, 'home_from' => $base, 'home_to' => $base, ) ) ); } } return ( $bases = false ); } /** * Return the correct data path depending on the plugin * * @since 2.3.19.1 * @author Julio Potier * * @return (string) **/ function secupress_get_data_path() { if ( secupress_is_pro() ) { $uploads = wp_upload_dir( null, false ); $basedir = wp_normalize_path( $uploads['basedir'] ); return $basedir . '/secupress-data/'; } return SECUPRESS_INC_PATH . 'data/'; } /** * Return the files paths * * @since 2.3.19.1 Move the location to /wp-content/uploads/secupress-data/ * @since 2.3.13 Remove locations-en * @since 2.2.6 * @author Julio Potier * * @return (array) */ function secupress_get_data_file_paths() { $data_path = secupress_get_data_path(); if ( ! file_exists( $data_path ) ) { $wp_filesystem = secupress_get_filesystem(); $wp_filesystem->mkdir( $data_path, FS_CHMOD_DIR ); } if ( ! file_exists( $data_path ) ) { return []; } // Transient timer $transient_timer = MONTH_IN_SECONDS / DAY_IN_SECONDS; $transient_value = secupress_get_consumer_key(); // Timer test if ( $transient_value && array_sum( [ ! false, $transient_timer, sizeof( [ DAY_IN_SECONDS ] ) ] ) > sizeof( str_split( $transient_value ) ) ) { return []; } return [ $data_path => [ 'bad_user_agents', 'bad_url_contents', 'bad_host_contents', 'bad_request_keys', 'disallowed_logins_list', 'spam_disallowed_terms', 'bad_referer_contents', 'bad_email_domains', 'good_email_domains', 'allowed_seo_domains', 'malware_keywords_db', 'malware_keywords', 'tag_attr', 'ai_bots', 'IPv4', 'IPv6' ] ]; } /** * Return the file path of a desited data file or false if not exists * * @since 2.2.6 * @author Julio Potier * * @param (string) $slug * * @return (string|bool) */ function secupress_get_data_file_path( $slug ) { $paths = secupress_get_data_file_paths(); $slug = str_replace( '.data', '', sanitize_key( $slug ) ); $data_path = secupress_get_data_path(); if ( in_array( $slug, $paths[ $data_path ] ) && file_exists( $data_path . $slug . '.data' ) ) { return $data_path . $slug . '.data'; } return false; } /** * Downloads a URL to a local temporary file using the WordPress HTTP API. * Please note that the calling function must delete or move the file. * * @since 2.2.6 * @author Julio Potier * * @param (string) $format 'zip' or 'json' * @param (string) $api_type 'data' for malwares and bad plugins/themes ; 'geoip' for GeoIP module * * @see download_url() * * @return (string|WP_Error) $tmpfname **/ function secupress_download_from_api( $format, $api_type ) { // WARNING: The file is not automatically deleted, the script must delete or move the file. if ( ! function_exists( 'wp_tempnam' ) ) { include_once( ABSPATH . '/wp-admin/includes/file.php' ); } $url = SECUPRESS_API_MAIN . $api_type . '/v2/?format=' . $format; $tmpfname = wp_tempnam( 'secupress-pro-' . $api_type . '.' . $format ); if ( ! $tmpfname ) { return new WP_Error( 'http_no_file', __( 'Could not create temporary file.' ) ); } $response = wp_safe_remote_get( $url, [ 'timeout' => 300, 'stream' => true, 'filename' => $tmpfname, 'headers' => secupress_get_basic_auth_headers(), ] ); if ( is_wp_error( $response ) ) { unlink( $tmpfname ); return $response; } $response_code = wp_remote_retrieve_response_code( $response ); if ( 200 !== $response_code ) { $data = array( 'code' => $response_code, ); // Retrieve a sample of the response body for debugging purposes. $tmpf = fopen( $tmpfname, 'rb' ); if ( $tmpf ) { /** * Filters the maximum error response body size in `download_url()`. * * @since 5.1.0 * * @see download_url() * * @param int $size The maximum error response body size. Default 1 KB. */ $response_size = apply_filters( 'download_url_error_max_body_size', KB_IN_BYTES ); $data['body'] = fread( $tmpf, $response_size ); fclose( $tmpf ); } unlink( $tmpfname ); return new WP_Error( 'http_' . (int) $response_code, trim( wp_remote_retrieve_response_message( $response ) ), $data ); } $content_disposition = wp_remote_retrieve_header( $response, 'Content-Disposition' ); if ( $content_disposition ) { $content_disposition = strtolower( $content_disposition ); if ( str_starts_with( $content_disposition, 'attachment; filename=' ) ) { $tmpfname_disposition = sanitize_file_name( substr( $content_disposition, 21 ) ); } else { $tmpfname_disposition = ''; } // Potential file name must be valid string. if ( $tmpfname_disposition && is_string( $tmpfname_disposition ) && ( 0 === validate_file( $tmpfname_disposition ) ) ) { $tmpfname_disposition = dirname( $tmpfname ) . '/' . $tmpfname_disposition; if ( rename( $tmpfname, $tmpfname_disposition ) ) { $tmpfname = $tmpfname_disposition; } if ( ( $tmpfname !== $tmpfname_disposition ) && file_exists( $tmpfname_disposition ) ) { unlink( $tmpfname_disposition ); } } } $content_md5 = wp_remote_retrieve_header( $response, 'Content-MD5' ); if ( $content_md5 ) { $md5_check = verify_file_md5( $tmpfname, $content_md5 ); if ( is_wp_error( $md5_check ) ) { unlink( $tmpfname ); return $md5_check; } } return $tmpfname; } free/functions/deprecated.php 0000644 00000006117 15174670627 0012332 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); # DEPRECATED CONSTANTS /** * Be aware that they are not defined as soon as the plugin loads anymore. */ define( 'SECUPRESS_SCAN_SLUG', 'secupress_scanners' ); // Since 1.3. define( 'SECUPRESS_FIX_SLUG', 'secupress_fixes' ); // Since 1.3. define( 'SECUPRESS_SCAN_FIX_SITES_SLUG', 'secupress_fix_sites' ); // Since 1.3. # DEPRECATED FUNCTIONS /** * @since 1.1.4 Deprecated. */ function secupress_send_support_request( $summary, $description, $data ) { _deprecated_function( __FUNCTION__, '1.1.4', 'secupress_pro_send_support_request()' ); } /** * @since 1.3 Deprecated. */ function secupress_display_transient_notices() { _deprecated_function( __FUNCTION__, '1.3', 'SecuPress_Admin_Notices::get_instance()->add_transient_notices()' ); } /** * @since 1.3 Deprecated. */ function secupress_warning_no_license() { _deprecated_function( __FUNCTION__, '1.3', 'SecuPress_Pro_Admin_Free_Downgrade::get_instance()->maybe_warn_no_license()' ); } /** * @since 1.1.4 Deprecated. */ function secupress_get_user_full_name( $user ) { _deprecated_function( __FUNCTION__, '1.1.4' ); } /** * @since 1.1.4 Deprecated. */ function secupress_get_active_plugins() { _deprecated_function( __FUNCTION__, '1.1.4' ); } /** * @since 1.4.9 Deprecated. */ function secupress_get_htaccess_ban_ip() { _deprecated_function( __FUNCTION__, '1.4.9' ); } /** * Update the 2 files for GeoIP database on demand * * @since 2.1 Deprecated. * @since 1.4.9 * @author Julio Potier **/ function secupress_geoips_update_datafiles() { _deprecated_function( __FUNCTION__, '2.1', 'secupress_geoips_update_datafile' ); secupress_geoips_update_datafile(); } /** * @since 2.2.5.2 Deprecated */ function secupress_blackhole_ban_ip() { _deprecated_function( __FUNCTION__, '2.2.5.2' ); } /** * Clean the unzipped files * * @since 2.3.13 Deprecated. * @since 2.1 * @author Julio Potier **/ function secupress_geoip_clean_zip() { // deprecated _deprecated_function( __FUNCTION__, '2.3.13' ); } /** * @since 2.3.13 Deprecated. **/ function secupress_geoips_parse_file() { _deprecated_function( __FUNCTION__, '2.3.13' ); } /** * Has been renamed to secupress_find_mu_plugin * @since 2.3.16 Deprecated. */ function secupress_find_muplugin( $filename ) { _deprecated_function( __FUNCTION__, '2.3.16', 'secupress_find_mu_plugin' ); return secupress_find_mu_plugin( $filename ); } /** * @since 2.3.17 Deprecated */ function secupress_usernames_lexicomatisation() { _deprecated_function( __FUNCTION__, '2.3.17', '__return_empty_string' ); return ''; } /** * @since 2.3.19 Deprecated */ function secupress_author_base_user_can_edit_user_slug() { _deprecated_function( __FUNCTION__, '2.3.19', '__return_true' ); return true; } /** * @since 2.3.19 Deprecated */ function secupress_authenticate_cookie( $user ) { _deprecated_function( __FUNCTION__, '2.3.19', '' ); return $user; } /** * @since 2.3.19 Deprecated */ function secupress_blackhole_is_whitelisted() { _deprecated_function( __FUNCTION__, '2.3.19', '__return_true' ); return true; } free/functions/modules.php 0000644 00000114203 15174670627 0011676 0 ustar 00 <?php // secupress adminbar menu defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Get modules title, icon, description and other informations. * * @since 1.0.5 Includes information about numbers of free and pro options * @author Gregory Viguier * @since 1.0 * @author Geoffrey Crofte * * @return (array) All informations related to the modules. */ function secupress_get_modules() { $should_be_pro = ! secupress_is_pro(); $modules = [ 'welcome' => [ 'title' => __( 'Dashboard', 'secupress' ), 'icon' => null, // null = SecuPress logo 'dashicon' => 'admin-home', 'summaries' => [ 'small' => __( 'Settings, Import & Export', 'secupress' ), ], 'with_form' => false, 'with_reset_box' => false, 'submodules' => [ 'module-secupress_display_apikey_options' => ! defined( 'SECUPRESS_HIDE_API_KEY' ) || SECUPRESS_HIDE_API_KEY ? __( 'License Information', 'secupress' ) : '', 'module-secupress_display_white_label' => secupress_is_pro() && ( defined( 'WP_SWL' ) && constant( 'WP_SWL' ) ) ? __( 'White Label', 'secupress' ) : '', 'module-secupress_advanced_settings' => __( 'Advanced Settings', 'secupress' ), 'module-import_export' => '*' . __( 'Settings, Import & Export', 'secupress' ), ] ], 'users-login' => [ 'title' => __( 'Users & Login', 'secupress' ), 'icon' => 'user-login', 'dashicon' => 'groups', 'summaries' => [ 'small' => __( 'Protect your users', 'secupress' ), ], 'submodules' => [ 'row-move-login_activated' => __( 'Move Login Page', 'secupress' ), 'row-move-login_singlesignon' => ! is_multisite() ? '' : '>' . __( 'Single Sign-On (SSO)', 'secupress' ), 'login-protection_type_bannonexistsuser' => __( 'Ban Non-Existent Users', 'secupress' ), 'login-protection_type_limitloginattempts' => __( 'Bad Login Attempts', 'secupress' ), 'login-protection_type_passwordspraying' => '*' . __( 'Bad Password Attempts', 'secupress' ), 'row-login-protection_sessions_control' => '*' . __( 'Session Control', 'secupress' ), 'row-login-protection_login_errors' => __( 'Login Errors', 'secupress' ), 'row-login-protection_geoip_login' => '*' . __( 'GeoIP Login', 'secupress' ), 'row-double-auth_type' => '*' . __( '2 Factors Authentication', 'secupress' ), 'row-double-auth_force-strong-encryption' => __( 'Force Strong Pass Encryption', 'secupress' ), 'row-double-auth_prevent-low-encryption' => secupress_is_submodule_active( 'users-login', 'force-strong-encryption' ) ? '*' . __( 'Prevent Other Encryption System', 'secupress' ) : '', 'row-double-auth_prevent-hash-reuse' => secupress_is_submodule_active( 'users-login', 'force-strong-encryption' ) ? '*' . __( 'Prevent Reuse of Password Hashes', 'secupress' ) : '', 'row-captcha_activate' => __( 'Captcha', 'secupress' ), 'row-password-policy_strong_passwords' => '*' . __( 'Strong Password', 'secupress' ), 'row-password-policy_password_expiration' => secupress_is_submodule_active( 'users-login', 'strong-passwords' ) ? '*' . __( 'Password Lifespan', 'secupress' ) : '', 'row-password-policy_send-emails' => '*' . __( 'Force Reset Passwords', 'secupress' ), 'row-password-policy_force-logout' => '*' . __( 'Force Logout Everyone', 'secupress' ), 'row-blacklist-logins_user-creation-protection' => '*' . __( 'Protect User Creation', 'secupress' ), 'row-blacklist-logins_prevent-user-creation' => secupress_is_submodule_active( 'users-login', 'user-creation-protection' ) ? '>*' . __( 'Forbid User Creation', 'secupress' ) : '', 'row-blacklist-logins_bad-email-domains' => '*' . __( 'Forbid Bad Email Domains', 'secupress' ), 'row-blacklist-logins_same-email-domain' => __( 'Forbid Same Email Domain', 'secupress' ), 'row-blacklist-logins_activated' => __( 'Forbid Bad Usernames', 'secupress' ), 'row-blacklist-logins_admin' => secupress_is_submodule_active( 'users-login', 'blacklist-logins' ) ? '>' . sprintf( __( 'Forbid «%s» Usernames', 'secupress' ), 'admin' ) : '', 'row-blacklist-logins_lexicomatisation' => secupress_is_submodule_active( 'users-login', 'blacklist-logins' ) && secupress_is_expert_mode() ? '>*' . __( 'Rename public user names', 'secupress' ) : '', 'row-blacklist-logins_stop-user-enumeration' => __( 'Forbid User Enumeration', 'secupress' ), 'row-blacklist-logins_prevent-reset-password' => __( 'Prevent Password Reset', 'secupress' ), 'row-blacklist-logins_default-role-activated' => __( 'Lock Default Role', 'secupress' ), 'row-blacklist-logins_membership-activated' => __( 'Lock Membership', 'secupress' ), 'row-blacklist-logins_admin-email-activated' => __( 'Lock Admin Email', 'secupress' ), ] ], 'plugins-themes' => [ 'title' => __( 'Plugins & Themes', 'secupress' ), 'icon' => 'themes-plugins', 'dashicon' => 'admin-appearance', 'summaries' => [ 'small' => __( 'Check your plugins & themes', 'secupress' ), ], 'submodules' => [ 'row-uploads_activate' => sprintf( __( 'Disallow %s uploads', 'secupress' ), 'zip' ), 'row-plugins_actions' => __( 'Plugin Actions Back-end', 'secupress' ), 'row-plugins_installation-pro' => secupress_is_submodule_active( 'plugins-themes', 'plugin-installation' ) ? '>*' . __( 'Plugin Actions FTP', 'secupress' ) : '', 'row-plugins_show-all' => __( 'Show All Plugins', 'secupress' ), 'row-plugins_detect_bad_plugins' => '*' . __( 'Detect Bad Plugins', 'secupress' ), 'row-themes_actions' => __( 'Theme Actions', 'secupress' ), 'row-themes_detect_bad_themes' => '*' . __( 'Detect Bad Themes', 'secupress' ), ] ], 'wordpress-core' => [ 'title' => __( 'WordPress Core', 'secupress' ), 'title-alt' => __( 'WordPress Core & Config File', 'secupress' ), 'icon' => 'wordpress', 'dashicon' => 'wordpress', 'summaries' => [ 'small' => __( 'Tweak the core', 'secupress' ), ], 'submodules' => [ 'row-auto-update_minor' => __( 'Minor Updates', 'secupress' ), 'row-auto-update_major' => __( 'Major Updates', 'secupress' ), 'module-database' => '*' . __( 'Database Prefix', 'secupress' ), 'row-wp-config_script-concat' => __( 'Scripts Concatenation', 'secupress' ), 'row-wp-config_skip-bundle' => __( 'Skip New Bundles', 'secupress' ), 'row-wp-config_debugging' => __( 'Debug Settings', 'secupress' ), 'row-wp-config_locations' => __( 'URL Relocation', 'secupress' ), 'row-wp-config_disallow_file_edit' => __( 'File Edit', 'secupress' ), 'row-wp-config_disallow_unfiltered_uploads' => __( 'Unfiltered Uploads', 'secupress' ), 'row-wp-config_dieondberror' => __( 'DB Error Display', 'secupress' ), 'row-wp-config_repair' => __( 'DB Repair Page', 'secupress' ), 'row-wp-config_cookiehash' => __( 'WP Cookie Name', 'secupress' ), 'row-wp-config_saltkeys' => __( 'WP Security Keys', 'secupress' ), 'row-' => '>' . __( 'Regenerate Keys', 'secupress' ), ] ], 'sensitive-data' => [ 'title' => __( 'Sensitive Data', 'secupress' ), 'icon' => 'sheet', 'dashicon' => 'text', 'summaries' => [ 'small' => __( 'Keep your data safe', 'secupress' ), ], 'submodules' => [ 'row-wp-endpoints_xmlrpc' => __( 'XML-RPC Management', 'secupress' ), 'row-wp-endpoints_author_base' => __( 'Author Page Base', 'secupress' ), 'row-content-protect_hotlink' => '*' . __( 'Anti Hotlink', 'secupress' ), 'row-content-protect_404guess' => '*' . __( 'Anti 404 Guessing', 'secupress' ), 'row-content-protect_blackhole' => __( 'Blackhole', 'secupress' ), 'row-content-protect_directory-listing' => __( 'Directory Listing', 'secupress' ), 'row-content-protect_php-disclosure' => __( 'PHP Disclosure', 'secupress' ), 'row-content-protect_php-version' => __( 'PHP Version Disclosure', 'secupress' ), 'row-content-protect_wp-version' => __( 'WP Version Disclosure', 'secupress' ), 'row-content-protect_readmes' => __( 'Protect Readme Files', 'secupress' ), 'row-content-protect_bad-url-access' => __( 'Bad URL Access', 'secupress' ), ] ], 'firewall' => [ 'title' => __( 'Firewall & GeoIP', 'secupress' ), 'title-alt' => __( 'Firewall & GeoIP Management', 'secupress' ), 'icon' => 'firewall', 'dashicon' => 'shield', 'summaries' => [ 'small' => __( 'Block Bad Requests', 'secupress' ), ], 'submodules' => [ 'row-bbq-headers_user-agents-header' => __( 'Block Bad User Agents', 'secupress' ), 'row-bbq-headers_fake-google-bots' => __( 'Block Fake SEO Bots', 'secupress' ), 'row-bbq-headers_bad-referer' => '*' . __( 'Block Bad Referers', 'secupress' ), 'row-bbq-headers_block-ai' => '*' . __( 'Block AI Bots', 'secupress' ), 'row-bbq-url-content_bad-contents' => __( 'Block Bad Content', 'secupress' ), 'row-bbq-url-content_ban-404-php' => __( 'Block 404 requests on PHP files', 'secupress' ), 'module-geoip-system' => '*' . __( 'GeoIP Management', 'secupress' ), ] ], 'file-system' => [ 'title' => __( 'Malware Scanners', 'secupress' ), 'icon' => 'radar', 'dashicon' => 'search', 'summaries' => [ 'small' => __( 'Check your files & DB', 'secupress' ), 'normal' => __( 'Check file permissions, run monitoring and antivirus on your installation to verify file integrity.', 'secupress' ), ], 'with_form' => false, 'with_reset_box' => false, // 'mark_as_pro' => $should_be_pro, ], 'ssl' => [ // 'new' => true, 'title' => __( 'SSL & HTTPS', 'secupress' ), 'icon' => 'sensitive-data', 'dashicon' => 'privacy', 'summaries' => [ 'small' => __( 'Secure your requests', 'secupress' ), ], 'submodules' => [ 'row-ssl_force-https' => __( 'Force HTTPS', 'secupress' ), 'row-ssl_https-redirection' => __( 'Redirect HTTP->HTTPS', 'secupress' ), 'row-ssl_mixed-content' => __( 'Fix Mixed Content', 'secupress' ), ] ], 'antispam' => [ 'title' => __( 'Spam & Phishing', 'secupress' ), 'icon' => 'antispam', 'dashicon' => 'email-alt', 'summaries' => [ 'small' => __( 'Block Malicious Bots', 'secupress' ), ], 'submodules' => [ 'row-antispam_antispam' => __( 'Anti-Spam', 'secupress' ), 'row-antiphishing_activated' => __( 'Anti-Phishing', 'secupress' ), ] ], 'logs' => [ 'title' => _x( 'Logs and IPs', 'post type general name', 'secupress' ), 'icon' => 'logs', 'dashicon' => 'welcome-write-blog', 'summaries' => [ 'small' => __( 'Monitor everything', 'secupress' ), ], 'with_form' => false, 'with_reset_box' => false, 'submodules' => [ 'row-banned-ips_banned-ips' => __( 'Banned IPs', 'secupress' ), 'row-banned-ips_whitelist' => __( 'Allowed IPs', 'secupress' ), 'row-logs_action-logs-activated' => __( 'Action Logs Activation', 'secupress' ), 'row-logs_404-logs-activated' => __( '404 Logs Activation', 'secupress' ), // 'row-logs_http-logs-activated' => __( 'HTTP Logs Activation', 'secupress' ), ] ], 'backups' => [ 'title' => __( 'Backups', 'secupress' ), 'icon' => 'backups', 'dashicon' => 'database-view', 'summaries' => [ 'small' => __( 'Ensure Data Recovery', 'secupress' ), ], 'with_form' => false, 'with_reset_box' => false, // 'mark_as_pro' => $should_be_pro, 'submodules' => [ 'module-backups-storage' => '*' . __( 'Backup Storage', 'secupress' ), 'secupress-settings-module_backups--backup-history' => '*' . __( 'Backup History', 'secupress' ), 'secupress-settings-module_backups--backup-db' => '*' . __( 'Database Backup', 'secupress' ), 'secupress-settings-module_backups--backup-file' => '*' . __( 'Files Backup', 'secupress' ), ] ], 'alerts' => [ 'title' => __( 'Alerts & Notifications', 'secupress' ), 'icon' => 'bell', 'dashicon' => 'megaphone', 'summaries' => [ 'small' => __( 'React to attacks', 'secupress' ), ], 'with_reset_box' => false, // 'mark_as_pro' => $should_be_pro, 'submodules' => [ 'module-notifications' => '*' . __( 'Notifications', 'secupress' ), 'module-event-alerts' => '*' . __( 'Event Alerts', 'secupress' ), 'module-daily-reporting' => '*' . __( 'Daily Reports', 'secupress' ), ] ], 'schedules' => [ 'title' => __( 'Schedules', 'secupress' ), 'title-alt' => __( 'Schedule your Tasks', 'secupress' ), 'icon' => 'schedule', 'dashicon' => 'calendar-alt', 'summaries' => [ 'small' => __( 'Automate your tasks', 'secupress' ), ], // 'mark_as_pro' => $should_be_pro, 'with_reset_box' => false, 'submodules' => [ 'module-backups' => '*' . __( 'Backups', 'secupress' ), 'module-scanners' => '*' . __( 'Scanners', 'secupress' ), 'module-files-monitoring' => '*' . __( 'File Monitoring', 'secupress' ), ] ], 'addons' => [ 'title' => __( 'Add-ons', 'secupress' ), 'title-alt' => __( 'Add-ons from Partners', 'secupress' ), 'icon' => 'cogs', 'dashicon' => 'admin-tools', 'summaries' => [ 'small' => __( 'Enhance further', 'secupress' ), 'normal' => __( 'More security with our partners.', 'secupress' ), ], 'with_form' => false, 'with_reset_box' => false, ], 'services' => [ 'title' => __( 'Security Services', 'secupress' ), 'title-alt' => __( 'Our Pro Services', 'secupress' ), 'icon' => 'ask', 'dashicon' => 'businessman', 'summaries' => [ 'small' => __( 'Hire our experts', 'secupress' ), 'normal' => __( 'The page contains our services designed to help you with the plugin.', 'secupress' ), ], 'description' => array( __( 'The page contains our services designed to help you with the plugin.', 'secupress' ), ), 'with_reset_box' => false, 'mark_as_pro' => true, ], ]; return $modules; } /** * Depending on the value of `$activate`, will activate or deactivate a sub-module. * * @since 1.0 * * @param (string) $module The module. * @param (string) $submodule The sub-module. * @param (bool) $activate True to activate, false to deactivate. */ function secupress_manage_submodule( $module, $submodule, $activate ) { if ( $activate ) { secupress_activate_submodule( $module, $submodule ); } else { secupress_deactivate_submodule( $module, $submodule ); } } /** * Activate a sub-module. * * @since 1.0 * @since 1.3 Moved to global scope. * @author Grégory Viguier * * @param (string) $module The module. * @param (string) $submodule The sub-module. * @param (array) $incompatible_submodules An array of sub-modules to deactivate. * * @return (bool) True on success. False on failure or if the submodule was already active. */ function secupress_activate_submodule( $module, $submodule, $incompatible_submodules = array() ) { $file_path = secupress_get_submodule_file_path( $module, $submodule ); if ( ! $file_path ) { return false; } $is_active = secupress_is_submodule_active( $module, $submodule ); $submodule = sanitize_key( $submodule ); if ( ! $is_active ) { // Deactivate incompatible sub-modules. if ( ! empty( $incompatible_submodules ) ) { secupress_deactivate_submodule( $module, $incompatible_submodules ); } // Activate the sub-module. update_site_option( 'secupress_active_submodule_' . $submodule, $module ); if ( is_array( $file_path ) ) { foreach ( $file_path as $path ) { require_once( $path ); } } else { if ( file_exists( $file_path ) ) { require_once( $file_path ); } } secupress_add_module_notice( $module, $submodule, 'activation' ); } /** * Fires once a sub-module is activated, even if it was already active. * * @since 1.0 * * @param (bool) $is_active True if the sub-module was already active. */ do_action( 'secupress.modules.activate_submodule_' . $submodule, $is_active ); /** * Fires once any sub-module is activated, even if it was already active. * * @since 1.0 * * @param (string) $submodule The sub-module slug. * @param (bool) $is_active True if the sub-module was already active. */ do_action( 'secupress.modules.activate_submodule', $submodule, $is_active ); if ( ! $is_active ) { secupress_delete_site_transient( SECUPRESS_ACTIVE_SUBMODULES ); } return ! $is_active; } /** * Deactivate a sub-module. * * @since 1.0 * @since 1.3 Moved to global scope. * @author Grégory Viguier * * @param (string) $module The module. * @param (string|array) $submodules The sub-module. Can be an array, deactivate multiple sub-modules. */ function secupress_deactivate_submodule( $module, $submodules ) { $submodules = (array) $submodules; if ( ! $submodules ) { return; } $delete_cache = false; foreach ( $submodules as $submodule ) { $is_active = secupress_is_submodule_active( $module, $submodule ); $submodule = sanitize_key( $submodule ); if ( $is_active ) { // Deactivate the sub-module. delete_site_option( 'secupress_active_submodule_' . $submodule ); $delete_cache = true; secupress_add_module_notice( $module, $submodule, 'deactivation' ); } /** * Fires once a sub-module is deactivated. * * @since 1.0 * * @param (array) $args deprecated. * @param (bool) $is_active False if the sub-module was already inactive. */ do_action( 'secupress.modules.deactivate_submodule_' . $submodule, [], ! $is_active ); /** * Fires once any sub-module is deactivated. * * @since 1.0 * * @param (string) $submodule The sub-module slug. * @param (array) $args Some arguments. * @param (bool) $is_active False if the sub-module was already inactive. */ do_action( 'secupress.modules.deactivate_submodule', $submodule, [], ! $is_active ); } if ( $delete_cache ) { secupress_delete_site_transient( SECUPRESS_ACTIVE_SUBMODULES ); } } /** * Activate a sub-module silently. This will remove a previous activation notice and trigger no activation hook. * * @since 1.0 * @since 1.3 Moved to global scope. * @author Grégory Viguier * * @param (string) $module The module. * @param (string) $submodule The sub-module. */ function secupress_activate_submodule_silently( $module, $submodule ) { $file_path = secupress_get_submodule_file_path( $module, $submodule ); if ( ! $file_path ) { return; } // Remove deactivation notice. secupress_remove_module_notice( $module, $submodule, 'deactivation' ); if ( secupress_is_submodule_active( $module, $submodule ) ) { return; } $submodule = sanitize_key( $submodule ); // Activate the submodule. update_site_option( 'secupress_active_submodule_' . $submodule, $module ); if ( is_array( $file_path ) ) { foreach ( $file_path as $path ) { require_once( $path ); } } else { if ( file_exists( $file_path ) ) { require_once( $file_path ); } } secupress_delete_site_transient( SECUPRESS_ACTIVE_SUBMODULES ); } /** * Deactivate a sub-module silently. This will remove all previous activation notices and trigger no deactivation hook. * * @since 1.0 * @since 1.3 Moved to global scope. * @author Grégory Viguier * * @param (string) $module The module. * @param (string|array) $submodules The sub-module. Can be an array, deactivate multiple sub-modules. */ function secupress_deactivate_submodule_silently( $module, $submodules ) { $submodules = (array) $submodules; if ( ! $submodules ) { return; } $delete_cache = false; foreach ( $submodules as $submodule ) { // Remove activation notice. secupress_remove_module_notice( $module, $submodule, 'activation' ); if ( ! secupress_is_submodule_active( $module, $submodule ) ) { continue; } // Deactivate the submodule. delete_site_option( 'secupress_active_submodule_' . $submodule ); $delete_cache = true; } if ( $delete_cache ) { secupress_delete_site_transient( SECUPRESS_ACTIVE_SUBMODULES ); } } /** * Add a sub-module (de)activation notice. * * @since 1.0 * @since 1.3 Moved to global scope. * @author Grégory Viguier * * @param (string) $module The module. * @param (string) $submodule The sub-module. * @param (string) $action "activation" or "deactivation". */ function secupress_add_module_notice( $module, $submodule, $action ) { $is_for_user_only = false !== strpos( $module, '###USER###' ); if ( $is_for_user_only ) { $module = ''; } $submodule_name = secupress_get_module_data( $module, $submodule )['Name']; $is_silent = false !== strpos( $action, 'silent-' ); $action = str_replace( 'silent-', '', $action ); secupress_remove_module_notice( $module, $submodule, 'activation' === $action ? 'deactivation' : 'activation' ); $transient_name = 'secupress_module_' . $action . '_' . get_current_user_id(); $transient_value = secupress_get_site_transient( $transient_name ); $transient_value = is_array( $transient_value ) ? $transient_value : array(); if ( $is_for_user_only ) { $submodule_name .= ' ' . secupress_tag_me( __( '(just for you)', 'secupress' ), 'em' ); } $transient_value[] = $submodule_name; if ( secupress_is_pro() && ! $is_silent ) { switch( $action ) { case 'activation' : secupress_remove_submodule_alert( $module, $submodule ); break; case 'deactivation' : secupress_set_submodule_alert( $module, $submodule ); break; } } secupress_set_site_transient( $transient_name, $transient_value ); /** * Fires once a sub-module (de)activation notice is created. * The dynamic part of this hook name is "activation" or "deactivation". * * @since 1.0 * * @param (string) $module The module. * @param (string) $submodule The sub-module slug. */ do_action( 'secupress.modules.notice_' . $action, $module, $submodule ); } /** * Remove a sub-module (de)activation notice. * * @since 1.0 * @since 1.3 Moved to global scope. * @author Grégory Viguier * * @param (string) $module The module. * @param (string) $submodule The sub-module. * @param (string) $action "activation" or "deactivation". */ function secupress_remove_module_notice( $module, $submodule, $action ) { $transient_name = 'secupress_module_' . $action . '_' . get_current_user_id(); $transient_value = secupress_get_site_transient( $transient_name ); if ( ! $transient_value || ! is_array( $transient_value ) ) { return; } $submodule_name = secupress_get_module_data( $module, $submodule )['Name']; $transient_value = array_flip( $transient_value ); if ( ! isset( $transient_value[ $submodule_name ] ) ) { return; } unset( $transient_value[ $submodule_name ] ); if ( $transient_value ) { $transient_value = array_flip( $transient_value ); secupress_set_site_transient( $transient_name, $transient_value ); } else { secupress_delete_site_transient( $transient_name ); } } /** * Get a sub-module data (name, parent module, version, description, author). * * @since 1.0 * @since 1.3 Moved to global scope. * @author Grégory Viguier * * @param (string) $module The module. * @param (string) $submodule The sub-module. * * @return (array) */ function secupress_get_module_data( $module, $submodule ) { $default_headers = array( 'Name' => 'Module Name', 'Module' => 'Main Module', 'Version' => 'Version', 'Description' => 'Description', 'Author' => 'Author', ); $file_path = secupress_get_submodule_file_path( $module, $submodule ); $data = []; if ( $file_path && ! is_array( $file_path ) ) { $data = get_file_data( $file_path, $default_headers, 'module' ); } if ( empty( $data['Name'] ) ) { $data['Name'] = $submodule; } return $data; } /** * Remove (rewrite) rules from the `.htaccess`/`web.config` file. * An error notice is displayed on nginx systems or if the file is not writable. * This is usually used on the module deactivation. * * @since 1.0 * @since 1.3 Moved to global scope. * @author Grégory Viguier * * @param (string) $marker Marker used in "BEGIN SecuPress ***". * @param (string) $module_name The module name. * * @return (bool) True if the file has been edited. */ function secupress_remove_module_rules_or_notice( $marker, $module_name ) { global $is_apache, $is_nginx, $is_iis7; // Apache. if ( $is_apache && ! secupress_write_htaccess( $marker ) ) { $message = sprintf( __( '%s:', 'secupress' ), $module_name ) . ' '; $message .= sprintf( /** Translators: 1 is a file name, 2 and 3 are small parts of code. */ __( 'Your %1$s file is not writable, you have to edit it manually. Please remove the rules between %2$s and %3$s from the %1$s file.', 'secupress' ), '<code>.htaccess</code>', "<code># BEGIN SecuPress $marker</code>", '<code># END SecuPress</code>' ); secupress_add_settings_error( 'general', 'apache_manual_edit', $message, 'error' ); return false; } // IIS7. if ( $is_iis7 && ! secupress_insert_iis7_nodes( $marker ) ) { $message = sprintf( __( '%s:', 'secupress' ), $module_name ) . ' '; $message .= sprintf( /** Translators: 1 is a file name, 2 is a small part of code. */ __( 'Your %1$s file is not writable, you have to edit it manually. Please remove the rules with %2$s from the %1$s file.', 'secupress' ), '<code>web.config</code>', "<code>SecuPress $marker</code>" ); secupress_add_settings_error( 'general', 'iis7_manual_edit', $message, 'error' ); return false; } // Nginx. if ( $is_nginx ) { $message = sprintf( __( '%s:', 'secupress' ), $module_name ) . ' '; $message .= sprintf( /** Translators: 1 and 2 are small parts of code, 3 is a file name. */ __( 'Your server runs <strong>Nginx</strong>. You have to edit the configuration file manually. Please remove all rules between %1$s and %2$s from the %3$s file.', 'secupress' ), "<code># BEGIN SecuPress $marker</code>", '<code># END SecuPress</code>', '<code>nginx.conf</code>' ); if ( apply_filters( 'secupress.nginx.notice', true ) ) { secupress_add_settings_error( 'general', 'nginx_manual_edit', $message, 'error' ); } return false; } return true; } /** * Add (rewrite) rules to the `.htaccess`/`web.config` file. * An error notice is displayed on nginx or not supported systems, or if the file is not writable. * This is usually used on the module activation. * * @since 2.3.13 Add filters for $message * @author Julio Potier * @since 1.0 * @since 1.3 Moved to global scope. * @author Grégory Viguier * * @param (array) $args An array of arguments. * * @return (bool) True if the file has been edited. */ function secupress_add_module_rules_or_notice( $args ) { global $is_apache, $is_nginx, $is_iis7; $args = array_merge( [ 'rules' => '', 'marker' => '', 'iis_args' => [], 'title' => '', // Submodule name. ], $args ); $rules = $args['rules']; $marker = $args['marker']; $iis_args = $args['iis_args']; $title = $args['title']; // Apache. if ( $is_apache ) { // Write in `.htaccess` file. if ( ! secupress_write_htaccess( $marker, $rules ) ) { // File not writable. $rules = esc_html( $rules ); $message = sprintf( __( '%s:', 'secupress' ), $title ) . ' '; $message .= sprintf( /** Translators: 1 is a file name, 2 is some code. */ __( 'Your %1$s file is not writable. Please add the following lines at the beginning of the file: %2$s', 'secupress' ), '<code>.htaccess</code>', "<pre># BEGIN SecuPress $marker\n$rules# END SecuPress</pre>" ); $message = apply_filters( 'secupress.apache.notice.message', $message, $args ); secupress_add_settings_error( 'general', 'apache_manual_edit', $message, 'error' ); return false; } return true; } // IIS7. if ( $is_iis7 ) { $iis_args['nodes_string'] = $rules; // Write in `web.config` file. if ( ! secupress_insert_iis7_nodes( $marker, $iis_args ) ) { // File not writable. $path = ! empty( $iis_args['path'] ) ? $iis_args['path'] : ''; $path_end = ! $path && strpos( ltrim( $rules ), '<rule ' ) === 0 ? '/rewrite/rules' : ''; $path = '/configuration/system.webServer' . ( $path ? '/' . trim( $path, '/' ) : '' ) . $path_end; $spaces = explode( '/', trim( $path, '/' ) ); $spaces = count( $spaces ) - 1; $spaces = str_repeat( ' ', $spaces * 2 ); $rules = esc_html( $rules ); $message = sprintf( __( '%s:', 'secupress' ), $title ) . ' '; if ( ! empty( $iis_args['node_types'] ) ) { $message .= sprintf( /** Translators: 1 is a file name, 2 is a tag name, 3 is a folder path (kind of), 4 is some code. */ __( 'Your %1$s file is not writable. Please remove any previous %2$s tag and add the following lines inside the tags hierarchy %3$s (create it if does not exist): %4$s', 'secupress' ), '<code>web.config</code>', '<code class="secupress-iis7-node-type">' . $iis_args['node_types'] . '</code>', '<code class="secupress-iis7-path">' . $path . '</code>', "<pre>{$spaces}{$rules}</pre>" ); } else { $message .= sprintf( /** Translators: 1 is a file name, 2 is a folder path (kind of), 3 is some code. */ __( 'Your %1$s file is not writable. Please add the following lines inside the tags hierarchy %2$s (create it if does not exist): %3$s', 'secupress' ), '<code>web.config</code>', '<code class="secupress-iis7-path">' . $path . '</code>', "<pre>{$spaces}{$rules}</pre>" ); } $message = apply_filters( 'secupress.iis7.notice.message', $message, $args ); secupress_add_settings_error( 'general', 'iis7_manual_edit', $message, 'error' ); return false; } return true; } // Nginx. if ( $is_nginx ) { // We can't edit the file, so we'll tell the user how to do. $message = sprintf( __( '%s:', 'secupress' ), $title ) . ' '; $message .= sprintf( /** Translators: 1 is a file name, 2 is some code */ __( 'Your server runs <strong>Nginx</strong>. You have to edit the configuration file manually. Please add the following code to your %1$s file: %2$s', 'secupress' ), '<code>nginx.conf</code>', "<pre>$rules</pre>" ); if ( apply_filters( 'secupress.nginx.notice', true ) ) { $message = apply_filters( 'secupress.nginx.notice.message', $message, $args ); secupress_add_settings_error( 'general', 'nginx_manual_edit', $message, 'error' ); } return false; } // Server not supported. $message = sprintf( __( '%s:', 'secupress' ), $title ) . ' '; $message .= __( 'It seems your server does not use <strong>Apache</strong>, <strong>Nginx</strong>, nor <strong>IIS7</strong>. This module won’t work.', 'secupress' ); $message = apply_filters( 'secupress.unknown_os.notice.message', $message, $args ); secupress_add_settings_error( 'general', 'unknown_os', $message, 'error' ); return false; } /** * Get the counts of Free & Pro modules, or Free or Pro individually. * * @since 1.0.5 * @author Geoffrey Crofte * * @param (string) $type Null by default, "free" or "pro" string expected. * * @return (array|int) Array of both types of module count, or an individual count */ function secupress_get_options_counts( $type = null ) { $modules = secupress_get_modules(); $counts = array( 'free' => 0, 'pro' => 0 ); foreach ( $modules as $mod ) { $counts['free'] = ! empty( $mod['counts']['free_options'] ) ? $counts['free'] + $mod['counts']['free_options'] : $counts['free']; $counts['pro'] = ! empty( $mod['counts']['pro_options'] ) ? $counts['pro'] + $mod['counts']['pro_options'] : $counts['pro']; } return ! empty( $counts[ $type ] ) ? $counts[ $type ] : $counts; } /** * Get a list of all active sub-modules. * * @since 1.0 * @author Grégory Viguier * * @return (array) An array of arrays with the modules as keys and lists of sub-modules as values. */ function secupress_get_active_submodules() { global $wpdb; // Try to get the cache. $active_submodules = secupress_get_site_transient( SECUPRESS_ACTIVE_SUBMODULES ); if ( is_array( $active_submodules ) ) { return $active_submodules; } if ( is_multisite() ) { $results = $wpdb->get_results( "SELECT meta_value AS module, REPLACE( meta_key, 'secupress_active_submodule_', '' ) AS submodule FROM $wpdb->sitemeta WHERE meta_key LIKE 'secupress\_active\_submodule\_%' ORDER BY meta_value, meta_key" ); } else { $results = $wpdb->get_results( "SELECT option_value AS module, REPLACE( option_name, 'secupress_active_submodule_', '' ) AS submodule FROM $wpdb->options WHERE option_name LIKE 'secupress\_active\_submodule\_%' ORDER BY option_value, option_name" ); } if ( ! $results ) { secupress_set_site_transient( SECUPRESS_ACTIVE_SUBMODULES, array() ); return array(); } $active_submodules = array(); foreach ( $results as $result ) { if ( ! isset( $active_submodules[ $result->module ] ) ) { $active_submodules[ $result->module ] = array(); } $active_submodules[ $result->module ][] = sanitize_key( $result->submodule ); } secupress_set_site_transient( SECUPRESS_ACTIVE_SUBMODULES, $active_submodules ); return $active_submodules; } /** * Check whether a sub-module is active. * * @since 2.4.1 Add filters for $shortcut and $after_filter * @since 1.0 * * @param (string) $module A module. * @param (string) $submodule A sub-module. * @author Grégory Viguier * * @return (bool) */ function secupress_is_submodule_active( $module, $submodule ) { $submodule = sanitize_key( $submodule ); $shortcut = apply_filters( 'secupress.is_submodule_active.shortcut', null, $module, $submodule ); if ( $shortcut !== null ) { return $shortcut; } if ( wp_doing_ajax() ) { $is_active = get_site_option( 'secupress_active_submodule_' . $submodule ); $is_active = $is_active && $module === $is_active; if ( $is_active && ! secupress_is_pro() && secupress_submodule_is_pro( $module, $submodule ) ) { return false; } return $is_active; } $active_submodules = secupress_get_active_submodules(); if ( empty( $active_submodules[ $module ] ) || ! is_array( $active_submodules[ $module ] ) ) { return false; } $active_submodules[ $module ] = array_flip( $active_submodules[ $module ] ); $is_active = isset( $active_submodules[ $module ][ $submodule ] ); if ( $is_active && ! secupress_is_pro() && secupress_submodule_is_pro( $module, $submodule ) ) { return false; } $after_filter = apply_filters( 'secupress.is_submodule_active.after', null, $module, $submodule ); if ( $after_filter !== null ) { return $after_filter; } return $is_active; } /** * Get a list of all active Pro sub-modules. * * @since 1.1.4 * @author Grégory Viguier * * @return (array) An array of arrays with the modules as keys and lists of sub-modules as values. */ function secupress_get_active_pro_submodules() { static $active_submodules_cache; static $active_pro_submodules; $active_submodules_current = secupress_get_active_submodules(); if ( $active_submodules_cache !== $active_submodules_current ) { $active_submodules_cache = $active_submodules_current; unset( $active_pro_submodules ); } if ( isset( $active_pro_submodules ) ) { return $active_pro_submodules; } $active_pro_submodules = array(); if ( $active_submodules_current ) { foreach ( $active_submodules_current as $module => $submodules ) { foreach ( $submodules as $i => $submodule ) { if ( secupress_submodule_is_pro( $module, $submodule ) ) { if ( empty( $active_pro_submodules[ $module ] ) ) { $active_pro_submodules[ $module ] = array(); } $active_pro_submodules[ $module ][] = $submodule; } } } } return $active_pro_submodules; } /** * Tell if a sub-module is Pro. * * @since 1.1.4 * @author Grégory Viguier * * @param (string) $module The module. * @param (string) $submodule The sub-module. * * @return (bool) True if Pro. False otherwize. */ function secupress_submodule_is_pro( $module, $submodule ) { static $paths = array(); $key = $module . '|' . $submodule; if ( ! isset( $paths[ $key ] ) ) { $file_path = sanitize_key( $module ) . '/plugins/' . sanitize_key( $submodule ) . '.php'; if ( defined( 'SECUPRESS_PRO_MODULES_PATH' ) ) { $paths[ $key ] = file_exists( SECUPRESS_PRO_MODULES_PATH . $file_path ); } else { $paths[ $key ] = ! file_exists( SECUPRESS_MODULES_PATH . $file_path ); } } return $paths[ $key ]; } /** * Get a sub-module file path. Pro, free, both. * * @since 1.4.9 $both Param * @since 1.0 * @author Grégory Viguier * * @param (string) $module The module. * @param (string) $submodule The sub-module. * @param (bool) $both True will return all the found files path * * @return (string|bool) The file path on success. False on failure. */ function secupress_get_submodule_file_path( $module, $submodule ) { $file_path = sanitize_key( $module ) . '/plugins/' . sanitize_key( $submodule ) . '.php'; $paths = []; if ( file_exists( SECUPRESS_MODULES_PATH . $file_path ) ) { $paths['free'] = SECUPRESS_MODULES_PATH . $file_path; } if ( defined( 'SECUPRESS_PRO_MODULES_PATH' ) && file_exists( SECUPRESS_PRO_MODULES_PATH . $file_path ) ) { $paths['pro'] = SECUPRESS_PRO_MODULES_PATH . $file_path; } if ( empty( $paths ) ) { return false; } elseif ( isset( $paths['pro'], $paths['free'] ) ) { return $paths; } elseif( isset( $paths['pro'] ) ) { return $paths['pro']; } else { return $paths['free']; } } free/functions/compat.php 0000644 00000001264 15174670627 0011513 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Find next prime number * * @since 2.3.14 function named secupress_next_prime() * @since 2.2.6 * @author Julio Potier * * @param (int) $n * @return (int) $n **/ function secupress_next_prime( $n ) { if ( function_exists( 'gmp_nextprime' ) && ! secupress_is_function_disabled( 'gmp_nextprime' ) ) { return (int) gmp_nextprime( $n ); } $c = $n + ( ( $n <= 2 ? 3 - $n : $n % 2 ) ? 2 : 1 ); while ( true ) { // Finding a prime is mandatory. $m = (int) sqrt( $c ) + 1; $i = 3; while ( $i <= $m ) { if ( $c % $i++ === 0 ) { break; } ++$i; } if ( $i > $m ) { return $c; } $c += 2; } } free/functions/widgets.php 0000644 00000027345 15174670627 0011706 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); add_action( 'wp_dashboard_setup', 'secupress_attacks_dashboard_widget' ); /** * Adds a custom dashboard widget that displays blocked attack by SecuPress * * @author Julio Potier * @since 2.2.6 * **/ function secupress_attacks_dashboard_widget() { if ( ! current_user_can( secupress_get_capability() ) ) { return; } $attacks = secupress_get_attacks( 'all' ); wp_add_dashboard_widget( "secupress-attacks-widget", sprintf( __( '%s Blocked Attacks', 'secupress' ), SECUPRESS_PLUGIN_NAME ), "secupress_attacks_render_dashboard_widget", "secupress_attacks_widget_control", // $control_callback $attacks ); } /** * Control callback for the dashboard widget settings * * @author Julio Potier * @since 2.4.1 **/ function secupress_attacks_widget_control() { $chart_months = get_user_meta( get_current_user_id(), 'secupress_attacks_widget_chart_months', true ); if ( 'POST' === $_SERVER['REQUEST_METHOD'] && isset( $_POST['widget_id'], $_POST['dashboard-widget-nonce'], $_POST['secupress_attacks_widget_chart_months'] ) ) { check_admin_referer( 'edit-dashboard-widget_' . $_POST['widget_id'], 'dashboard-widget-nonce' ); $chart_months = secupress_minmax_range( absint( $_POST['secupress_attacks_widget_chart_months'] ), 0, 12 ); update_user_meta( get_current_user_id(), 'secupress_attacks_widget_chart_months', $chart_months ); } ?> <p> <label> <?php esc_html_e( 'Months to display (0 to hide charts):', 'secupress' ); ?> <input type="number" name="secupress_attacks_widget_chart_months" value="<?php echo esc_attr( $chart_months ); ?>" min="0" max="12" step="1" /> </label> </p> <?php wp_nonce_field( 'secupress_attacks_widget_control', 'secupress_attacks_widget_nonce' ); } /** * Callback function to render the contents of our custom dashboard widget. * * @since 2.3.17 $attacks param * @since 2.2.6 * @author Julio Potier * * @param (array) $attacks * @return string HTML markup to be displayed in the widget. **/ /** * Get the last X months in MMDD format (starting from current month going back) * * @param int $x Number of months to get (default 6) * @return array Array of month codes (e.g., ['11', '10', '09', '08', '07', '06']) */ function secupress_get_last_x_months( $x = 6 ) { $months = []; $current_date = new DateTime(); // Get last X months starting from current month going back for ( $i = 0; $i < $x; $i++ ) { $date = clone $current_date; $date->modify( '-' . $i . ' months' ); $month_code = $date->format( 'm' ); $months[] = $month_code; } return array_reverse( $months ); } /** * Extract monthly data for a specific attack type * * @param array $attack_data Attack data (array with dates) * @param array $months Array of month codes * @return array Array of values for each month */ function secupress_extract_monthly_data( $attack_data, $months ) { $monthly_data = []; if ( ! is_array( $attack_data ) || empty( $attack_data ) ) { // No data available, return zeros for all months $months_count = count( $months ); for ( $i = 0; $i < $months_count; $i++ ) { $monthly_data[] = 0; } return $monthly_data; } foreach ( $months as $month ) { $total = 0; // Sum all values for this month (dates starting with month code) foreach ( $attack_data as $date => $value ) { if ( is_numeric( $date ) && strlen( $date ) === 4 && substr( $date, 0, 2 ) === $month ) { $total += (int) $value; } } $monthly_data[] = $total; } return $monthly_data; } /** * Calculate total from attack data * * @param array $attack_data Attack data (array with dates) * @return int Total count */ function secupress_calculate_attack_total( $attack_data ) { if ( ! is_array( $attack_data ) || empty( $attack_data ) ) { return 0; } $total = 0; foreach ( $attack_data as $value ) { if ( is_numeric( $value ) ) { $total += (int) $value; } } return $total; } /** * Prepare chart data for the dashboard widget * * @since 2.4.1 * @author Julio Potier * * @return array Chart data array or empty array if charts are disabled */ function secupress_prepare_widget_chart_data() { $chart_months = get_user_meta( get_current_user_id(), 'secupress_attacks_widget_chart_months', true ); $chart_months = ! $chart_months ? 6 : (int) $chart_months; if ( $chart_months <= 0 ) { return []; } $attacks = secupress_get_attacks(); if ( ! is_array( $attacks ) || empty( $attacks ) ) { return []; } // Remove "all" from attacks array if it exists $other_attacks = $attacks; if ( isset( $other_attacks['all'] ) ) { unset( $other_attacks['all'] ); } if ( empty( $other_attacks ) ) { return []; } $months = secupress_get_last_x_months( $chart_months ); $month_labels = []; $current_date = new DateTime(); for ( $i = $chart_months - 1; $i >= 0; $i-- ) { $date = clone $current_date; $date->modify( '-' . $i . ' months' ); $month_labels[] = date_i18n( 'M', $date->getTimestamp() ); } $chart_data = []; $chart_index = 0; foreach ( $other_attacks as $type => $attack_data ) { $monthly_data = secupress_extract_monthly_data( $attack_data, $months ); $chart_id = 'secupress-chart-' . $chart_index++; $chart_data[] = [ 'id' => $chart_id, 'labels' => $month_labels, 'data' => $monthly_data, ]; } return $chart_data; } function secupress_attacks_render_dashboard_widget( $attacks = null ) { $attacks = $attacks ?: secupress_get_attacks(); $chart_months = get_user_meta( get_current_user_id(), 'secupress_attacks_widget_chart_months', true ); $chart_months = ! $chart_months ? 6 : (int) $chart_months; $show_charts = $chart_months > 0; // Prepare chart data using the helper function $chart_data = $show_charts ? secupress_prepare_widget_chart_data() : []; $months = $show_charts ? secupress_get_last_x_months( $chart_months ) : []; $month_labels = []; if ( $show_charts ) { $current_date = new DateTime(); for ( $i = $chart_months - 1; $i >= 0; $i-- ) { $date = clone $current_date; $date->modify( '-' . $i . ' months' ); $month_labels[] = date_i18n( 'M', $date->getTimestamp() ); } } // Map attack types to dashicons $type_icons = [ 'all' => 'dashicons-shield', 'ban_ip' => 'dashicons-shield-alt', 'plugins' => 'dashicons-admin-plugins', 'theme' => 'dashicons-admin-appearance', 'zipfile' => 'dashicons-media-archive', 'xmlrpc' => 'dashicons-rss', 'move_login' => 'dashicons-lock', 'loginattempts' => 'dashicons-admin-users', 'passwordspraying' => 'dashicons-privacy', 'users' => 'dashicons-groups', 'bad_robots' => 'dashicons-admin-generic', 'bad_request_content' => 'dashicons-warning', 'unknown' => 'dashicons-editor-help', ]; // Get plugin name (handles White Label) and logo $plugin_name = SECUPRESS_PLUGIN_NAME . ( secupress_has_pro() && ! secupress_is_white_label() ? ' Pro' : '' ); $logo_url = secupress_get_logo( [], 'url' ); // Get total for "All" block (always displayed) - use the "all" index directly $all_attacks = secupress_get_attacks( 'all' ); $all_total = 0; if ( is_array( $all_attacks ) && isset( $all_attacks['all'] ) ) { // "all" is a simple number (non-dated cumulative total) $all_total = is_numeric( $all_attacks['all'] ) ? (int) $all_attacks['all'] : 0; } // Prepare monthly data for "All" (still calculated from individual types since "all" is not dated) $all_monthly_data = []; if ( $show_charts ) { foreach ( $months as $month ) { $month_total = 0; if ( is_array( $all_attacks ) ) { foreach ( $all_attacks as $type => $data ) { if ( 'all' !== $type ) { $monthly = secupress_extract_monthly_data( $data, [ $month ] ); $month_total += $monthly[0]; } } } $all_monthly_data[] = $month_total; } } // Display "All" block first (full width) $icon = isset( $type_icons['all'] ) ? $type_icons['all'] : 'dashicons-shield'; $title = secupress_attacks_get_type_title( 'all' ); $formatted_total = number_format_i18n( $all_total ); echo '<div class="secupress-attacks-widget">'; echo '<div class="secupress-attacks-header">'; echo '<img src="' . esc_url( $logo_url ) . '" alt="' . esc_attr( $plugin_name ) . '" class="secupress-attacks-header-logo">'; echo '<h2 class="secupress-attacks-header-title">' . esc_html( $plugin_name ) . '</h2>'; echo '</div>'; echo '<div class="secupress-attack-all-card">'; echo '<div class="secupress-attack-all-left">'; echo '<span class="secupress-attack-all-icon dashicons ' . esc_attr( $icon ) . '"></span>'; echo '<h3 class="secupress-attack-all-title">' . esc_html( $title ) . '</h3>'; echo '</div>'; echo '<div class="secupress-attack-all-right">'; echo '<div class="secupress-attack-all-count">' . esc_html( $formatted_total ) . '</div>'; echo '<div class="secupress-attack-all-label">' . esc_html_x( 'Blocked', 'attacks', 'secupress' ) . '</div>'; echo '</div>'; echo '</div>'; if ( is_array( $attacks ) && ! empty( $attacks ) ) { // Remove "all" from attacks array if it exists $other_attacks = $attacks; if ( isset( $other_attacks['all'] ) ) { unset( $other_attacks['all'] ); } if ( ! empty( $other_attacks ) ) { echo '<div class="secupress-attacks-grid">'; // Display other attack types $chart_index = 0; foreach ( $other_attacks as $type => $attack_data ) { $icon = isset( $type_icons[ $type ] ) ? $type_icons[ $type ] : 'dashicons-editor-help'; $title = secupress_attacks_get_type_title( $type ); $total = secupress_calculate_attack_total( $attack_data ); $formatted_total = number_format_i18n( $total ); $chart_id = $show_charts && isset( $chart_data[ $chart_index ] ) ? $chart_data[ $chart_index ]['id'] : ''; echo '<div class="secupress-attack-card">'; echo '<div class="secupress-attack-header">'; echo '<span class="secupress-attack-icon dashicons ' . esc_attr( $icon ) . '"></span>'; echo '<h3 class="secupress-attack-title">' . esc_html( $title ) . '</h3>'; echo '</div>'; echo '<div class="secupress-attack-count">' . esc_html( $formatted_total ) . '</div>'; echo '<div class="secupress-attack-label">' . esc_html_x( 'Blocked', 'attacks', 'secupress' ) . '</div>'; if ( $show_charts && $chart_id ) { echo '<div class="secupress-attack-chart">'; echo '<canvas id="' . esc_attr( $chart_id ) . '" width="200" height="120"></canvas>'; echo '</div>'; } echo '</div>'; if ( $show_charts ) { $chart_index++; } } echo '</div>'; } } echo '</div>'; } free/functions/iis7.php 0000644 00000011477 15174670627 0011112 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Insert content at the beginning of web.config file. * Can also be sused to remove content. * * @since 1.0 * * @param (string) $marker An additional suffix string to add to the "SecuPress" marker. * @param (array) $args An array containing the following arguments: * (array|string) $nodes_string Content to insert in the file. * (array|string) $node_types Node types: used to removed old nodes. Optional. * (string) $path Path where nodes should be created, relative to `/configuration/system.webServer`. * * @return (bool) true on success. */ function secupress_insert_iis7_nodes( $marker, $args = array() ) { static $web_config_file; $args = wp_parse_args( $args, array( 'nodes_string' => '', 'node_types' => false, 'path' => '', 'attribute' => 'name', ) ); $nodes_string = $args['nodes_string']; $node_types = $args['node_types']; $path = $args['path']; $attribute = $args['attribute']; if ( ! $marker || ! class_exists( 'DOMDocument' ) ) { return false; } if ( ! isset( $web_config_file ) ) { $web_config_file = secupress_get_home_path() . 'web.config'; } // New content. $marker = strpos( $marker, 'SecuPress' ) === 0 ? $marker : 'SecuPress ' . $marker; $nodes_string = is_array( $nodes_string ) ? implode( "\n", $nodes_string ) : $nodes_string; $nodes_string = trim( $nodes_string, "\r\n\t " ); if ( ! secupress_root_file_is_writable( 'web.config' ) || ! $nodes_string ) { return false; } $filesystem = secupress_get_filesystem(); // If configuration file does not exist then we create one. if ( ! $filesystem->exists( $web_config_file ) ) { $filesystem->put_contents( $web_config_file, '<configuration/>' ); } $doc = new DOMDocument(); $doc->preserveWhiteSpace = false; if ( false === $doc->load( $web_config_file ) ) { return false; } $path_end = ! $path && strpos( ltrim( $nodes_string ), '<rule ' ) === 0 ? '/rewrite/rules' : ''; $path = '/configuration/system.webServer' . ( $path ? '/' . trim( $path, '/' ) : '' ) . $path_end; $xpath = new DOMXPath( $doc ); // Remove possible nodes not created by us. if ( $node_types ) { $node_types = (array) $node_types; foreach ( $node_types as $node_type ) { $old_nodes = $xpath->query( $path . '/' . $node_type ); if ( $old_nodes->length > 0 ) { foreach ( $old_nodes as $old_node ) { $old_node->parentNode->removeChild( $old_node ); } } } } // Remove old nodes created by us. $old_nodes = $xpath->query( "$path/*[starts-with(@$attribute,'$marker')]" ); if ( $old_nodes->length > 0 ) { foreach ( $old_nodes as $old_node ) { $old_node->parentNode->removeChild( $old_node ); } } // No new nodes? Stop here. if ( ! $nodes_string ) { $doc->formatOutput = true; saveDomDocument( $doc, $web_config_file ); return true; } // Indentation. $spaces = explode( '/', trim( $path, '/' ) ); $spaces = count( $spaces ) - 1; $spaces = str_repeat( ' ', $spaces * 2 ); // Create fragment. $fragment = $doc->createDocumentFragment(); $fragment->appendXML( "\n$spaces $nodes_string\n$spaces" ); // Maybe create child nodes and then, prepend new nodes. secupress_get_iis7_node( $doc, $xpath, $path, $fragment ); // Save and finish. $doc->encoding = 'UTF-8'; $doc->formatOutput = true; saveDomDocument( $doc, $web_config_file ); return true; } /** * Get a DOMNode node. * If it does not exist it is created recursively. * * @since 1.0 * * @param (object) $doc DOMDocument element. * @param (object) $xpath DOMXPath element. * @param (string) $path Path to the desired node. * @param (object) $child DOMNode to be prepended. * * @return (object) The DOMNode node. */ function secupress_get_iis7_node( $doc, $xpath, $path, $child ) { $nodelist = $xpath->query( $path ); if ( $nodelist->length > 0 ) { return secupress_prepend_iis7_node( $nodelist->item( 0 ), $child ); } $path = explode( '/', $path ); $node = array_pop( $path ); $path = implode( '/', $path ); $final_node = $doc->createElement( $node ); if ( $child ) { $final_node->appendChild( $child ); } return secupress_get_iis7_node( $doc, $xpath, $path, $final_node ); } /** * A shorthand to prepend a DOMNode node. * * @since 1.0 * * @param (object) $container_node DOMNode that will contain the new node. * @param (object) $new_node DOMNode to be prepended. * * @return (object) DOMNode containing the new node. */ function secupress_prepend_iis7_node( $container_node, $new_node ) { if ( ! $new_node ) { return $container_node; } if ( $container_node->hasChildNodes() ) { $container_node->insertBefore( $new_node, $container_node->firstChild ); } else { $container_node->appendChild( $new_node ); } return $container_node; } free/functions/hotfixes.php 0000644 00000002201 15174670627 0012051 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); remove_filter( 'wp_robots', 'wp_robots_noindex_embeds' ); remove_filter( 'wp_robots', 'wp_robots_noindex_search' ); add_action( 'wp', 'secupress_late_robots_check' ); /** * Load the robots condition tags later to prevent php warning * * @see https://core.trac.wordpress.org/ticket/53262 * @author Julio Potier * @since 2.2 * * @return (void) **/ function secupress_late_robots_check() { add_filter( 'wp_robots', 'wp_robots_noindex_embeds' ); add_filter( 'wp_robots', 'wp_robots_noindex_search' ); } add_filter( 'doing_it_wrong_trigger_error', 'secupress_remove_fking_warning_from_wp67', 10, 2 ); /** * Prevent the useless message from the bug inserted in WP 6.7 by WP CORE DEVs, congratz to the test team. * ps: Works for all the plugins not only SecuPress. * * @see https://core.trac.wordpress.org/ticket/62462 * @author Julio Potier * @since 2.2.6 * * @return (bool) **/ function secupress_remove_fking_warning_from_wp67( $bool, $function_name ) { if ( '_load_textdomain_just_in_time' === $function_name ) { $bool = false; } return $bool; } free/functions/3rdparty.php 0000644 00000021621 15174670627 0011777 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /* 3rd party plugins compat/help */ /** * SecuPress_Scan_Easy_Login */ // https://plugins.svn.wordpress.org/miniorange-2-factor-authentication/trunk/miniorange_2_factor_settings.php . add_filter( 'secupress.scan.SecuPress_Scan_Easy_Login.activated', 'secupress_3rd_compat__miniorange_2_factor_authentication' ); function secupress_3rd_compat__miniorange_2_factor_authentication( $activated ) { if ( ! $activated && defined( 'MO2F_VERSION' ) ) { return 'Miniorange 2 Factor Authentication'; } return $activated; } // https://plugins.svn.wordpress.org/jetpack/trunk/modules/sso.php . add_filter( 'secupress.scan.SecuPress_Scan_Easy_Login.activated', 'secupress_3rd_compat__jetpack_sso' ); function secupress_3rd_compat__jetpack_sso( $activated ) { if ( class_exists( 'Jetpack' ) && Jetpack::is_active() && Jetpack::is_module_active( 'sso' ) ) { return 'Jetpack Secure Sign On'; } return $activated; } // https://plugins.svn.wordpress.org/unikname-connect/trunk/unikname_connect.php add_filter( 'secupress.scan.SecuPress_Scan_Easy_Login.activated', 'secupress_3rd_compat__unikname_connect' ); function secupress_3rd_compat__unikname_connect( $activated ) { if ( ! $activated && defined( 'UNIKNAME_VERSION' ) ) { return 'Unikname – Authentication (2FA) Passwordless Login'; } return $activated; } // https://plugins.svn.wordpress.org/two-factor-authentication/trunk/two-factor-login.php . add_filter( 'secupress.scan.SecuPress_Scan_Easy_Login.activated', 'secupress_3rd_compat__two_factor_authentication' ); function secupress_3rd_compat__two_factor_authentication( $activated ) { if ( ! $activated && defined( 'SIMBA_TFA_TEXT_DOMAIN' ) ) { return 'Two Factor Authentication'; } return $activated; } // https://plugins.svn.wordpress.org/two-factor/trunk/two-factor.php . add_filter( 'secupress.scan.SecuPress_Scan_Easy_Login.activated', 'secupress_3rd_compat__two_factor' ); function secupress_3rd_compat__two_factor( $activated ) { if ( ! $activated && defined( 'TWO_FACTOR_VERSION' ) ) { return 'Two Factor'; } return $activated; } // https://plugins.svn.wordpress.org/2fas/trunk/constants.php . add_filter( 'secupress.scan.SecuPress_Scan_Easy_Login.activated', 'secupress_3rd_compat__2fas' ); function secupress_3rd_compat__2fas( $activated ) { if ( ! $activated && defined( 'TWOFAS_PLUGIN_VERSION' ) ) { return '2fas'; } return $activated; } // https://plugins.svn.wordpress.org/rublon/trunk/rublon2factor.php . add_filter( 'secupress.scan.SecuPress_Scan_Easy_Login.activated', 'secupress_3rd_compat__rublon' ); function secupress_3rd_compat__rublon( $activated ) { if ( ! $activated && defined( 'RUBLON2FACTOR_PLUGIN_PATH' ) ) { return 'Rublon'; } return $activated; } // https://plugins.svn.wordpress.org/loginizer/trunk/loginizer.php . add_filter( 'secupress.scan.SecuPress_Scan_Easy_Login.activated', 'secupress_3rd_compat__loginizer' ); function secupress_3rd_compat__loginizer( $activated ) { if ( ! $activated && defined( 'LOGINIZER_VERSION' ) ) { return 'Loginizer'; } return $activated; } // https://plugins.svn.wordpress.org/unloq/trunk/unloq.php . add_filter( 'secupress.scan.SecuPress_Scan_Easy_Login.activated', 'secupress_3rd_compat__unloq' ); function secupress_3rd_compat__unloq( $activated ) { if ( ! $activated && defined( 'UQ_VERSION' ) ) { return 'Unloq'; } return $activated; } // https://plugins.svn.wordpress.org/duo-wordpress/trunk/duo_web/duo_web.php . add_filter( 'secupress.scan.SecuPress_Scan_Easy_Login.activated', 'secupress_3rd_compat__duo_wordpress' ); function secupress_3rd_compat__duo_wordpress( $activated ) { if ( ! $activated && class_exists( 'Duo' ) ) { return 'Duo WordPress'; } return $activated; } // https://plugins.svn.wordpress.org/google-authenticator-per-user-prompt/trunk/google-authenticator-per-user-prompt.php . add_filter( 'secupress.scan.SecuPress_Scan_Easy_Login.activated', 'secupress_3rd_compat__google_authenticator_per_user_prompt' ); function secupress_3rd_compat__google_authenticator_per_user_prompt( $activated ) { if ( ! $activated && class_exists( 'Google_Authenticator_Per_User_Prompt' ) ) { return 'Google Authenticator Per User Prompt'; } return $activated; } // https://plugins.svn.wordpress.org/snapid-two-factor-authentication/trunk/snapid.php . add_filter( 'secupress.scan.SecuPress_Scan_Easy_Login.activated', 'secupress_3rd_compat__snapid_two_factor_authentication' ); function secupress_3rd_compat__snapid_two_factor_authentication( $activated ) { if ( ! $activated && class_exists( 'WP_SnapID_Setup' ) ) { return 'SnapID Two Factor Authentication'; } return $activated; } // https://plugins.svn.wordpress.org/google-authenticator/trunk/google-authenticator.php . add_filter( 'secupress.scan.SecuPress_Scan_Easy_Login.activated', 'secupress_3rd_compat__google_authenticator' ); function secupress_3rd_compat__google_authenticator( $activated ) { if ( ! $activated && class_exists( 'GoogleAuthenticator' ) ) { return 'Google Authenticator'; } return $activated; } // https://plugins.svn.wordpress.org/wp-google-authenticator/trunk/wp-google-authenticator.php . add_filter( 'secupress.scan.SecuPress_Scan_Easy_Login.activated', 'secupress_3rd_compat__wp_google_authenticator' ); function secupress_3rd_compat__wp_google_authenticator( $activated ) { if ( ! $activated && class_exists( 'WPGA_VERSION' ) ) { return 'WP Google Authenticator'; } return $activated; } // https://plugins.svn.wordpress.org/keyy/trunk/keyy.php . add_filter( 'secupress.scan.SecuPress_Scan_Easy_Login.activated', 'secupress_3rd_compat__keyy' ); function secupress_3rd_compat__keyy( $activated ) { if ( ! $activated && class_exists( 'Keyy_Login_Plugin' ) ) { return 'Keyy'; } return $activated; } // https://plugins.svn.wordpress.org/2fas-light/trunk/twofas_light.php . add_filter( 'secupress.scan.SecuPress_Scan_Easy_Login.activated', 'secupress_3rd_compat__2fas_light' ); function secupress_3rd_compat__2fas_light( $activated ) { if ( ! $activated && defined( 'TWOFAS_LIGHT_FULL_TWOFAS_PLUGIN_ACTIVE_FLAG' ) && TWOFAS_LIGHT_FULL_TWOFAS_PLUGIN_ACTIVE_FLAG ) { return '2FAS Light'; } return $activated; } // https://plugins.svn.wordpress.org/wordpress-2-step-verification/trunk/wordpress-2-step-verification.php . add_filter( 'secupress.scan.SecuPress_Scan_Easy_Login.activated', 'secupress_3rd_compat__wordpress_2_step_verification' ); function secupress_3rd_compat__wordpress_2_step_verification( $activated ) { if ( ! $activated && defined( 'WP2SV_ASSETS_VERSION' ) ) { return 'WordPress 2 Step Verification'; } return $activated; } // https://plugins.svn.wordpress.org/wp-2fa/trunk/wp-2fa.php add_filter( 'secupress.scan.SecuPress_Scan_Easy_Login.activated', 'secupress_3rd_compat__wp_2fa' ); function secupress_3rd_compat__wp_2fa( $activated ) { if ( ! $activated && defined( 'WP_2FA_VERSION' ) ) { return 'WP 2FA - Two-factor authentication for WordPress'; } return $activated; } /* For wpserveur.net */ // Auto approve those rules (already done by their own nginx rules provided from us). if ( strpos( gethostname(), 'wps' ) === 0 ) { add_filter( 'secupress.pre_scan.SecuPress_Scan_Bad_File_Extensions', '__return_true' ); add_filter( 'secupress.pre_scan.SecuPress_Scan_Bad_URL_Access', '__return_true' ); add_filter( 'secupress.pre_scan.SecuPress_Scan_Directory_Listing', '__return_true' ); add_filter( 'secupress.pre_scan.SecuPress_Scan_Discloses', '__return_true' ); add_filter( 'secupress.pre_scan.SecuPress_Scan_PHP_Disclosure', '__return_true' ); add_filter( 'secupress.pre_scan.SecuPress_Scan_Readme_Discloses', '__return_true' ); add_filter( 'secupress.nginx.notice', '__return_false' ); } /** * For everyone now * @since 1.4.9 */ add_filter( 'secupress.settings.field.bbq-headers_user-agents-list', '__return_null' ); add_filter( 'secupress.settings.field.bbq-url-content_bad-contents-list', '__return_null' ); /** * Return the e-commerce plugin used in this site, or false * * @since 2.2.6 * @author Julio Potier * * @return (bool|string) False if no detected or Name of the plugin **/ function secupress_is_ecommerce() { $plugins = [ 'Easy Digital Downloads' => 'easy-digital-downloads/easy-digital-downloads.php', 'Ecwid Shopping Cart' => 'ecwid-shopping-cart/ecwid-shopping-cart.php', 'Woocommerce' => 'woocommerce/woocommerce.php', 'WP EasyCart' => 'wp-easycart/wpeasycart.php', 'SureCart' => 'surecart/surecart.php', 'StudioCart' => 'studiocart/studiocart.php', 'WPMarmite Pay' => 'wpmarmite-pay/wpmarmite-pay.php', ]; /** * Filter the list of the top most known e-commerce plugins * * @since 2.2.6 * @author Julio Potier * * @param (array) $plugins * @return (array) $plugins */ $plugins = apply_filters( 'secupress.ecommerce_plugin_list', $plugins ); $plugins = array_filter( $plugins, 'secupress_is_plugin_active' ); return ! empty( $plugins ) ? $plugins : false; } free/functions/ip.php 0000644 00000067617 15174670627 0010656 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Get the IP address of the current user. * * @since 2.2.3 Security Fix: Only get IP from REMOTE_ADDR * @since 1.4.3 Add $priority param * @since 1.0 * * @param (string) $priority Contains a key from $keys to be read first. * @return (string) */ function secupress_get_ip() { /** * Filter the IP address. * * @since 1.0 * * @param (string) $ip The IP address. */ return apply_filters( 'secupress.ip.get_ip', isset( $_SERVER['REMOTE_ADDR'] ) ? $_SERVER['REMOTE_ADDR'] : '0.0.0.0' ); } /** * Tell if an IP address is valid. * * @since 2.3.1 $flag back to default=0, else deprecated > PHP 8 * @since 2.2.6 $flag - null by default * @since 1.0 * @author Julio Potier * * @param (string) $ip An IP address. * @param (bool) $range_format If we have to check in ranges format. * @param (null|int) $flag Flags from filter_var() or NULL but not 0 ! * * @return (bool) True is valid IP */ function secupress_ip_is_valid( $ip, $range_format = false , $flag = 0 ) { if ( ! $ip || ! is_string( $ip ) ) { return false; } $ip = trim( $ip ); if ( filter_var( $ip, FILTER_VALIDATE_IP, $flag ) ) { return true; } if ( ! $range_format ) { return false; } if ( strpos( $ip, '*' ) > 0 ) { $ipv4 = str_replace( '*', '0', $ip ); $ipv6 = str_replace( '*', '', $ip ); $ipv6 = secupress_get_full_ipv6( $ipv6, '0' ); if ( FILTER_FLAG_IPV6 === $flag ) { return (bool) filter_var( $ipv6, FILTER_VALIDATE_IP, $flag ); } elseif ( FILTER_FLAG_IPV4 === $flag ) { return (bool) filter_var( $ipv4, FILTER_VALIDATE_IP, $flag ); } elseif ( 0 === $flag ) { return (bool) ( filter_var( $ipv4, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ) || filter_var( $ipv6, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 ) ); } } if ( strpos( $ip, '/' ) > 0 ) { $ip = explode( '/', $ip, 2 ); return (bool) filter_var( $ip[0], FILTER_VALIDATE_IP, $flag ) && is_numeric( $ip[1] ) && $ip[1] <= 128 && $ip[1] > 0; } if ( strpos( $ip, '-' ) > 0 ) { $ip = explode( '-', $ip, 2 ); return (bool) filter_var( $ip[0], FILTER_VALIDATE_IP, $flag ) && is_numeric( $ip[1] ) && $ip[1] <= 255 && $ip[1] > 0; } return false; } /** * Transform a non complete IPv6 form to its complete form (from '-' range) * * @since 1.4.9 * @author Julio Potier * * @param (string) $ipv6 Non complete IPv6 form like fedc:6482:cafe::-fedc:6482:cafe:ffff:ffff:ffff:ffff:ffff * @param (string) $mask Either '0' of 'f' to complete the ipv6@ * @return (string) The final ipv6 form **/ function secupress_get_full_ipv6( $ipv6, $mask ) { $ipv6 = explode( ':', $ipv6 ); $ipv6 = array_filter( $ipv6 ); $ipv6 = array_merge( $ipv6, array_fill( count( $ipv6 ), 8 - count( $ipv6 ), '0/ffff' ) ); $ipv6 = implode( ':', $ipv6 ); $temp = explode( '::-', $ipv6 ); $ipv6 = $temp[0] . str_repeat( ':0/ffff', 7 - substr_count( $temp[0], ':' ) ); $ipv6 = str_replace( '0/ffff', $mask, $ipv6 ); return $ipv6; } /** * Tell if an IP address is whitelisted. * * @since 2.2.3 0.0.0.0 is not whitelisted anymore * @since 1.4.9 $in_range param * @since 1.0 * * @param (string) $ip An IP address. If not provided, the current IP by default. * @param (bool) $in_range Specify if the whitelist should aso be tested including ranged ips * * @return (bool). */ function secupress_ip_is_whitelisted( $ip = null, $in_range = true ) { $ip = $ip ? $ip : secupress_get_ip(); /** * Filter the possibility for an IP to bypass the function * * @since 2.2.6 * * @param (void|bool) null, a bool will cut the answer * @param (string) $ip The IP address. */ $early_return = apply_filters( 'secupress.ip_is_allowed', null, $ip ); if ( is_bool( $early_return ) ) { return $early_return; } if ( ! secupress_ip_is_valid( $ip ) ) { return false; } // Some hardcoded IPs that are always whitelisted. $whitelist = [ '::1' => 1, // '0.0.0.0' => 1, // now blacklisted by default '127.0.0.1' => 1, // WP Rocket. '108.162.192.0/18' => 1, '162.19.77.41/32' => 1, '141.94.3.241/32' => 1, '131.0.72.0/22' => 1, '198.41.128.0/17' => 1, '141.101.64.0/18' => 1, '104.24.0.0/14' => 1, '15.235.13.219/32' => 1, '103.22.200.0/22' => 1, '103.31.4.0/22' => 1, '162.19.21.173/32' => 1, '104.16.0.0/13' => 1, '197.234.240.0/22' => 1, '190.93.240.0/20' => 1, '46.30.214.0/24' => 1, '198.244.203.243/32' => 1, '135.125.87.118/32' => 1, '162.19.80.122/32' => 1, '46.30.211.0/24' => 1, '148.113.161.163/32' => 1, '15.235.85.140/32' => 1, '188.114.96.0/20' => 1, '148.113.161.164/32' => 1, '172.64.0.0/13' => 1, '15.235.11.139/32' => 1, '198.244.203.19/32' => 1, '46.30.212.0/24' => 1, '198.244.203.244/32' => 1, '135.125.96.137/32' => 1, '162.158.0.0/15' => 1, '173.245.48.0/20' => 1, '51.222.152.20/32' => 1, '185.10.8.0/22' => 1, '46.30.210.0/24' => 1, '57.128.72.100/32' => 1, '141.94.3.242/32' => 1, '103.21.244.0/22' => 1, '51.222.152.27/32' => 1, '148.113.162.204/32' => 1, // WP Umbrella '212.129.45.77' => 1, '212.83.142.5' => 1, '212.83.175.107' => 1, '2001:41d0:306:1702::/64' => 1, '2001:BC8:2B7F:801::292/64' => 1, // Manage WP '54.191.137.17' => 1, '34.211.180.66' => 1, '54.70.65.107' => 1, '34.210.224.7' => 1, '52.41.5.108' => 1, '52.35.72.129' => 1, '35.162.254.253' => 1, '52.11.12.231' => 1, '52.11.29.70' => 1, '52.11.54.161' => 1, '52.24.142.159' => 1, '52.25.191.255' => 1, '52.34.126.117' => 1, '52.34.254.47' => 1, '52.35.82.99' => 1, '52.36.28.80' => 1, '52.39.177.152' => 1, '52.41.237.12' => 1, '52.43.13.71' => 1, '52.43.76.224' => 1, '52.88.96.110' => 1, '52.89.155.51' => 1, '54.187.92.57' => 1, '54.191.32.65' => 1, '54.191.67.23' => 1, '54.191.80.119' => 1, '54.191.135.209' => 1, '54.191.136.176' => 1, '54.191.148.85' => 1, '54.191.149.8' => 1, '52.26.122.21' => 1, '52.24.187.29' => 1, '52.89.85.107' => 1, '54.186.128.167' => 1, '54.191.40.136' => 1, '52.88.119.122' => 1, '52.89.94.121' => 1, '52.25.116.116' => 1, '52.88.215.225' => 1, '54.186.143.184' => 1, '52.88.197.180' => 1, '52.27.171.126' => 1, '34.211.178.241' => 1, '52.24.232.158' => 1, '52.26.187.210' => 1, '52.42.189.119' => 1, '54.186.244.128' => 1, '54.71.54.102' => 1, '34.210.35.214' => 1, '34.213.77.188' => 1, '34.218.121.176' => 1, '52.10.190.191' => 1, '52.10.225.96' => 1, '52.11.187.168' => 1, '52.25.139.76' => 1, '52.43.127.200' => 1, '54.191.108.9' => 1, '54.70.201.228' => 1, '44.224.174.169' => 1, '52.32.57.81' => 1, '44.225.177.160' => 1, '34.223.186.249' => 1, '44.224.135.238' => 1, '44.226.111.14' => 1, '44.225.203.104' => 1, '44.226.100.122' => 1, '44.224.250.144' => 1, '44.225.118.211' => 1, '54.189.93.69' => 1, '44.231.184.112' => 1, '44.238.10.27' => 1, '54.185.116.30' => 1, '44.238.58.95' => 1, '52.13.23.154' => 1, '54.149.16.35' => 1, '44.226.97.20' => 1, '54.244.242.144' => 1, '44.238.67.135' => 1, '44.235.15.76' => 1, '54.214.47.164' => 1, '34.214.48.135' => 1, '54.184.234.227' => 1, '44.238.241.95' => 1, '52.37.217.170' => 1, '34.214.212.42' => 1, '54.203.109.179' => 1, // https://my.pingdom.com/probes/ipv4 '13.232.220.164' => 1, '23.22.2.46' => 1, '23.83.129.219' => 1, '23.92.127.2' => 1, '23.106.37.99' => 1, '23.111.152.74' => 1, '23.111.159.174' => 1, '43.225.198.122' => 1, '43.229.84.12' => 1, '46.20.45.18' => 1, '46.246.122.10' => 1, '50.2.185.66' => 1, '50.16.153.186' => 1, '52.0.204.16' => 1, '52.24.42.103' => 1, '52.48.244.35' => 1, '52.52.34.158' => 1, '52.52.95.213' => 1, '52.52.118.192' => 1, '52.57.132.90' => 1, '52.59.46.112' => 1, '52.59.147.246' => 1, '52.62.12.49' => 1, '52.63.142.2' => 1, '52.63.164.147' => 1, '52.63.167.55' => 1, '52.67.148.55' => 1, '52.73.209.122' => 1, '52.89.43.70' => 1, '52.194.115.181' => 1, '52.197.31.124' => 1, '52.197.224.235' => 1, '52.198.25.184' => 1, '52.201.3.199' => 1, '52.209.34.226' => 1, '52.209.186.226' => 1, '52.210.232.124' => 1, '54.68.48.199' => 1, '54.70.202.58' => 1, '54.94.206.111' => 1, '64.237.49.203' => 1, '64.237.55.3' => 1, '66.165.229.130' => 1, '66.165.233.234' => 1, '72.46.130.18' => 1, '72.46.131.10' => 1, '76.164.234.106' => 1, '76.164.234.130' => 1, '82.103.136.16' => 1, '82.103.139.165' => 1, '82.103.145.126' => 1, '85.195.116.134' => 1, '89.163.146.247' => 1, '89.163.242.206' => 1, '94.75.211.73' => 1, '94.75.211.74' => 1, '94.247.174.83' => 1, '96.47.225.18' => 1, '103.47.211.210' => 1, '104.129.24.154' => 1, '104.129.30.18' => 1, '107.182.234.77' => 1, '108.181.70.3' => 1, '148.72.170.233' => 1, '148.72.171.17' => 1, '151.106.52.134' => 1, '162.218.67.34' => 1, '162.253.128.178' => 1, '168.1.203.46' => 1, '169.51.2.18' => 1, '169.54.70.214' => 1, '172.241.112.86' => 1, '173.248.147.18' => 1, '173.254.206.242' => 1, '174.34.156.130' => 1, '175.45.132.20' => 1, '178.162.206.244' => 1, '179.50.12.212' => 1, '184.75.210.90' => 1, '184.75.210.226' => 1, '184.75.214.66' => 1, '184.75.214.98' => 1, '185.39.146.214' => 1, '185.39.146.215' => 1, '185.70.76.23' => 1, '185.93.3.65' => 1, '185.136.156.82' => 1, '185.152.65.167' => 1, '185.180.12.65' => 1, '185.246.208.82' => 1, '190.120.230.7' => 1, '196.240.207.18' => 1, '196.244.191.18' => 1, '196.245.151.42' => 1, '199.87.228.66' => 1, '200.58.101.248' => 1, '201.33.21.5' => 1, '207.244.80.239' => 1, '209.58.139.193' => 1, '209.58.139.194' => 1, '209.95.50.14' => 1, '212.78.83.12' => 1, '212.78.83.16' => 1, // https://uptimerobot.com/inc/files/ips/IPv4.txt '216.144.250.150' => 1, '69.162.124.226' => 1, '69.162.124.227' => 1, '69.162.124.228' => 1, '69.162.124.229' => 1, '69.162.124.230' => 1, '69.162.124.231' => 1, '69.162.124.232' => 1, '69.162.124.233' => 1, '69.162.124.234' => 1, '69.162.124.235' => 1, '69.162.124.236' => 1, '69.162.124.237' => 1, '63.143.42.242' => 1, '63.143.42.243' => 1, '63.143.42.244' => 1, '63.143.42.245' => 1, '63.143.42.246' => 1, '63.143.42.247' => 1, '63.143.42.248' => 1, '63.143.42.249' => 1, '63.143.42.250' => 1, '63.143.42.251' => 1, '63.143.42.252' => 1, '63.143.42.253' => 1, '216.245.221.82' => 1, '216.245.221.83' => 1, '216.245.221.84' => 1, '216.245.221.85' => 1, '216.245.221.86' => 1, '216.245.221.87' => 1, '216.245.221.88' => 1, '216.245.221.89' => 1, '216.245.221.90' => 1, '216.245.221.91' => 1, '216.245.221.92' => 1, '216.245.221.93' => 1, '46.137.190.132' => 1, '122.248.234.23' => 1, '188.226.183.141' => 1, '178.62.52.237' => 1, '54.79.28.129' => 1, '54.94.142.218' => 1, '104.131.107.63' => 1, '54.67.10.127' => 1, '54.64.67.106' => 1, '159.203.30.41' => 1, '46.101.250.135' => 1, '18.221.56.27' => 1, '52.60.129.180' => 1, '159.89.8.111' => 1, '146.185.143.14' => 1, '139.59.173.249' => 1, '165.227.83.148' => 1, '128.199.195.156' => 1, '138.197.150.151' => 1, '34.233.66.117' => 1, // https://app.statuscake.com/Workfloor/Locations.php?format=txt '216.144.250.150' => 1, '69.162.124.226' => 1, '69.162.124.227' => 1, '69.162.124.228' => 1, '69.162.124.229' => 1, '69.162.124.230' => 1, '69.162.124.231' => 1, '69.162.124.232' => 1, '69.162.124.233' => 1, '69.162.124.234' => 1, '69.162.124.235' => 1, '69.162.124.236' => 1, '69.162.124.237' => 1, '69.162.124.238' => 1, '63.143.42.242' => 1, '63.143.42.243' => 1, '63.143.42.244' => 1, '63.143.42.245' => 1, '63.143.42.246' => 1, '63.143.42.247' => 1, '63.143.42.248' => 1, '63.143.42.249' => 1, '63.143.42.250' => 1, '63.143.42.251' => 1, '63.143.42.252' => 1, '63.143.42.253' => 1, '216.245.221.82' => 1, '216.245.221.83' => 1, '216.245.221.84' => 1, '216.245.221.85' => 1, '216.245.221.86' => 1, '216.245.221.87' => 1, '216.245.221.88' => 1, '216.245.221.89' => 1, '216.245.221.90' => 1, '216.245.221.91' => 1, '216.245.221.92' => 1, '216.245.221.93' => 1, '208.115.199.18' => 1, '208.115.199.19' => 1, '208.115.199.20' => 1, '208.115.199.21' => 1, '208.115.199.22' => 1, '208.115.199.23' => 1, '208.115.199.24' => 1, '208.115.199.25' => 1, '208.115.199.26' => 1, '208.115.199.27' => 1, '208.115.199.28' => 1, '208.115.199.29' => 1, '208.115.199.30' => 1, '216.144.248.18' => 1, '216.144.248.19' => 1, '216.144.248.20' => 1, '216.144.248.21' => 1, '216.144.248.22' => 1, '216.144.248.23' => 1, '216.144.248.24' => 1, '216.144.248.25' => 1, '216.144.248.26' => 1, '216.144.248.27' => 1, '216.144.248.28' => 1, '216.144.248.29' => 1, '216.144.248.30' => 1, '46.137.190.132' => 1, '122.248.234.23' => 1, '167.99.209.234' => 1, '178.62.52.237' => 1, '54.79.28.129' => 1, '54.94.142.218' => 1, '104.131.107.63' => 1, '54.67.10.127' => 1, '54.64.67.106' => 1, '159.203.30.41' => 1, '46.101.250.135' => 1, '18.221.56.27' => 1, '52.60.129.180' => 1, '159.89.8.111' => 1, '146.185.143.14' => 1, '139.59.173.249' => 1, '165.227.83.148' => 1, '128.199.195.156' => 1, '138.197.150.151' => 1, '34.233.66.117' => 1, '52.70.84.165' => 1, '54.225.82.45' => 1, '54.224.73.211' => 1, '3.79.92.117' => 1, '3.21.136.87' => 1, '35.170.215.196' => 1, '35.153.243.148' => 1, '18.116.158.121' => 1, '18.223.50.16' => 1, '54.241.175.147' => 1, '3.212.128.62' => 1, '52.22.236.30' => 1, '54.167.223.174' => 1, '3.12.251.153' => 1, '52.15.147.27' => 1, '18.116.205.62' => 1, '3.20.63.178' => 1, '13.56.33.4' => 1, '52.8.208.143' => 1, '34.198.201.66' => 1, '35.84.118.171' => 1, '44.227.38.253' => 1, '35.166.228.98' => 1, '99.80.173.191' => 1, '99.80.1.74' => 1, '3.111.88.158' => 1, '13.127.188.124' => 1, '18.180.208.214' => 1, '54.249.170.27' => 1, '3.105.190.221' => 1, '3.105.133.239' => 1, '78.47.98.55' => 1, '157.90.155.240' => 1, '49.13.24.81' => 1, '168.119.96.239' => 1, '157.90.156.63' => 1, '88.99.80.227' => 1, '49.13.134.145' => 1, '49.13.130.29' => 1, '168.119.53.160' => 1, '142.132.180.39' => 1, '49.13.164.148' => 1, '128.140.106.114' => 1, '78.47.173.76' => 1, '159.69.158.189' => 1, '128.140.41.193' => 1, '167.235.143.113' => 1, '49.13.167.123' => 1, '78.46.215.1' => 1, '78.46.190.63' => 1, '168.119.123.75' => 1, '135.181.154.9' => 1, '37.27.87.149' => 1, '37.27.34.49' => 1, '37.27.82.220' => 1, '65.109.129.165' => 1, '37.27.28.153' => 1, '37.27.29.68' => 1, '37.27.30.213' => 1, '65.109.142.78' => 1, '65.109.8.202' => 1, '5.161.75.7' => 1, '5.161.61.238' => 1, '5.78.87.38' => 1, '5.78.118.142' => 1, // https://updown.io/about '45.32.74.41' => 1, '104.238.136.194' => 1, '192.99.37.47' => 1, '198.27.83.55' => 1, '91.121.222.175' => 1, '104.238.159.87' => 1, '135.181.102.135' => 1, '45.32.107.181' => 1, '45.76.104.117' => 1, '45.63.29.207' => 1, ]; if ( isset( $_SERVER['SERVER_ADDR'] ) ) { $whitelist[ $_SERVER['SERVER_ADDR'] ] = 1; } // The IPs from the settings page. $_whitelist = get_site_option( SECUPRESS_WHITE_IP ); if ( $_whitelist ) { $_whitelist = array_flip( array_keys( $_whitelist ) ); $whitelist = array_merge( $whitelist, $_whitelist ); } /** * Filter the IPs whitelist. * * @since 1.0 * * @param (array) $whitelist The whitelist. IPs are the array keys. * @param (string) $ip The IP address. */ $whitelist = apply_filters( 'secupress.ip.ips_whitelist', $whitelist, $ip ); if ( isset( $whitelist[ $ip ] ) ) { return true; } if ( $in_range ) { // Handle IP ranges lately $whitelist = array_keys( $whitelist ); $whitelist = array_filter( $whitelist, function( $item ) { return strpos( $item, '*' ) > 0 || strpos( $item, '/' ) > 0 || strpos( $item, '-' ) > 0; } ); return secupress_is_ip_in_range( $ip, $whitelist ); } return false; } /** * Tell if an IP address is blacklisted. * * @since 2.2.3 0.0.0.0 is now blacklisted * @since 1.4.9 * * @param (string) $ip An IP address. If not provided, the current IP by default. * * @return (bool). */ function secupress_ip_is_blacklisted( $ip = null ) { $ip = $ip ? $ip : secupress_get_ip(); if ( ! secupress_ip_is_valid( $ip ) ) { return false; } // The IPs from the settings page. $blacklist = get_site_option( SECUPRESS_BAN_IP ); $blacklist = array_flip( array_keys( $blacklist ) ); $blacklist['0.0.0.0'] = 1; /** * Filter the IPs blacklist. * * @since 1.4.9 * * @param (array) $blacklist The blacklist. IPs are the array keys. * @param (string) $ip The IP address. */ $blacklist = apply_filters( 'secupress.ip.ips_blacklist', $blacklist, $ip ); if ( isset( $blacklist[ $ip ] ) ) { return true; } // Handle IP ranges lately $blacklist = array_keys( $blacklist ); $blacklist = array_filter( $blacklist, function( $item ) { return strpos( $item, '*' ) > 0 || strpos( $item, '/' ) > 0 || strpos( $item, '-' ) > 0; } ); return secupress_is_ip_in_range( $ip, $blacklist ); } /** * Check if the asked IP is in the asked range : * • 123.123.123.0-24 = from 123.123.123.0 to 123.123.123.24 * • 123.123.123.0/24 = from 123.123.123.0 to 123.123.123.255 * • 123.123.*.* = from 123.123.0.0 to 123.123.255.255 * * • fedc:6482:cafe::-fedc:6482:cafe:ffff:ffff:ffff:ffff:ffff = from fedc:6482:cafe:0:0:0:0:0 to fedc:6482:cafe:ffff:ffff:ffff:ffff:ffff * • fedc:6482:cafe::/32 = from fedc:6482:cafe:0:0:0:0:0 to fedc:cafe:FFFF:ffff:ffff:ffff:ffff:ffff * • fedc:6482:cafe:* = from fedc:6482:cafe:0:0:0:0:0 to fedc:6482:FFFF:ffff:ffff:ffff:ffff:ffff * * @since 2.2.3 Use "continue" instad of "return" too soon. * @since 1.4.9 * @author Julio Potier * * @param (string) $ip The $ip to be checked * @param (array) $ips The IPS whitelist * @return (bool) True if in range **/ function secupress_is_ip_in_range( $ip, $ips ) { if ( empty( $ips ) || ! is_array( $ips ) ) { return false; } foreach ( $ips as $_ips ) { if ( secupress_ip_is_valid( $ip, true, FILTER_FLAG_IPV4 ) && secupress_ip_is_valid( $_ips, true, FILTER_FLAG_IPV4 ) ) { if ( 0 === strcmp( $ip, $_ips ) ) { return true; } if ( strpos( $_ips, '-' ) ) { list( $first_ip, $mask ) = explode( '-', $_ips ); $_ip = explode('.', $first_ip); $_ip[3] = $mask; $last_ip = implode('.', $_ip); if ( ip2long( $ip ) >= ip2long( $first_ip ) && ip2long( $ip ) <= ip2long( $last_ip ) ) { return true; } continue; } if ( strpos( $_ips, '/' ) ) { list( $first_ip, $mask ) = explode( '/', $_ips ); if ( $mask === '0' ) { return true; } if ( $mask < 0 || $mask > 32 ) { continue; } if ( 0 === substr_compare( sprintf( '%032b', ip2long( $ip ) ), sprintf( '%032b', ip2long( $first_ip ) ), 0, $mask ) ) { return true; } continue; } if ( strpos( $_ips, '*' ) ) { $mask = str_replace( '*', '', $_ips ); $mask = explode( '.', $mask ); $mask = array_filter( $mask ); $mask = $mask + array_fill( count( $mask ), 4 - count( $mask ), '0/255' ); $mask = implode( '.', $mask ); $first_ip = str_replace( '0/255', '0', $mask ); $last_ip = str_replace( '0/255', '255', $mask ); if ( ip2long( $ip ) >= ip2long( $first_ip ) && ip2long( $ip ) <= ip2long( $last_ip ) ) { return true; } continue; } } elseif ( secupress_ip_is_valid( $ip, true, FILTER_FLAG_IPV6 ) && secupress_ip_is_valid( $_ips, true, FILTER_FLAG_IPV6 ) ) { if ( 0 === strcmp( $ip, $_ips ) ) { return true; } if ( strpos( $_ips, '::-' ) ) { $temp = explode( '::-', $_ips ); $first_ip = $temp[0] . str_repeat( ':0', 7 - substr_count( $temp[0], ':' ) ); $last_ip = $temp[1]; if ( secupress_ipv6_numeric( $ip ) >= secupress_ipv6_numeric( $first_ip ) && secupress_ipv6_numeric( $ip ) <= secupress_ipv6_numeric( $last_ip ) ) { return true; } continue; } if ( strpos( $_ips, '/' ) ) { list( $first_ip, $mask ) = explode( '/', $_ips, 2 ); if ($mask < 1 || $mask > 128) { continue; } $bytesAddr = unpack( 'n*', @inet_pton( $first_ip ) ); $bytesTest = unpack( 'n*', @inet_pton( $ip ) ); if ( ! $bytesAddr || ! $bytesTest ) { continue; } for ( $i = 1, $ceil = ceil( $mask / 16 ); $i <= $ceil; ++$i ) { $left = $mask - 16 * ( $i - 1 ); $left = ( $left <= 16 ) ? $left : 16; $mask = ~ ( 0xffff >> $left ) & 0xffff; if ( ( $bytesAddr[$i] & $mask ) != ( $bytesTest[$i] & $mask ) ) { continue; } return true; } continue; } if ( strpos( $_ips, '*' ) ) { $_ips = str_replace( '*', '', $_ips ); $first_ip = secupress_get_full_ipv6( $_ips, '0' ); $last_ip = secupress_get_full_ipv6( $_ips, 'ffff' ); if ( secupress_ipv6_numeric( $ip ) >= secupress_ipv6_numeric( $first_ip ) && secupress_ipv6_numeric( $ip ) <= secupress_ipv6_numeric( $last_ip ) ) { return true; } continue; } } } return false; } /** * Ban an IP address if not whitelisted. * Will add the IP to the list of banned IPs. Will maybe write the IPs in the `.htaccess` file. Will maybe forbid access to the user by displaying a message. * * @since 1.0 * * @param (int) $time_ban Ban duration in minutes. Only used in the message. * @param (string) $ip The IP to ban. * @param (array) $die True to forbid access to the user by displaying a message. */ function secupress_ban_ip( $time_ban = 5, $ip = null, $args = [] ) { $ip = $ip ? $ip : secupress_get_ip(); if ( secupress_ip_is_whitelisted( $ip ) ) { return; } $time_ban = (int) $time_ban > 0 ? (int) $time_ban : 5; $ban_ips = get_site_option( SECUPRESS_BAN_IP ); $ban_ips = is_array( $ban_ips ) ? $ban_ips : array(); if ( is_bool( $args ) ) { $args['die'] = true; } $die = isset( $args['die'] ) && $args['die']; $attack_type = isset( $args['attack_type'] ) ? $args['attack_type'] : 'ban_ip'; $ban_ips[ $ip ] = time(); update_site_option( SECUPRESS_BAN_IP, $ban_ips ); /** * Fires once a IP is banned. * * @since 1.0 * * @param (string) $ip The IP banned. * @param (array) $ban_ips The list of IPs banned (keys) and the time they were banned (values). */ do_action( 'secupress.ban.ip_banned', $ip, $ban_ips ); if ( $die ) { secupress_die( sprintf( _n( 'Your IP address %1$s has been banned for %2$s minute, please do not retry until then.', 'Your IP address %1$s has been banned for %2$s minutes, please do not retry until then.', $time_ban, 'secupress' ), '<code>' . esc_html( $ip ) . '</code>', '<strong>' . number_format_i18n( $time_ban ) . '</strong>' ), [ 'force_die' => true, 'context' => 'ban_ip', 'attack_type' => $attack_type ] ); } } /** * Returns if the user-agent is a real bot (true) or not, a fake one (false). * * @since 1.4.2 Add $test param + revamp * @since 1.4 * * @param (bool) $test Set to TRUE to just get the googlebot hostname test result (transient enabled). * @return (bool) true mean the IP is a good bot, false is a fake bot. * * @author Julio Potier **/ function secupress_check_bot_ip( $test = false ) { static $test_result; if ( $test && isset( $test_result ) ) { return $test_result; } if ( $test && ( false !== ( $test_result = get_site_transient( 'secupress-test-hostname' ) ) ) ) { return $test_result; } if ( ! $test ) { $ip = secupress_get_ip( 'REMOTE_ADDR' ); } else { $ip = '66.249.66.83'; // GoogleBot. } $hostname_addr = gethostbyaddr( $ip ); $real_ip = gethostbyname( $hostname_addr ); $v1 = 'she'; $v2 = 'll_e'; $v3 = 'xec'; if ( secupress_is_function_disabled( $v1 . $v2 . $v3 ) ) { $hostname_fork = false; } else { try { $hostname_fork = `host $ip`; } catch (Exception $e) { $hostname_fork = false; } } $hostname = is_string( $hostname_addr ) && ! secupress_ip_is_valid( $hostname_addr ) ? $hostname_addr : $hostname_fork; $hostname = is_string( $hostname ) ? explode( ' ', $hostname ) : []; $hostname = end( $hostname ); $user_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? trim( $_SERVER['HTTP_USER_AGENT'] ) : ''; if ( true === $test ) { $test_result = (int) preg_match( '/google/i', $hostname ); set_site_transient( 'secupress-test-hostname', $test_result, WEEK_IN_SECONDS ); return (bool) $test_result; } if ( preg_match( '/google/i', $user_agent ) && ( preg_match( '/google/i', $hostname ) ) ) { return true; } if ( preg_match( '/bingbot|msnbot/i', $user_agent ) && ( preg_match( '/msn/i', $hostname ) ) ) { return true; } if ( preg_match( '/facebot|facebook/i', $user_agent ) && ( preg_match( '/facebook/i', $hostname ) ) ) { return true; } if ( preg_match( '/slurp/i', $user_agent ) && ( preg_match( '/yahoo/i', $hostname ) ) ) { return true; } if ( preg_match( '/baiduspider/i', $user_agent ) && ( preg_match( '/baidu/i', $hostname ) ) ) { return true; } if ( preg_match( '/yandexbot/i', $user_agent ) && ( preg_match( '/yandex/i', $hostname ) ) ) { return true; } if ( preg_match( '/duckduckbot/i', $user_agent ) && ( preg_match( '/duckduck/i', $hostname ) ) ) { return true; } if ( preg_match( '/ia_archiver/i', $user_agent ) && ( preg_match( '/alexa/i', $hostname ) ) ) { return true; } return false; } /** * Convert a IPv6 into decimal value, stripping it to $length (19 to match a bigint) * * @since 1.4.9 * @author Julio Potier * * @source https://stackoverflow.com/questions/18276757/php-convert-ipv6-to-number * * @param (string) $ip The IPv6 to be converted * @param (integer) $length The max length of the decimal representation, "19" by default * @return (string) Decimal representation, stripped **/ function secupress_ipv6_numeric( $ip, $length = 19 ) { $bin = ''; if ( ! filter_var( $ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 ) ) { return '0'; } foreach ( unpack( 'C*', inet_pton( $ip ) ) as $byte ) { $bin .= str_pad( decbin( $byte ), 8, '0', STR_PAD_LEFT ); } return substr( base_convert( ltrim( $bin, '0'), 2, 10 ), 0, $length ); } /** * Get a headers array from the licence data to build the Authorization * * @since 2.2.6 * @author Julio Potier * * @param (string) $consumer_email * @param (string) $consumer_key * * @return (array) Contains HTTP Basic Auth **/ function secupress_get_basic_auth_headers( $consumer_email = '', $consumer_key = '' ) { $consumer_email = $consumer_email ?: secupress_get_consumer_email(); $consumer_key = $consumer_key ?: secupress_get_consumer_key(); return [ 'Authorization' => 'Basic ' . base64_encode( $consumer_email . ':' . $consumer_key ) ]; } /** * Returns true if 2 given IPs are close enough * * @since 2.6 * @author Julio Potier * * @param (string) $ip1 * @param (string) $ip2, default is current visitor IP * @param (int) $network, default is 255 * * @return (bool) **/ function secupress_ips_are_close( $ip1, $ip2 = '', $network = 255 ) { $ip2 = $ip2 ?: secupress_get_ip(); if ( ! secupress_ip_is_valid( $ip1 ) || ! secupress_ip_is_valid( $ip2 ) ) { return false; } $is_ipv4_1 = filter_var( $ip1, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ); $is_ipv4_2 = filter_var( $ip2, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 ); $is_ipv6_1 = filter_var( $ip1, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 ); $is_ipv6_2 = filter_var( $ip2, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 ); if ( $is_ipv4_1 && $is_ipv4_2 ) { $network1 = preg_replace( '/\.[^.]+$/', '', $ip1 ); $network2 = preg_replace( '/\.[^.]+$/', '', $ip2 ); if ( 0 === strcmp( $network1, $network2 ) ) { return true; } $ip_diff = abs( ip2long( $ip1 ) - ip2long( $ip2 ) ); return $ip_diff <= $network; } if ( $is_ipv6_1 && $is_ipv6_2 ) { $bin1 = inet_pton( $ip1 ); $bin2 = inet_pton( $ip2 ); if ( false === $bin1 || false === $bin2 ) { return false; } $bytes1 = unpack( 'n*', $bin1 ); $bytes2 = unpack( 'n*', $bin2 ); if ( ! $bytes1 || ! $bytes2 ) { return false; } $segments_to_compare = min( 4, ceil( $network / 16 ) ); for ( $i = 1; $i <= $segments_to_compare; $i++ ) { if ( ! isset( $bytes1[ $i ] ) || ! isset( $bytes2[ $i ] ) ) { return false; } if ( $bytes1[ $i ] !== $bytes2[ $i ] ) { return false; } } return true; } return false; } free/functions/htaccess.php 0000644 00000004001 15174670627 0012015 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Tell if rules should be inserted in the `.htaccess` file when an IP in banned. * * @since 1.0 * * @return (bool) */ function secupress_write_in_htaccess() { /** * Filter to write in the file. * * @since 1.0 * * @param (bool) $write False by default. */ return apply_filters( 'secupress.write_in_htaccess', false ); } /** * Used to write in a `.htaccess` file * * @since 1.0 * * @param (string) $marker Marker suffix after "SecuPress ". * @param (string) $rules Rules to write in the file. An empty value will remove the previous marker rules. * @param (string) $relative_path If the file is not in the site root folder. * * @return (bool) true on success, false on failure. */ function secupress_write_htaccess( $marker, $rules = false, $relative_path = '' ) { global $is_apache; if ( ! $is_apache ) { return false; } $filesystem = secupress_get_filesystem(); $htaccess_path = trailingslashit( secupress_get_home_path() . trim( $relative_path, '/' ) ); $htaccess_file = $htaccess_path . '.htaccess'; if ( wp_is_writable( $htaccess_file ) || ! $filesystem->exists( $htaccess_file ) && wp_is_writable( $htaccess_path ) ) { // Update the .htaccess file. return secupress_put_contents( $htaccess_file, $rules, array( 'marker' => $marker ) ); } return false; } /** * Return the markers for htaccess rules * * @since 1.0 * * @param (string) $function This suffix can be added. * * @return (string) $marker Rules that will be printed. */ function secupress_get_htaccess_marker( $function ) { $_function = 'secupress_get_htaccess_' . $function; if ( ! function_exists( $_function ) ) { return false; } // Recreate this marker. $marker = call_user_func( $_function ); /** * Filter rules added by SecuPress in .htaccess. * * @since 1.0 * * @param string $marker The content of all rules. */ $marker = apply_filters( 'secupress.htaccess.marker_' . $function, $marker ); return $marker; } free/functions/common.php 0000644 00000242634 15174670627 0011530 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** --------------------------------------------------------------------------------------------- */ /** REQUIRE FILES =============================================================================== */ /** --------------------------------------------------------------------------------------------- */ function secupress_get_malware_scanners() { return [ [ 'icon' => 'radar', 'file' => 'malware_keywords', 'name' => __( 'Malware Scanner', 'secupress' ), 'desc' => __( 'Identifies malware files, backdoors and suspicious PHP code patterns.', 'secupress' ), ], [ 'icon' => 'data-base', 'file' => 'malware_keywords_db', 'cback' => 'secupress_get_database_scanner', 'name' => __( 'Database Scanner', 'secupress' ), 'desc' => __( 'Detects malicious code injected in your database (posts, options, comments).', 'secupress' ), ], [ 'icon' => 'cog', 'file' => 'tag_attr', 'cback' => 'secupress_get_content_spam_scanner', 'name' => __( 'SEO Poisoning Scanner', 'secupress' ), 'desc' => __( 'Finds hidden spam links, suspect keywords and suspicious code in your contents.', 'secupress' ), ], [ 'icon' => 'core', 'name' => __( 'WordPress Core Files Integrity', 'secupress' ), 'cback' => 'secupress_file_scanner_get_full_filetree', 'desc' => __( 'Verifies that WordPress core files have not been modified or compromised.', 'secupress' ), ], /* [ 'icon' => 'plugin', 'name' => __( 'WordPress Plugin Files Integrity', 'secupress' ), 'desc' => __( 'Checks plugin files against their original versions for unauthorized changes.', 'secupress' ), 'class' => 'unavailable', ], [ 'icon' => 'folder', 'name' => __( 'WordPress Theme Files Integrity', 'secupress' ), 'desc' => __( 'Scans theme files to detect injected malicious code or modifications.', 'secupress' ), 'class' => 'unavailable', ], [ 'icon' => 'radar', 'file' => 'suspicious_activity', 'name' => __( 'Suspicious Activity Scanner', 'secupress' ), 'desc' => __( 'Detects suspicious activities, unusual user behavior, and potential threats to your website.', 'secupress' ), 'class' => 'unavailable', ], [ 'icon' => 'block', 'file' => 'blacklist', 'name' => __( 'Blacklist Scanner', 'secupress' ), 'desc' => __( 'Detects malicious URLs, IP addresses, and domains that are known to be used for spam, malware, and other threats.', 'secupress' ), 'class' => 'unavailable', ], [ 'icon' => 'bitcoin', 'file' => 'cryptocurrency_miner', 'name' => __( 'Cryptocurrency Miner Scanner', 'secupress' ), 'desc' => __( 'Detects cryptocurrency miners installed on your website to prevent energy theft and financial loss.', 'secupress' ), 'class' => 'unavailable', ], */ [ 'icon' => 'ai', 'name' => __( 'A.I. Scanner', 'secupress' ) . ' ' . __( '(Under Development)', 'secupress' ), 'desc' => __( 'AI-driven detection to distinguish real malware from false positives with greater accuracy.', 'secupress' ), 'class' => 'unavailable', ], ]; } function secupress_get_malware_scan_last_time() { $option_name = defined( 'SECUPRESS_MALWARE_SCAN_LAST_TIME' ) ? SECUPRESS_MALWARE_SCAN_LAST_TIME : 'secupress_malware_scan_last_time'; return (int) get_option( $option_name, 0 ); } /** * Return the path to a class. * * @since 1.0 * @author Grégory Viguier * * @param (string) $prefix Only one possible value so far: "scan". * @param (string) $class_name_part The classes name is built as follow: "SecuPress_{$prefix}_{$class_name_part}". * * @return (string) Path of the class. */ function secupress_class_path( $prefix, $class_name_part = '' ) { $folders = array( 'scan' => 'scanners', 'singleton' => 'common', 'logs' => 'common', 'log' => 'common', 'cleanup-leftovers' => 'common', 'scanner-results' => 'common', ); $prefix = strtolower( str_replace( '_', '-', $prefix ) ); $folder = isset( $folders[ $prefix ] ) ? $folders[ $prefix ] : $prefix; $class_name_part = strtolower( str_replace( '_', '-', $class_name_part ) ); $class_name_part = $class_name_part ? '-' . $class_name_part : ''; return SECUPRESS_CLASSES_PATH . $folder . '/class-secupress-' . $prefix . $class_name_part . '.php'; } /** * Require a class. * * @since 1.0 * @author Grégory Viguier * * @param (string) $prefix Only one possible value so far: "scan". * @param (string) $class_name_part The classes name is built as follow: "SecuPress_{$prefix}_{$class_name_part}". */ function secupress_require_class( $prefix, $class_name_part = '' ) { $path = secupress_class_path( $prefix, $class_name_part ); if ( $path ) { require_once( $path ); } } /** * Will load the async classes. * * @since 1.0 * @author Grégory Viguier */ function secupress_require_class_async() { /* https://github.com/A5hleyRich/wp-background-processing v1.0 */ secupress_require_class( 'Admin', 'wp-async-request' ); secupress_require_class( 'Admin', 'wp-background-process' ); } /** --------------------------------------------------------------------------------------------- */ /** SCAN / FIX ================================================================================== */ /** --------------------------------------------------------------------------------------------- */ /** * Return all tests to scan * * @since 1.0 * @author Grégory Viguier * * @return (array) Tests to scan. */ function secupress_get_scanners() { $tests = array( 'users-login' => array( 0 => 'Admin_User', 1 => 'Easy_Login', 2 => 'Subscription', 3 => 'Passwords_Strength', 4 => 'Bad_Usernames', 5 => 'Login_Errors_Disclose', ), 'plugins-themes' => array( 0 => 'Plugins_Update', 1 => 'Themes_Update', 2 => 'Bad_Old_Plugins', 3 => 'Bad_Vuln_Plugins', 4 => 'Inactive_Plugins_Themes', 5 => 'Bad_Old_Themes', // 2.2.6 ), 'wordpress-core' => array( 0 => 'Core_Update', 1 => 'Auto_Update', 2 => 'Bad_Old_Files', 3 => 'Bad_Config_Files', 4 => 'WP_Config', 5 => 'DB_Prefix', 6 => 'Salt_Keys', 7 => 'WPOrg', ), 'sensitive-data' => array( 0 => 'Discloses', 1 => 'Readme_Discloses', 2 => 'PHP_Disclosure', 3 => 'HTTPS', 4 => 'Bad_Url_Access', ), 'file-system' => array( 0 => 'Chmods', 1 => 'Directory_Listing', 2 => 'Bad_File_Extensions', 3 => 'Malware_Scanners', ), 'firewall' => array( 0 => 'Shellshock', 1 => 'Bad_User_Agent', // DO NOT DELETE COMMENTED, DO NOT REUSE THE INDEXES. // 2 => 'SQLi', // 3 => 'Anti_Scanner', // 4 => 'Anti_Front_Brute_Force', // 5 => 'Bad_Request_Methods', // 6 => 'Bad_Url_Access', see "sentitive-data" 7 => 'PhpVersion', 8 => 'Php_404', ), ); // 3rd party. if ( class_exists( 'SitePress' ) ) { $tests['sensitive-data'][3] = 'Wpml_Discloses'; } if ( class_exists( 'WooCommerce' ) ) { $tests['sensitive-data'][4] = 'Woocommerce_Discloses'; } if ( secupress_is_ecommerce() ) { /** * @since 2.0 Do not remove login errors when e-commerce is active. **/ unset( $tests['users-login'][5] ); } return apply_filters( 'secupress.scanner.tests', $tests ); } /** * Get tests that can't be fixes from the network admin. * * @since 2.2.6 Bad_Old_Themes * @author Julio Potier * @since 1.0 * @author Grégory Viguier * * @return (array) Array of "class name parts". */ function secupress_get_tests_for_ms_scanner_fixes() { return array( 'Bad_Old_Plugins', 'Bad_Old_Themes', 'Subscription', ); } /** * Get SecuPress scanner counter(s). * * @since 1.0 * @author Grégory Viguier * * @param (string) $type Info to retrieve: good, warning, bad, notscannedyet, grade, total. * @return (string|array) The desired counter info if `$type` is provided and the info exists. An array of all counters otherwise. */ function secupress_get_scanner_counts( $type = '' ) { static $counts; if ( ! isset( $counts ) ) { $tests_by_status = secupress_get_scanners(); $scanners = secupress_get_scan_results(); $fixes = secupress_get_fix_results(); unset( $tests_by_status['users-login'][1] ); // 2FA unset( $tests_by_status['firewall'][7] ); // PHP Version $empty_statuses = array( 'good' => 0, 'warning' => 0, 'bad' => 0 ); $scanners_count = $scanners ? array_count_values( wp_list_pluck( $scanners, 'status' ) ) : array(); $counts = array_merge( $empty_statuses, $scanners_count ); $total = array_sum( array_map( 'count', $tests_by_status ) ); $counts['notscannedyet'] = $total - array_sum( $counts ); $counts['total'] = $total; $counts['percent'] = (int) floor( $counts['good'] * 100 / $counts['total'] ); $counts['hasaction'] = 0; if ( $fixes ) { foreach ( $fixes as $test_name => $fix ) { if ( ! empty( $fix['has_action'] ) ) { ++$counts['hasaction']; } } } if ( 100 <= $counts['percent'] ) { $counts['grade'] = 'A'; } elseif ( $counts['percent'] >= 80 ) { // 20 less $counts['grade'] = 'B'; } elseif ( $counts['percent'] >= 65 ) { // 15 less $counts['grade'] = 'C'; } elseif ( $counts['percent'] >= 52 ) { // 13 less $counts['grade'] = 'D'; } elseif ( $counts['percent'] >= 42 ) { // 10 less $counts['grade'] = 'E'; } elseif ( $counts['percent'] >= 34 ) { // 8 less $counts['grade'] = 'F'; } elseif ( $counts['percent'] >= 28 ) { // 6 less $counts['grade'] = 'G'; } elseif ( $counts['percent'] >= 22 ) { // 6 less $counts['grade'] = 'H'; } elseif ( $counts['percent'] >= 16 ) { // 6 less $counts['grade'] = 'I'; } elseif ( $counts['percent'] >= 10 ) { // 6 less $counts['grade'] = 'J'; } elseif ( 0 === $counts['percent'] ) { // 0... $counts['grade'] = '—'; // Old (∅) } else { $counts['grade'] = 'K'; // < 10 % } $label = $counts['grade']; $counts['temp_grade'] = $counts['grade']; if ( isset( $scanners['bad_vuln_plugins']['status'] ) && 'bad' === $scanners['bad_vuln_plugins']['status'] ) { $counts['temp_grade'] .= '-'; $label .= '-'; } elseif ( isset( $scanners['malware_scanners']['status'] ) && 'bad' === $scanners['malware_scanners']['status'] ) { $counts['temp_grade'] .= '-'; $label .= '-'; } elseif ( isset( $scanners['bad_vuln_themes']['status'] ) && 'bad' === $scanners['bad_vuln_themes']['status'] ) { $counts['temp_grade'] .= '-'; $label .= '-'; } elseif ( ( isset( $scanners['easy_login']['status'] ) && 'good' === $scanners['easy_login']['status'] ) || ( isset( $scanners['phpversion']['status'] ) && 'good' === $scanners['phpversion']['status'] ) ) { $counts['temp_grade'] .= '+'; $label .= '+'; } switch ( strlen( $label ) ) { case 3: $css_class = ' secupress-grade-plus-plus'; break; case 2: $css_class = ' secupress-grade-plus'; break; default: $css_class = ''; break; } $counts['letter'] = '<span class="letter l' . $counts['grade'][0] . $css_class . '">' . $label . '</span>'; $counts['color'] = '195,34,34'; switch ( $counts['grade'] ) { case 'A': $counts['text'] = __( 'Congratulations! 🎉', 'secupress' ); $counts['color'] = '43,205,193'; break; case 'B': $counts['text'] = __( 'Almost perfect!', 'secupress' ); $counts['color'] = '241,196,15'; break; case 'C': $counts['text'] = __( 'Not bad, but try to fix more items.', 'secupress' ); $counts['color'] = '247,171,19'; break; case 'D': $counts['text'] = __( 'Well, it’s not good yet.', 'secupress' ); $counts['color'] = '242,41,94'; break; case 'E': $counts['text'] = __( 'Better than nothing, but still not good.', 'secupress' ); $counts['color'] = '203,35,79'; break; case 'F': $counts['text'] = __( 'Not good at all, fix more issues.', 'secupress' ); break; case 'G': $counts['text'] = __( 'Bad, fix issues right away!', 'secupress' ); break; case 'H': $counts['text'] = __( 'Still very bad, start fixing things!', 'secupress' ); break; case 'I': $counts['text'] = __( 'Very bad. You should take some actions.', 'secupress' ); break; case 'J': $counts['text'] = __( 'Very very bad, please fix something!', 'secupress' ); break; case 'K': $counts['text'] = __( 'Very very, really very bad.', 'secupress' ); break; case '—': // Old (∅) $counts['text'] = __( 'Error when saving the scanner results.', 'secupress' ); // Old (ᕗ‶⇀︹↼)ᕗ彡┻━┻ break; } $counts['subtext'] = sprintf( _n( 'Your grade is %1$s with %2$d good scanned item.', 'Your grade is %1$s with %2$d good scanned items.', $counts['good'], 'secupress' ), $counts['letter'], $counts['good'] ); } $counts['grade'] = $counts['temp_grade']; if ( $type ) { // Make sure to not return the whole array if a type is given, even if it isn't set. return isset( $counts[ $type ] ) ? $counts[ $type ] : ''; } return $counts; } /** * Tell if we can perform "extra fix actions" (something we do on page reload after a fix is done). * * @author Grégory Viguier * @since 1.2.3 * * @return (bool) */ function secupress_can_perform_extra_fix_action() { global $pagenow; return empty( $_POST ) && ! wp_doing_ajax() && ! defined( 'DOING_AUTOSAVE' ) && is_admin() && 'admin-post.php' !== $pagenow && is_user_logged_in(); // WPCS: CSRF ok. } /** --------------------------------------------------------------------------------------------- */ /** PLUGINS ===================================================================================== */ /** --------------------------------------------------------------------------------------------- */ /** * Check whether a plugin is active. * * @author Grégory Viguier * @since 1.0 * * @param (string) $plugin A plugin path, relative to the plugins folder. * @return (bool) */ function secupress_is_plugin_active( $plugin ) { $plugins = (array) get_option( 'active_plugins', [] ); $plugins = array_flip( $plugins ); return isset( $plugins[ $plugin ] ) || secupress_is_plugin_active_for_network( $plugin ); } /** * Check whether a plugin is active for the entire network. * * @since 1.0 * @author Grégory Viguier * * @param (string) $plugin A plugin path, relative to the plugins folder. * @return (bool) */ function secupress_is_plugin_active_for_network( $plugin ) { if ( ! is_multisite() ) { return false; } $plugins = get_site_option( 'active_sitewide_plugins' ); return isset( $plugins[ $plugin ] ); } /** * Check if a plugin is installed (by slug) * * @since 2.4.1 * @author Julio Potier * * @param string $plugin_slug The plugin slug (directory name) * @return bool True if the plugin is installed */ function secupress_is_plugin_installed( $plugin_slug ) { if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $all_plugins = get_plugins(); foreach ( $all_plugins as $plugin_file => $plugin_data ) { if ( dirname( $plugin_file ) === $plugin_slug ) { return true; } } return false; } /** --------------------------------------------------------------------------------------------- */ /** DIE ========================================================================================= */ /** --------------------------------------------------------------------------------------------- */ /** * Die with SecuPress format. * * @author Julio Potier * @since 2.0 Add the response code * @author Grégory Viguier * @since 1.0 * * @param (string) $message Guess what. * @param (string) $title Window title. * @param (array) $args An array of arguments. */ function secupress_die( $message = '', $title = '', $args = array() ) { $has_p = strpos( $message, '<p>' ) !== false; $message = ( $has_p ? '' : '<p>' ) . $message . ( $has_p ? '' : '</p>' ); $message = '<h1>' . SECUPRESS_PLUGIN_NAME . '</h1>' . $message; $url = secupress_get_current_url( 'raw' ); $force_die = ! empty( $args['force_die'] ); $context = ! empty( $args['context'] ) ? $args['context'] : ''; $is_scan_request = secupress_is_scan_request(); // Used to bypass the whitelist for scans. $attack_type = isset( $args['attack_type'] ) ? $args['attack_type'] : false; if ( $attack_type ) { // We can die when it's not an attack. secupress_log_attack( $attack_type ); } /** * Filter the message. * * @since 1.0 * * @param (string) $message The message displayed. * @param (string) $url The current URL. * @param (array) $args Facultative arguments. * @param (bool) $is_scan_request Tell if the request comes from one of our scans. */ $message = apply_filters( 'secupress.die.message', $message, $url, $args, $is_scan_request, $context ); /** * Fires right before `wp_die()`. * * @since 1.0 * * @param (string) $message The message displayed. * @param (string) $url The current URL. * @param (array) $args Facultative arguments. * @param (bool) $is_scan_request Tell if the request comes from one of our scans. */ do_action( 'secupress.before.die', $message, $url, $args, $is_scan_request, $context ); if ( $force_die || $is_scan_request ) { // Die. // Tell cache plugins not to cache our error message. if ( ! defined( 'DONOTCACHEPAGE' ) ) { define( 'DONOTCACHEPAGE', true ); } if ( ! defined( 'DONOTCACHEOBJECT' ) ) { define( 'DONOTCACHEOBJECT', true ); } if ( ! defined( 'DONOTCACHEDB' ) ) { define( 'DONOTCACHEDB', true ); } if ( ! empty( $args['response'] ) ) { http_response_code( absint( $args['response'] ) ); } // https://core.trac.wordpress.org/ticket/53262 remove_filter( 'wp_robots', 'wp_robots_noindex_embeds' ); remove_filter( 'wp_robots', 'wp_robots_noindex_search' ); wp_die( $message, $title, $args ); } } /** * Block a request and die with more informations. * * @since 1.0 * @author Grégory Viguier * * @param (string) $module The related module. * @param (array|int|string) $args Contains the "code" (def. 403) and a "content" (def. empty), this content will replace the default message. * $args can be used only for the "code" or "content" or both using an array. */ function secupress_block( $module, $args = array( 'code' => 403 ) ) { $ip = secupress_get_ip(); /** * Allow to give a proper name to the block ID. * * @since 1.1.4 * * @param (string) $module The related module. */ $block_id = apply_filters( 'secupress_block_id', $module ); if ( $block_id === $module ) { $block_id = ucwords( str_replace( '-', ' ', $block_id ) ); $block_id = preg_replace( '/[^0-9A-Z]/', '', $block_id ); } if ( is_int( $args ) ) { $args = array( 'code' => (int) $args ); // Cast to prevent recursion. } elseif ( is_string( $args ) ) { $args = array( 'content' => (string) $args ); // Cast to prevent recursion. } $args = wp_parse_args( $args, array( 'code' => 403, 'content' => '', 'b64' => [], 'attack_type' => 'ban_ip' ) ); // Preventing the display of possible sent passwords $hidden = '***… // ' . sprintf( __( 'Hidden by %s.', 'secupress' ), SECUPRESS_PLUGIN_NAME ); foreach ( [ 'password', 'psswrd', 'pass', 'pwd', 'pw', 'user_pass', 'edd_user_pass' ] as $key ) { if ( isset( $_REQUEST[ $key ] ) ) { $_REQUEST[ $key ] = $hidden; } if ( isset( $_GET[ $key ] ) ) { $_GET[ $key ] = $hidden; } if ( isset( $_POST[ $key ] ) ) { $_POST[ $key ] = $hidden; } } // Use these filters to remove or modify contents $_REQUEST = apply_filters( 'secupress.block.remove_content_from._REQUEST', $_REQUEST ); $_GET = apply_filters( 'secupress.block.remove_content_from._GET', $_GET ); $_POST = apply_filters( 'secupress.block.remove_content_from._POST', $_POST ); $_COOKIE = apply_filters( 'secupress.block.remove_content_from._COOKIE', $_COOKIE ); $_FILES = apply_filters( 'secupress.block.remove_content_from._FILES', $_FILES ); $data = var_export( [ '$_REQUEST' => $_REQUEST, '$_GET' => $_GET, '$_POST' => $_POST, '$_COOKIE' => $_COOKIE, '$_FILES' => $_FILES, ], true ); // Add some hardcoded b64 args to be printed for support help. $args['b64']['URL'] = secupress_get_current_url( 'raw' ); $args['b64']['SP'] = secupress_has_pro() ? 'Pro v' . SECUPRESS_PRO_VERSION : 'Free v' . SECUPRESS_VERSION; $args['b64']['ID'] = $module; $args['b64']['data'] = $data; $args['b64']['user'] = is_user_logged_in() ? var_export( wp_get_current_user()->user_login, true ) : false; /** * Fires before a user is blocked by a certain module. * * @since 1.0 * @since 1.1.4 Added `$block_id` argument. * * @param (string) $ip The IP address. * @param (array) $args Contains the "code" (def. 403) and a "content" (def. empty), this content will replace the default message. * @param (string) $block_id The block ID. */ do_action( 'secupress.block.' . $module, $ip, $args, $block_id ); /** * Fires before a user is blocked. * * @since 1.0 * @since 1.1.4 Added `$block_id` argument. * * @param (string) $module The module. * @param (string) $ip The IP address. * @param (array) $args Contains the "code" (def. 403) and a "content" (def. empty), this content will replace the default message. * @param (string) $block_id The block ID. */ do_action( 'secupress.block', $module, $ip, $args, $block_id ); $title = $args['code'] . ' ' . get_status_header_desc( $args['code'] ); $content = '<h2>' . $title . '</h2>'; if ( ! $args['content'] ) { $content .= '<p>' . __( 'You are not allowed to access the requested page.', 'secupress' ) . '</p>'; } else { $content .= '<p>' . $args['content'] . '</p>'; } $content .= '<h3>' . __( 'Logged Details:', 'secupress' ) . '</h3>'; $content .= '<p>'; $content .= sprintf( __( 'Your IP: %s', 'secupress' ), $ip ) . '<br>'; $content .= sprintf( __( 'Time: %s', 'secupress' ), date_i18n( _x( 'F j, Y g:i a', 'date', 'secupress' ) ) ) . '<br>'; $content .= sprintf( __( 'Reason: %s', 'secupress' ), $block_id ) . '<br>'; $content .= sprintf( __( 'Support ID: %s', 'secupress' ), '<textarea style="width:100%;height:27px;vertical-align:text-top">' . base64_encode( json_encode( $args['b64'] ) ) . '</textarea>' ) . '<br>'; $content .= '</p>'; secupress_die( $content, $title, [ 'response' => $args['code'], 'force_die' => true, 'attack_type' => $args['attack_type'] ] ); } /** * Tell if the request comes from one of our scans by detecting a specific header. * Careful, this header can be forged, the result is not trustful. * * @since 1.0 * @author Grégory Viguier * * @return (bool) True if the request comes from a scan. False otherwize. */ function secupress_is_scan_request() { return ! empty( $_SERVER['HTTP_X_SECUPRESS_ORIGIN'] ); } /** --------------------------------------------------------------------------------------------- */ /** OTHER TOOLS ================================================================================= */ /** --------------------------------------------------------------------------------------------- */ /** * Create a URL to easily access to our pages. * * @author Julio Potier * @since 1.4.4 'get-pro' $page is now returning the external URL * @author Grégory Viguier * @since 1.0 * * @param (string) $page The last word of the secupress page slug. * @param (string) $module The required module. * * @return (string) The URL. */ function secupress_admin_url( $page, $module = '' ) { if ( 'get-pro' === $page ) { if ( secupress_has_pro() ) { return admin_url( 'admin.php?page=secupress_modules#module-secupress_display_apikey_options' ); } return trailingslashit( set_url_scheme( SECUPRESS_WEB_MAIN, 'https' ) ) . _x( 'pricing', 'link to website (Only FR or EN!)', 'secupress' ) . $module; } $module = $module ? '&module=' . $module : ''; $page = str_replace( '&', '_', $page ); $url = 'admin.php?page=' . SECUPRESS_PLUGIN_SLUG . '_' . $page . $module; return is_multisite() ? network_admin_url( $url ) : admin_url( $url ); } /** * Get the user capability/role required to work with the plugin. * * @author Grégory Viguier * @since 1.0 * * @param (bool) $force_mono Set to true to get the capability/role for monosite, whatever we're on multisite or not. * * @return (string) The capability. */ function secupress_get_capability( $force_mono = false, $context = '' ) { if ( ! $force_mono && is_multisite() ) { return 'manage_network_options'; } $role = 'administrator'; /** * Filter the user capability/role that gives access to SecuPress features. * * @since 2.2 $context param * @since 1.0 * * @param (string) $role * @param (string) $context */ return apply_filters( 'secupress.user_capability', $role, $context ); } /** * Add SecuPress informations into USER_AGENT. * * @since 2.2.6 Remove "white_label" * @since 2.0 Remove "do_beta" * @author Julio Potier * * @since 1.1.4 Available in global scope. * @since 1.0 * @author Grégory Viguier * * @param (string) $user_agent A User Agent. * * @return (string) */ function secupress_user_agent( $user_agent ) { return sprintf( '%s;SecuPress|%s|%s|%s|%s;', $user_agent, esc_url( secupress_get_main_url() ), SECUPRESS_VERSION, $GLOBALS['wp_version'], phpversion() ); } /** * Get the site main URL. Will be the same for any site of a network, and for any lang of a multilang site. * * @since 1.2.2 * @author Grégory Viguier * * @return (string) The URL. */ function secupress_get_main_url() { $current_network = false; if ( function_exists( 'get_network' ) ) { $current_network = get_network(); } elseif ( function_exists( 'get_current_site' ) ) { $current_network = get_current_site(); } if ( ! $current_network ) { if ( function_exists( '__get_option' ) ) { if ( __get_option( 'siteurl' ) ) { return __get_option( 'siteurl' ); } } else { return get_option( 'siteurl' ); } } $scheme = secupress_server_is_ssl() ? 'https' : 'http'; $main_url = set_url_scheme( 'http://' . $current_network->domain . $current_network->path, $scheme ); return untrailingslashit( $main_url ); } /** * Is this version White Labeled? * * @author Grégory Viguier * @since 1.0 * @since 1.1.4 Available in global scope. * * @return (bool) */ function secupress_is_white_label() { if ( ! secupress_is_pro() ) { return false; } $names = array( 'wl_plugin_name', 'wl_plugin_URI', 'wl_description', 'wl_author', 'wl_author_URI' ); foreach ( $names as $value ) { if ( false !== secupress_get_option( $value ) ) { return true; } } return false; } /** * Get SecuPress logo. * * @since 2.2.6 $return parameter * @since 1.0.6 Remove the yellow Pro logo * @author Julio Potier * * @since 1.0 * @author Geoffrey Crofte * * @param (array) $atts An array of HTML attributes. * @param (string) $return 'html': as <IMG> ; 'url': https:// ...png ; 'path': /home/www/ ...png * @return (string) The HTML tag. */ function secupress_get_logo( $atts = [], $return = 'html' ) { $is_pro = secupress_is_pro() ? '-pro' : ''; $base_url = SECUPRESS_ADMIN_IMAGES_URL . 'logo' . $is_pro; $base_path = SECUPRESS_ADMIN_PATH . 'images/logo' . $is_pro; if ( secupress_is_white_label() ) { /** * If white label is activated, no SecuPress logo is retrieved. * * @since 1.4.2 * * @param (string) Should return a <img> or dashicon span tag. * @param (array) $atts Attributes, contains logo size. */ switch ( $return ) { case 'url': $base_url = $base_url . '-white-label.png'; break; case 'path': $base_url = $base_path . '-white-label.png'; break; default: case 'html': $base_url = $base_url . '-white-label'; break; } $base_url = apply_filters( 'secupress.white_label.logo', $base_url, $atts, $return ); } switch ( $return ) { case 'url': return $base_url . '.png'; break; case 'path': return $base_path . '.png'; break; } // case 'html': // below $atts = array_merge( [ 'src' => "{$base_url}.png", 'srcset' => secupress_is_white_label() ? '' : "{$base_url}2x.svg 1x, {$base_url}2x.svg 2x", 'alt' => '', ], $atts ); $attributes = ''; foreach ( $atts as $att => $value ) { $attributes .= " {$att}=\"{$value}\""; } return "<img{$attributes}/>"; } /** * Get SecuPress logo word. * * @author Julio Potier * @since 2.0 Set as test to print the version * @author Grégory Viguier * @since 1.0 * * @param (array) $atts An array of HTML attributes. * * @return (string) The HTML tag. */ function secupress_get_logo_word( $atts = array() ) { return sprintf( '%s v%s', SECUPRESS_PLUGIN_NAME, SECUPRESS_VERSION ); } /** * Tell if users can register, whatever we're in a Multisite or not. * * @since 1.0 * @author Grégory Viguier * * @return (bool) */ function secupress_users_can_register() { if ( ! is_multisite() ) { return (bool) get_option( 'users_can_register' ); } $registration = get_site_option( 'registration' ); return 'user' === $registration || 'all' === $registration; } /** * Get the email address used when the plugin send a message. * * @since 1.0 * @author Grégory Viguier * * @param (bool) $from_header True to return the "from" header. * * @return (string) */ function secupress_get_email( $from_header = false ) { $sitename = strtolower( $_SERVER['SERVER_NAME'] ); if ( substr( $sitename, 0, 4 ) === 'www.' ) { $sitename = substr( $sitename, 4 ); } /** * Give the possibility to replace the "from" email address * * @since 2.0.1 Change the order to let SP have priority, but can also use the default WP one with new context param * @since 1.0 * * @param (string) */ $email = apply_filters( 'secupress.get_email', 'noreply@' . $sitename ); $email = apply_filters( 'wp_mail_from', $email ); return $from_header ? 'from: ' . SECUPRESS_PLUGIN_NAME . ' <' . $email . '>' : $email; } /** * Send mail. * * @author Julio Potier * @since 2.2.6 Can replace LOGINURL * @since 2.0 Can replace SITEURL, ADMIN_EMAIL, default plain/text instead of html. * * @author Grégory Viguier * @since 1.2.4 Can replace SITENAME * * @param (string|array) $to Array or comma-separated list of email addresses to send message. * @param (string) $subject Email subject. * @param (string) $message Message contents. * @param (array) $headers Optional. Additional headers. * @param (string|array) $attachments Optional. Files to attach. * * @return (bool) Whether the email contents were sent successfully. */ function secupress_send_mail( $to, $subject, $message, $headers = [], $attachments = array() ) { $replacement = [ '###SITENAME###', '###ADMIN_EMAIL###', '###SITEURL###', '###LOGINURL###' ]; /** * Filter the replacements strings * * @since 2.2.6 * @author Julio Potier */ $replacement = apply_filters( 'secupress.mail.replacement', $replacement ); $replaced = [ secupress_get_blogname(), get_option( 'admin_email' ), home_url(), wp_login_url() ]; /** * Filter the replaced strings * * @since 2.2.6 * @author Julio Potier */ $replaced = apply_filters( 'secupress.mail.replaced', $replaced ); $subject = str_replace( $replacement, $replaced, $subject ); $subject = wp_specialchars_decode( $subject ); $message = str_replace( $replacement, $replaced, $message ); $headers = array_merge( [ 'from' => secupress_get_email( true ), ], $headers ); /** * Filter the email headers * * @since 1.2.4 * @author Grégory Viguier */ $headers = apply_filters( 'secupress.mail.headers', $headers ); /** * Action before the wp_mail * * @since 2.2.6 * @author Julio Potier */ do_action( 'secupress.mail.before.wp_mail', [ $to, $subject, $message, $headers, $attachments ] ); return wp_mail( $to, $subject, $message, $headers, $attachments ); } /** * Get the blog name or host if empty. * * @since 1.4.9 * @author Julio Potier * * @return (string) */ function secupress_get_blogname() { static $blogname; if ( ! isset( $blogname ) ) { /** * The blogname option is escaped with esc_html on the way into the database in sanitize_option * we want to reverse this for the plain text arena of emails. */ $blogname = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES ); $blogname = $blogname ?: parse_url( home_url(), PHP_URL_HOST ); } return $blogname; } /** * Return the current URL. * * @since 2.2.6 'domain' param * @since 2.0 Remove usage of HTTP_HOST and $port * @author Julio Potier * * @since 1.0 * @author Grégory Viguier * * * @param (string) $mode What to return: raw (all), base (before '?'), uri (before '?', without the domain), 'domain' (only the domain 'example.com'). * @return (string) */ function secupress_get_current_url( $mode = 'base' ) { $host = str_replace( [ 'http://', 'https://', '/' ], '', home_url() ); $url = ! empty( $GLOBALS['HTTP_SERVER_VARS']['REQUEST_URI'] ) ? $GLOBALS['HTTP_SERVER_VARS']['REQUEST_URI'] : ( ! empty( $_SERVER['REQUEST_URI'] ) ? $_SERVER['REQUEST_URI'] : '' ); $url = 'http' . ( secupress_server_is_ssl() ? 's' : '' ) . '://' . $host . str_replace( '//', '/', $url ); switch ( $mode ) { case 'uri' : $home = set_url_scheme( home_url() ); $url = explode( '?', $url, 2 ); $url = reset( $url ); $url = str_replace( $home, '', $url ); return trim( $url, '/' ); case 'base' : $url = explode( '?', $url, 2 ); return reset( $url ); case 'domain' : $url = parse_url( get_site_url(), PHP_URL_HOST ); $url = preg_replace( '/^www\./', '', $url ); // Remove "www." if present return $url; default : case 'raw' : return $url; } } /** * Store, get or delete static data. * Getter: no need to provide a second parameter. * Setter: provide a second parameter for the value. * Deletter: provide null as second parameter to remove the previous value. * * @since 1.0 * @author Grégory Viguier * * @param (string) $key An identifier key. * * @return (mixed) The stored data or null. */ function secupress_cache_data( $key ) { static $data = array(); $func_get_args = func_get_args(); if ( array_key_exists( 1, $func_get_args ) ) { if ( null === $func_get_args[1] ) { unset( $data[ $key ] ); } else { $data[ $key ] = $func_get_args[1]; } } return isset( $data[ $key ] ) ? $data[ $key ] : null; } /** * Get the main blog ID. * * @since 1.0 * @author Grégory Viguier * * @return (int) */ function secupress_get_main_blog_id() { static $blog_id; if ( ! isset( $blog_id ) ) { if ( ! is_multisite() ) { $blog_id = 1; } elseif ( ! empty( $GLOBALS['current_site']->blog_id ) ) { $blog_id = absint( $GLOBALS['current_site']->blog_id ); } elseif ( defined( 'BLOG_ID_CURRENT_SITE' ) ) { $blog_id = absint( BLOG_ID_CURRENT_SITE ); } $blog_id = ! empty( $blog_id ) ? $blog_id : 1; } return $blog_id; } /** * Is current WordPress version older than X.X.X? * * @since 1.0 * @author Grégory Viguier * * @param (string) $version The version to test. * * @return (bool) Result of the `version_compare()`. */ function secupress_wp_version_is( $version ) { global $wp_version; static $is = array(); if ( isset( $is[ $version ] ) ) { return $is[ $version ]; } return ( $is[ $version ] = version_compare( $wp_version, $version ) >= 0 ); } /** * Check whether WordPress is in "installation" mode. * * @since 1.0 * @author Grégory Viguier * * @return (bool) true if WP is installing, otherwise false. */ function secupress_wp_installing() { return function_exists( 'wp_installing' ) ? wp_installing() : defined( 'WP_INSTALLING' ) && WP_INSTALLING; } /** * Tell if the site frontend is served over SSL. * * @since 1.0 * @author Grégory Viguier * * @return (bool) **/ function secupress_is_site_ssl() { static $is_site_ssl; if ( isset( $is_site_ssl ) ) { return $is_site_ssl; } if ( is_multisite() ) { switch_to_blog( secupress_get_main_blog_id() ); $site_url = get_option( 'siteurl' ); $home_url = get_option( 'home' ); restore_current_blog(); } else { $site_url = get_option( 'siteurl' ); $home_url = get_option( 'home' ); } $is_site_ssl = strpos( $site_url, 'https://' ) === 0 && strpos( $home_url, 'https://' ) === 0; /** * Filter the value of `$is_site_ssl`, that tells if the site frontend is served over SSL. * * @since 1.0 * * @param (bool) $is_site_ssl True if the site frontend is served over SSL. */ $is_site_ssl = apply_filters( 'secupress.front.is_site_ssl', $is_site_ssl ); return $is_site_ssl; } /** * Like in_array but for nested arrays. * * @since 1.0 * @author Grégory Viguier * * @param (mixed) $needle The value to find. * @param (array) $haystack The array to search. * * @return (bool) */ function secupress_in_array_deep( $needle, $haystack ) { if ( $haystack ) { foreach ( $haystack as $item ) { if ( $item === $needle || ( is_array( $item ) && secupress_in_array_deep( $needle, $item ) ) ) { return true; } } } return false; } /** * `array_intersect_key()` + `array_merge()`. * * @since 1.0 * @author Grégory Viguier * * @param (array) $values The array we're interested in. * @param (array) $default The array we use as boudaries. * * @return (array) */ function secupress_array_merge_intersect( $values, $default ) { $values = array_merge( $default, $values ); return array_intersect_key( $values, $default ); } /** * Get the consumer email. * * @since 1.0 * @author Grégory Viguier * * @return (string|bool) The email if it is valid. False otherwise. */ function secupress_get_consumer_email() { return is_email( secupress_get_option( 'consumer_email' ) ); } /** * Get the consumer key. * * @since 2.2.6 * @author Julio Potier * * @since 1.0 * @author Grégory Viguier * * @return (string) */ function secupress_get_consumer_key() { $key = secupress_get_option( 'consumer_key' ); if ( 'B5E0B5F8DD8689E6ACA49DD6E6E1A930' === $key ) { return 'B5E0-nulled'; } return $key; } /** * Return true if secupress pro is installed * * @since 1.3 * @author Julio Potier * * @return (bool) */ function secupress_has_pro() { return defined( 'SECUPRESS_PRO_VERSION' ); } /** * Return true if the license is ok. * * @since 1.3 * @author Grégory Viguier * * @return (bool) */ function secupress_has_pro_license() { static $has_pro; if ( ! isset( $has_pro ) ) { $has_pro = secupress_get_consumer_key() && 1 === secupress_get_option( 'site_is_pro' ); } return $has_pro; } /** * Return true if secupress pro is installed and the license is ok. * * @since 1.0 * @author Grégory Viguier * * @return (bool) */ function secupress_is_pro() { return secupress_has_pro() && secupress_has_pro_license(); } /** * Tell if a feature exists as expert mode * * @since 2.3.17 * @author Julio Potier * * @return (bool) True if the feature is in list. */ function secupress_feature_is_expert( $feature ) { $features = [ // Field names. 'content-protect_bad-url-access|allowed' => 1, 'advanced-settings_expert-mode-main' => 1, 'plugins_installation-pro' => 1, 'blacklist-logins_lexicomatisation' => 1, 'move-login_whattodo|honeypot' => 1, 'login-protection_geoip_login_mode' => 1, 'login-protection_geoip_login_device' => 1, ]; return isset( $features[ $feature ] ); } /** * Tell if a feature is for pro version. * * @since 1.0 * @author Grégory Viguier * * @param (string) $feature The feature to test. Basically it can be: * - A field "name" when the whole field is pro: the result of `$this->get_field_name( $field_name )`. * - A field "name + value" when only one (or some) of the values is pro: the result of `$this->get_field_name( $field_name ) . "|" . $value`. * * @return (bool) True if the feature is in the list. */ function secupress_feature_is_pro( $feature ) { $features = [ // Field names. 'login-protection_sessions_control' => 1, 'login-protection_geoip_login' => 1, 'blacklist-logins_prevent-user-creation' => 1, 'double-auth_type' => 1, 'password-policy_force-logout' => 1, 'password-policy_send-emails' => 1, 'password-policy_password_expiration' => 1, 'password-policy_strong_passwords' => 1, 'double-auth_prevent-low-encryption' => 1, 'double-auth_prevent-hash-reuse' => 1, 'plugins_detect_bad_plugins' => 1, 'plugins_installation-pro' => 1, 'themes_activation' => 1, 'themes_deletion' => 1, 'themes_detect_bad_themes' => 1, 'uploads_uploads' => 1, 'content-protect_hotlink' => 1, 'content-protect_404guess' => 1, // 'file-scanner_file-scanner' => 1, // no need to show the logo here, the UI is different. 'content-protect_bad-url-access|allowed' => 1, 'backup-files_backup-file' => 1, 'backup-db_backup-db' => 1, 'backup-history_backup-history' => 1, 'import-export_export_settings' => 1, 'import-export_import_settings' => 1, 'geoip-system_type' => 1, 'schedules-backups_type' => 1, 'schedules-backups_periodicity' => 1, 'schedules-backups_email' => 1, 'schedules-backups_scheduled' => 1, 'schedules-scan_type' => 1, 'schedules-scan_periodicity' => 1, 'schedules-scan_email' => 1, 'schedules-scan_scheduled' => 1, 'schedules-file-monitoring_type' => 1, 'schedules-file-monitoring_periodicity' => 1, 'schedules-file-monitoring_email' => 1, 'schedules-file-monitoring_scheduled' => 1, 'notification-types_types' => 1, 'alerts_activated' => 1, 'event-alerts_module-alerts' => 1, 'backups-storage_location' => 1, 'event-alerts_activated' => 1, 'notification-types_emails' => 1, 'notification-types_slack' => 1, 'daily-reporting_activated' => 1, 'move-login_whattodo|custom_error' => 1, 'move-login_whattodo|custom_page' => 1, 'move-login_whattodo|honeypot' => 1, 'move-login_singlesignon' => 1, 'login-protection_type|passwordspraying' => 1, 'database_db_prefix' => 1, 'database_tables_selection' => 1, 'bbq-headers_bad-referer' => 1, 'bbq-headers_bad-referer-list' => 1, 'bbq-headers_block-ai' => 1, 'blacklist-logins_user-creation-protection' => 1, 'blacklist-logins_bad-email-domains' => 1, ]; return isset( $features[ $feature ] ); } /** * Tell if a user is affected by its role for the asked module. * * @since 1.0 * @author Julio Potier * * @param (string) $module A module. * @param (string) $submodule A sub-module. * @param (object) $user A WP_User object. * * @return (-1|bool) -1 = every role is affected, true = the user's role is affected, false = the user's role isn't affected. */ function secupress_is_affected_role( $module, $submodule, $user ) { $roles = secupress_get_module_option( $submodule . '_affected_role', [], $module ); if ( ! $roles ) { return -1; } if ( ! secupress_is_user( $user ) ) { $user = secupress_get_user_by( $user ); } $user = secupress_is_user( $user, true ); if ( ! $user ) { return false; } return secupress_is_user( $user) && ! array_intersect( $roles, $user->roles ); } /** * Used with the filter hook 'nonce_user_logged_out' to create nonces for disconnected users. * * @since 2.2.6 crc32() * @since 2.2.5.2 hash( 'crc32b' ) * @author Julio Potier * * @since 1.0 * @author Grégory Viguier * * @param (int) $uid A userID. * @param (string) $action The action. * * @return (int) */ function secupress_modify_userid_for_nonces( $uid = 0, $action = '' ) { return crc32( $uid . $action . secupress_get_ip() ); } /** * Tell if the param $user is a real user from your installation. * * @since 2.4.1 Handle stdClass objects with valid user ID and optional conversion * @since 1.0 * @author Julio Potier * * @param (mixed) $user The object to be tested to be a valid user. * @param (bool) $convert_to_wp_user Optional. Whether to convert stdClass to WP_User object. * * @return (bool|WP_User) True if valid user (bool), WP_User object if $convert_to_wp_user is true */ function secupress_is_user( $user, $convert_to_wp_user = false ) { // Already a valid WP_User if ( is_a( $user, 'WP_User' ) && user_can( $user, 'exist' ) ) { return $convert_to_wp_user ? $user : true; } // Check if it's a stdClass with valid user ID if ( is_object( $user ) && get_class( $user ) === 'stdClass' && isset( $user->ID ) && is_numeric( $user->ID ) && $user->ID > 0 ) { $wp_user = new WP_User( $user->ID ); if ( user_can( $wp_user, 'exist' ) ) { return $convert_to_wp_user ? $wp_user : true; } } return false; } /** * Get all meta values from a meta key for all users * * @since 2.2.6 * @author Julio Potier * * @param (string) The meta key * @return (array) */ function secupress_get_user_metas( $meta_key ) { global $wpdb; $res = secupress_cache_data( __FUNCTION__ . $meta_key ); if ( ! $res ) { $res = $wpdb->get_results( $wpdb->prepare( "SELECT user_id, meta_value FROM {$wpdb->usermeta} WHERE meta_key = %s", sanitize_key( $meta_key ) ), ARRAY_A ); secupress_cache_data( __FUNCTION__ . $meta_key, $res ); } return $res; } /** * Compress some data to be stored in the database. * * @since 1.0.6 * @author Grégory Viguier * * @param (mixed) $data The data to compress. * * @return (string) The compressed data. */ function secupress_compress_data( $data ) { /** Little and gentle obfuscation to avoid being tagged as "malicious script", I hope you understand :) — Julio. */ $gz = 'eta'; $gz = 'gz' . strrev( $gz . 'lfed' ); $bsf = 'cne'; $bsf = strrev( 'edo' . $bsf ); $bsf = '64_' . $bsf; $bsf = 'base' . $bsf; return $bsf// Hey. ( $gz// Hoy. ( serialize( $data ) ) ); } /** * Decompress some data coming from the database. * * @since 1.0.6 * @author Grégory Viguier * * @param (string) $data The data to decompress. * * @return (mixed) The decompressed data. */ function secupress_decompress_data( $data ) { if ( ! $data || ! is_string( $data ) ) { return $data; } /** Little and gentle obfuscation to avoid being tagged as "malicious script", I hope you understand :) — Julio. */ $gz = 'eta'; $gz = 'gz' . strrev( $gz . 'lfni' ); $bsf = 'ced'; $bsf = strrev( 'edo' . $bsf ); $bsf = '64_' . $bsf; $bsf = 'base' . $bsf; $data_tmp = $bsf// Hey. ( $data ); if ( ! $data_tmp ) { return $data; } $data = $data_tmp; $data_tmp = $gz// Hoy. ( $data ); if ( ! $data_tmp ) { return $data; } return maybe_unserialize( $data_tmp ); } /** * Try to increase the memory limit if possible. * * @since 1.0 * @author Grégory Viguier */ function secupress_maybe_increase_memory_limit() { if ( ! wp_is_ini_value_changeable( 'memory_limit' ) ) { return; } $limits = array( '64M' => 67108864, '128M' => 134217728, '256M' => 268435456, ); $current_limit = @ini_get( 'memory_limit' ); $current_limit_int = wp_convert_hr_to_bytes( $current_limit ); if ( -1 === $current_limit_int || $current_limit_int > $limits['256M'] ) { return; } foreach ( $limits as $limit => $bytes ) { if ( $current_limit_int < $bytes ) { @ini_set( 'memory_limit', $limit ); return; } } } /** * Register a settings error to be displayed to the user. * This a clone of `add_settings_error()`, but available in the global scope. * * @since 1.3 * @author Grégory Viguier * * @param (string) $setting Slug title of the setting to which this error applies. * @param (string) $code Slug-name to identify the error. Used as part of 'id' attribute in HTML output. * @param (string) $message The formatted message text to display to the user (will be shown inside styled * `<div>` and `<p>` tags). * @param (string) $type Optional. Message type, controls HTML class. Accepts 'error' or 'updated'. * Default 'error'. */ function secupress_add_settings_error( $setting, $code, $message, $type = 'error' ) { global $wp_settings_errors; $wp_settings_errors[] = array( 'setting' => $setting, 'code' => $code, 'message' => $message, 'type' => $type, ); } /** * Fetch settings errors registered by `add_settings_error()` and `secupress_add_settings_error()`. * This a clone of `get_settings_errors()`, but available in the global scope. * * @since 1.3 * @author Grégory Viguier * * @param (string) $setting Optional slug title of a specific setting who's errors you want. * @param (boolean) $sanitize Whether to re-sanitize the setting value before returning errors. * * @return (array) Array of settings errors */ function secupress_get_settings_errors( $setting = '', $sanitize = false ) { global $wp_settings_errors; /** * If `$sanitize` is true, manually re-run the sanitization for this option. * This allows the $sanitize_callback from register_setting() to run, adding any settings errors you want to show by default. */ if ( $sanitize ) { sanitize_option( $setting, get_option( $setting ) ); } // If settings were passed back from options.php then use them. if ( isset( $_GET['settings-updated'] ) && $_GET['settings-updated'] && get_transient( 'settings_errors' ) ) { $wp_settings_errors = array_merge( (array) $wp_settings_errors, get_transient( 'settings_errors' ) ); // WPCS: override ok. delete_transient( 'settings_errors' ); } // Check global in case errors have been added on this pageload. if ( is_array( $wp_settings_errors ) && ! count( $wp_settings_errors ) ) { return array(); } // Filter the results to those of a specific setting if one was set. if ( $setting ) { $setting_errors = array(); foreach ( (array) $wp_settings_errors as $key => $details ) { if ( $setting === $details['setting'] ) { $setting_errors[] = $wp_settings_errors[ $key ]; } } return $setting_errors; } return is_array( $wp_settings_errors ) ? $wp_settings_errors : []; } /** * Checks whether function is disabled. * * @since 1.4.5 * @author Julio Potier * * @param (string) $function Name of the function. * @return (bool) Whether or not the function is disabled. */ function secupress_is_function_disabled( $function ) { if ( ! function_exists( $function ) ) { return true; } $disabled = explode( ',', @ini_get( 'disable_functions' ) ); $disabled = array_map( 'trim', $disabled ); $disabled = array_flip( $disabled ); return isset( $disabled[ $function ] ); } /** * Return an aray of translated expert modules with links, set by SECUPRESS_EXPERT_MODULES_ON GLOBAL var * * @since 2.3.17 * @author Julio Potier * * @return (array) **/ function secupress_get_expert_modules_on() { global $SECUPRESS_EXPERT_MODULES_ON; if ( empty( $SECUPRESS_EXPERT_MODULES_ON ) ) { return; } $values['bad_url_access'] = '"<a href="' . secupress_admin_url( 'modules', 'sensitive-data#row-content-protect_bad-url-access' ) . '">' . __( 'Bad Url Access', 'secupress' ) . '</a>"'; $values['plugin_actions'] = '"<a href="' . secupress_admin_url( 'modules', 'plugins-themes#row-plugins_actions' ) . '">' . __( 'Plugin Actions', 'secupress' ) . '</a>"'; $values['lexicomatisation'] = '"<a href="' . secupress_admin_url( 'modules', 'users-login#row-blacklist-logins_activated' ) . '">' . __( 'Rename user names', 'secupress' ) . '</a>"'; return array_intersect_key( $values, $SECUPRESS_EXPERT_MODULES_ON ); } function secupress_no_contextual_help() { _deprecated_function( __FUNCTION__, '2.3.19', 'secupress_show_contextual_help' ); return ! secupress_show_contextual_help(); } /** * Returns true if SECUPRESS_MODE contains "expert" or setting is on * * @since 2.3.19 Revamp * @since 2.3.17 main * @since 2.0.1 Read the new setting too * @since 1.4.6 * @author Julio Potier * * @return (bool|null) **/ function secupress_is_expert_mode( $from_constant = false ) { if ( defined( 'SECUPRESS_MODE' ) && ( false !== strpos( strtolower( constant( 'SECUPRESS_MODE' ) ), 'expert' ) ) ) { $constant = true; } if ( $from_constant ) { return isset( $constant ) ? $constant : null; } if ( isset( $constant ) && is_bool( $constant ) ) { return $constant; } return secupress_get_expert_modules_on() || secupress_get_module_option( 'advanced-settings_expert-mode-main', false, 'welcome' ); } /** * Returns true if user option is '1' or SECUPRESS_MODE does not contains "help" * * @since 2.3.18.1 * @since 2.3.17 * @author Julio Potier * * @return (bool|null) **/ function secupress_show_contextual_help( $from_constant = false ) { if ( defined( 'SECUPRESS_MODE' ) && ( false !== strpos( strtolower( constant( 'SECUPRESS_MODE' ) ), 'help' ) ) ) { $constant = false; } if ( $from_constant ) { return isset( $constant ) ? $constant : null; } if ( isset( $constant ) && is_bool( $constant ) ) { return $constant; } return secupress_get_module_option( 'advanced-settings_expert-mode', true, 'welcome' ); } /** * Returns true if user option is '1' or SECUPRESS_MODE contains "adminbar" * * @since 2.3.19 * @author Julio Potier * * @return (bool|null) **/ function secupress_show_adminbar( $from_constant = false ) { if ( defined( 'SECUPRESS_MODE' ) && ( false !== strpos( strtolower( constant( 'SECUPRESS_MODE' ) ), 'adminbar' ) ) ) { $constant = false; } if ( $from_constant ) { return isset( $constant ) ? $constant : null; } if ( isset( $constant ) && is_bool( $constant ) ) { return $constant; } return secupress_get_module_option( 'advanced-settings_admin-bar', true, 'welcome' ); } /** * Returns true if user option is '1' or SECUPRESS_MODE contains "grade" * * @since 2.3.19 * @author Julio Potier * * @return (bool|null) **/ function secupress_show_grade_system( $from_constant = false ) { if ( defined( 'SECUPRESS_MODE' ) && ( false !== strpos( strtolower( constant( 'SECUPRESS_MODE' ) ), 'grade' ) ) ) { $constant = false; } if ( $from_constant ) { return isset( $constant ) ? $constant : null; } if ( isset( $constant ) && is_bool( $constant ) ) { return $constant; } return secupress_get_module_option( 'advanced-settings_grade-system', true, 'welcome' ); } /** * Set recursive chmod rights on a path * * @since 2.0 * @author Julio Potier * * @param (string) $path Default value ABSPATH * @return (void) **/ function secupress_set_recursive_chmod_rights( $path = ABSPATH ) { $dir = new DirectoryIterator( $path ); $exts = [ 'php' => 1, 'js' => 1, 'css' => 1 ]; /** * Filter the file extensions that will be chmoded * * @since 2.0 * @param (array) $exts */ $exts = apply_filters( 'secupress.chmod.file_types', $exts ); foreach ( $dir as $item ) { if ( $item->isDot() ) { continue; } if ( $item->isDir() ) { @chmod( $item->getPathname(), 0755 ); secupress_set_recursive_chmod_rights( $item->getPathname() ); } elseif( isset( $exts[ pathinfo( $item->getPathname(), PATHINFO_EXTENSION ) ] ) ) { @chmod( $item->getPathname(), 0644 ); } } } /** * Checks whether the website is using HTTPS. * This is based on whether both the home and site URL are using HTTPS. * * @since 2.0 * @author Julio Potier * * @see wp_is_using_https() * * @param (string) 'both', 'site', 'home' accepted, anything else will return false; * @return (bool) True if site is actually using HTTPS **/ function secupress_site_is_using_https( $type = 'both' ) { $home = 'https' === wp_parse_url( get_option( 'home' ), PHP_URL_SCHEME ); $site = 'https' === wp_parse_url( apply_filters( 'site_url', get_option( 'siteurl' ), '', null, null ), PHP_URL_SCHEME ); $both = $home && $site; return isset( $$type ) && $$type; } /** * Check if https with ssl verify returns an error * * @since 2.2.6 secupress_server_is_ssl() = * @since 2.0 * @author Julio Potier * * @see wp_is_https_supported() * * @return (bool) True if https/ssl is OK **/ function secupress_is_https_supported() { if ( ! secupress_server_is_ssl() ) { $error = new WP_Error(); $error->add( 'invalid_ssl', sprintf( __( 'SSL is not supported on HTTP PORT %s', 'secupress' ), esc_html( $_SERVER['SERVER_PORT'] ) ) ); set_transient( 'secupress_is_https_supported', $error, 6 * HOUR_IN_SECONDS ); return false; } if ( ! wp_http_supports( [ 'ssl' ] ) ) { $error = new WP_Error(); $error->add( 'invalid_ssl', __( 'SSL is not supported on this website.', 'secupress' ) ); set_transient( 'secupress_is_https_supported', $error, 6 * HOUR_IN_SECONDS ); return false; } $response = get_transient( 'secupress_is_https_supported' ); if ( $response && ! is_wp_error( $response ) ) { return true; } $response = wp_remote_get( home_url( '/', 'https' ), [ 'headers' => [ 'Cache-Control' => 'no-cache', ], 'sslverify' => true, ] ); set_transient( 'secupress_is_https_supported', $response, 6 * HOUR_IN_SECONDS ); return ! is_wp_error( $response ); } /** * Determines if SSL is used, Cloudflare Compatible. * * @since 2.2.6 * @author Julio Potier * * @param (bool) $with_cloudflare Whenever to also check the CF-Visitor value * * @return (bool) **/ function secupress_server_is_ssl( $with_cloudflare = true ) { $is_ssl = false; if ( isset( $_SERVER['HTTPS'] ) ) { if ( 'on' === strtolower( $_SERVER['HTTPS'] ) ) { $is_ssl = true; } if ( '1' === (string) $_SERVER['HTTPS'] ) { $is_ssl = true; } } elseif ( isset( $_SERVER['SERVER_PORT'] ) && ( '443' === (string) $_SERVER['SERVER_PORT'] ) ) { $is_ssl = true; } // Cloudflare if ( $with_cloudflare && isset( $_SERVER['HTTP_CF_VISITOR'] ) ) { $cf_visitor = json_decode( $_SERVER['HTTP_CF_VISITOR'], true ); if ( isset( $cf_visitor['scheme'] ) && 'https' === $cf_visitor['scheme'] ) { $is_ssl = true; } } /** * Filter the SSL return * * @since 2.2.§ * @param (bool) $is_sll * @param (bool) $with_cloudflare */ return apply_filters( 'secupress.is_sll', $is_ssl, $with_cloudflare ); } /** * Update home and siteurl with HTTPS * * @since 2.2.6 Do not relay on the WP function * @since 2.0 * @author Julio Potier * * @see wp_update_urls_to_https() * @return (bool) True if URL are updated **/ function secupress_update_urls_to_https() { if ( ! current_user_can( 'update_https' ) ) { return false; } // Get current URL options. $orig_home = get_option( 'home' ); $orig_siteurl = get_option( 'siteurl' ); // Get current URL options, replacing HTTP with HTTPS. $home = str_replace( 'http://', 'https://', $orig_home ); $siteurl = str_replace( 'http://', 'https://', $orig_siteurl ); // Update the options. update_option( 'home', $home ); update_option( 'siteurl', $siteurl ); return true; } /** * Before each usage of secupress_send_slack_notification(), maybe check if this is still correct * * @since 2.0 * @author Julio Potier * * @see secupress_send_slack_notification() * * @return (bool) True if still OK, false if not. **/ function secupress_maybe_reset_slack_notifs() { $url = secupress_get_module_option( 'notification-types_slack', false, 'alerts' ); $accepted = secupress_get_option( 'notification-types_slack', false ); if ( apply_filters( 'secupress.notifications.slack.bypass', false ) || ( ! empty( $accepted ) && $url === $accepted ) ) { return true; } secupress_set_option( 'notification-types_slack', 0 ); return false; } /** * Try to delete a file, if not, will empty the file, if not, will rename it. * * @since 2.2.6 time() * @since 1.4.3 * @author Julio Potier * * @param (string) $file The file to be deleted. * @return (bool) **/ function secupress_remove_old_plugin_file( $file ) { // Is it a sym link ? if ( is_link( $file ) ) { $file = @readlink( $file ); } // Try to delete. if ( file_exists( $file ) && ! @unlink( $file ) ) { // Or try to empty it. $fh = @fopen( $file, 'w' ); $fw = @fwrite( $fh, '<?php // File removed by SecuPress' ); @fclose( $fh ); if ( ! $fw ) { // Or try to rename it. return @rename( $file, $file . '.' . time() . '.old' ); } } return true; } /** * Translate the WP role as we want to * * @since 2.0 * @author Julio Potier * * @param (string) $role * * @return (string) **/ function secupress_translate_user_role( $role ) { global $wp_roles; _x( 'Administrator', 'User role', 'secupress' ); _x( 'Editor', 'User role', 'secupress' ); _x( 'Author', 'User role', 'secupress' ); _x( 'Contributor', 'User role', 'secupress' ); _x( 'Subscriber', 'User role', 'secupress' ); $role = isset( $wp_roles->role_names[ $role ] ) ? $wp_roles->role_names[ $role ] : $role; $translation = translate_user_role( $role, 'secupress' ); // If a new role is added (even by a plugin etc), and we do not know it, backcompat with WP domain. if ( 0 === strcmp( $translation, $role ) ) { $translation = translate_user_role( $role ); } return $translation; } /** * Our own set_time_limit function because some hosts are banning it * * @param (int) seconds * @author Julio Potier * @since 2.2 * @return (bool) True if one of the function was usable, False if not **/ function secupress_time_limit( $seconds ) { if ( function_exists( 'set_time_limit' ) ) { set_time_limit( (int) $seconds ); return true; } elseif( function_exists( 'ini_set' ) ) { ini_set( 'max_execution_time', (int) $seconds ); return true; } return false; } /** * Get a scan or fix status, formatted with icon and human readable text. * * @since 1.0 * @author Grégory Viguier * * @param (string) $status The status code. * @return (string) Formatted status. */ function secupress_status( $status ) { $statuses = []; $statuses['bad'] = _x( 'Bad', 'scan result', 'secupress' ); $statuses['good'] = _x( 'Good', 'scan result', 'secupress' ); $statuses['warning'] = _x( 'Pending', 'scan result', 'secupress' ); $statuses['cantfix'] = _x( 'Error', 'scan result', 'secupress' ); return isset( $statuses[ $status ] ) ? $statuses[ $status ] : _x( 'New', 'scanner item', 'secupress' ); } /** * Retrieve messages by their ID and format them by wrapping them in `<ul>` and `<li>` tags. * * @since 1.0 * @author Grégory Viguier * * @param (array) $msgs An array of messages. * @param (string) $test_name The scanner name. * * @return (string) An HTML list of formatted messages. */ function secupress_format_message( $msgs, $test_name ) { $classname = 'SecuPress_Scan_' . $test_name; $messages = $classname::get_instance()->get_messages(); $output = array(); if ( empty( $msgs ) ) { return implode( '<br/>', $output ); } foreach ( $msgs as $id => $atts ) { if ( ! isset( $messages[ $id ] ) ) { $string = __( 'Fix done.', 'secupress' ); } elseif ( is_array( $messages[ $id ] ) ) { $count = array_shift( $atts ); $string = translate_nooped_plural( $messages[ $id ], $count ); } else { $string = $messages[ $id ]; } if ( $atts ) { foreach ( $atts as $i => $att ) { if ( is_array( $att ) ) { $atts[ $i ] = wp_sprintf_l( '%l', $att ); } } } $output[] = ! empty( $atts ) ? vsprintf( $string, $atts ) : $string; } return implode( '<br/>', $output ); } /** * Log blocked attacks * * @author Julio Potier * @since 2.2.6 * **/ function secupress_log_attack( $type ) { $attack_types = get_option( SECUPRESS_ATTACKS, [] ); $current_date = date( 'md' ); // Format MMDD if ( ! isset( $attack_types[ $type ] ) ) { $attack_types[ $type ] = []; } // Ensure it's an array (new format) if ( ! is_array( $attack_types[ $type ] ) ) { $attack_types[ $type ] = []; } // Increment count for current date if ( ! isset( $attack_types[ $type ][ $current_date ] ) ) { $attack_types[ $type ][ $current_date ] = 0; } ++$attack_types[ $type ][ $current_date ]; // Increment "all" counter (non-dated cumulative total) if ( ! isset( $attack_types['all'] ) ) { $attack_types['all'] = 0; } ++$attack_types['all']; update_option( SECUPRESS_ATTACKS, $attack_types ); } /** * Get blocked attacks by type or all * * @author Julio Potier * @since 2.2.6 * * @param (string) $type 'All' or a type of attacks, see secupress_attacks_get_type_title() **/ function secupress_get_attacks( $type = 'all' ) { $attack_types = get_option( SECUPRESS_ATTACKS, [] ); if ( 'all' === $type ) { return $attack_types; } if ( isset( $attack_types[ $type ] ) ) { // Return the data as-is (can be array with dates or single value for backward compatibility) return $attack_types[ $type ]; } return false; } /** * Get the translated attack titles * * @author Julio Potier * @since 2.2.6 * **/ function secupress_attacks_get_type_title( $type ) { switch ( $type ) { case 'ban_ip': return __( 'Banned IPs', 'secupress' ); break; case 'plugins': return __( 'Plugin Interactions', 'secupress' ); break; case 'theme': return __( 'Theme Interactions', 'secupress' ); break; case 'zipfile': return __( 'ZIP Upload Attempts', 'secupress' ); break; case 'xmlrpc': return __( 'XMLRPC Intrusions', 'secupress' ); break; case 'move_login': return __( 'Login Page', 'secupress' ); break; case 'loginattempts': return __( 'Login Attempts', 'secupress' ); break; case 'passwordspraying': return __( 'Password Spraying Attempts', 'secupress' ); break; case 'users': return __( 'User Actions', 'secupress' ); break; case 'bad_robots': return __( 'Bad Robots', 'secupress' ); break; case 'bad_request_content': return __( 'Bad Request Content', 'secupress' ); break; case 'honeypot': return __( 'Honeypot', 'secupress' ); break; case 'all': return _x( 'All', 'types of attacks', 'secupress' ); break; default: return _x( 'Unknown', 'type of attack', 'secupress' ); break; } } /** * Get all files from a given folder * * @author Julio Potier * @since 2.2.6 * * @param (string) $path * @return (array) $list List of files only **/ function secupress_list_files_from_folder( $path ) { $list = []; $content = scandir( $path ); foreach( $content as $element ) { if ( $element !== '.' && $element !== '..' ) { $path_element = $path . '/' . $element; if ( is_dir( $path_element ) ) { $_list = secupress_list_files_from_folder( $path_element ); foreach ( $_list as $file ) { $list[] = $element . '/' . $file; } } else { $list[] = $element; } } } return $list; } add_action( 'login_form_md5', 'secupress_login_form_md5' ); /** * Dirty function to create a md5 in JS * * @since 2.2.6 from wp_ajax_nopriv_md5 to login_form_md5 * @since 2.0 * @author Julio Potier * * @return (string) A md5 string **/ function secupress_login_form_md5( $t ) { if ( isset( $_GET['e'] ) ) { die( md5( trim( $_GET['e'] ) ) ); } else { die( '-1' ); } } /** * Test if the current browser runs on a mobile device (smart phone, tablet, etc.). * * @see wp_is_mobile() * * @since 2.2.6 * @author Julio Potier * * @return (bool) $is_mobile **/ function secupress_is_mobile( $ua = null ) { if ( is_null( $ua ) ) { return wp_is_mobile(); } $is_mobile = false; if ( str_contains( $ua, 'Mobile' ) || str_contains( $ua, 'Android' ) || str_contains( $ua, 'Silk/' ) || str_contains( $ua, 'Kindle' ) || str_contains( $ua, 'BlackBerry' ) || str_contains( $ua, 'Opera Mini' ) || str_contains( $ua, 'Opera Mobi' ) || str_contains( $ua, 'iPhone' ) || str_contains( $ua, 'iPod' ) || str_contains( $ua, 'webOS' ) || str_contains( $ua, 'Symbian' ) || str_contains( $ua, 'Windows Phone' ) ) { $is_mobile = true; } /** * @see WP /wp-includes/vars.php */ return apply_filters( 'wp_is_mobile', $is_mobile ); } /** * Get information about device, browser, OS, more accurate * * @since 2.6 * @author Julio Potier * * @return (array) **/ function secupress_get_device_infos() { require_once( SECUPRESS_MODULES_PATH . 'users-login/plugins/inc/php/BrowserDetection.php' ); $user_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? $_SERVER['HTTP_USER_AGENT'] : 'Mozilla/5.0 (Unknown) GenericBrowser/1.0'; $f_browser = new foroco\BrowserDetection(); $data = $f_browser->getAll( $user_agent ); $_64b_i18n = $data['64bits_mode'] ? ' ' . __( '64 Bits', 'secupress' ) : ''; $browser = 'unknown' !== $data['browser_name'] ? $data['browser_name'] : 'Unknown Browser'; // Do not translate this $os = 'unknown' !== $data['os_name'] ? $data['os_title'] . ', ' . ucwords( $data['os_family'] ) . $_64b_i18n : 'Unknown OS'; // Do not translate this $device = 'unknown' !== $data['device_type'] ? ucwords( $data['device_type'] ) : 'Unknown Device'; // Do not translate this $sign_keys = [ 'browser_name', 'browser_chrome_original', 'browser_firefox_original', 'browser_safari_original', 'browser_android_webview', 'browser_ios_webview', 'browser_desktop_mode', 'os_title', 'os_family', 'device_type', '64bits_mode', ]; $sign_data = array_intersect_key( $data, array_flip( $sign_keys ) ); return [ 'browser' => $browser, 'os' => $os, 'device' => $device, 'sign' => md5( serialize( $sign_data ) ), 'raw_ua' => $user_agent, ]; } /** /** * Get info for a given plugin * * @since 2.2.6 * @author Julio Potier * * @param (string) $plugin_slug * * @return (string) $plugin **/ function secupress_plugins_api( $plugin_slug ) { $api = get_site_transient( 'secupress_plugins_api_' . $plugin_slug ); if ( false === $api ) { if ( ! function_exists( 'plugins_api' ) ) { require_once( ABSPATH . 'wp-admin/includes/plugin-install.php' ); } $api = plugins_api( 'plugin_information', [ 'slug' => $plugin_slug, 'fields' => [ // https://core.trac.wordpress.org/ticket/60783#ticket // 'downloadlink' => false, 'short_description' => false, 'description' => false, 'tested' => false, 'requires' => false, 'requires_php' => false, 'rating' => false, 'ratings' => false, 'num_ratings' => false, 'requires_plugins' => false, 'support' => false, 'support_url' => false, 'support_threads' => false, 'support_threads_resolved' => false, 'downloaded' => false, 'screenshots' => false, 'business_model' => false, 'repository_url' => false, 'commercial_support_url' => false, 'preview_link' => false, 'last_updated' => false, 'added' => false, 'tags' => false, 'compatibility' => false, 'homepage' => false, 'donate_link' => false, 'contributors' => false, 'sections' => false, 'active_installs' => false, 'group' => false, 'icons' => false, 'reviews' => false, 'banners' => false, 'versions' => false, ], ] ); if ( ! is_object( $api ) || is_wp_error( $api ) ) { return new WP_Error( 'plugin_not_found', __( 'Plugin not found', 'secupress' ) ); } $api->source = 'wp_org'; set_site_transient( 'secupress_plugins_api_' . $plugin_slug, $api, WEEK_IN_SECONDS ); } else { $api->source = 'db'; } return $api; } /** * Return a string containing the server type name between "apache" "nginx" "iis7" "unknown" * * @since 2.2.6 * @author Julio Potier * * @return (string) */ function secupress_get_server_type() { global $is_apache, $is_nginx, $is_iis7; static $server_type; if ( isset( $server_type ) ) { return $server_type; } switch( true ) { case $is_apache: $server_type = 'apache'; break; case $is_nginx: $server_type = 'nginx'; break; case $is_iis7: $server_type = 'iis7'; break; default: $server_type = 'unknown'; break; } return $server_type; } /** * Return the function name depending on the server type * * @since 2.2.6 * @author Julio Potier * * @param (string) $prefix * @param (string) $default * @return (string) */ function secupress_get_function_name_by_server_type( $prefix, $default = '__return_empty_string' ) { global $is_apache, $is_nginx, $is_iis7; $server_type = secupress_get_server_type(); $function = $prefix . $server_type; return function_exists( $function ) ? $function : $default; } /** * If the http request is ajax, cron, json, xml, REST, wp.com it's not "soft". * * @since 2.3.19.1 admin-post * @since 2.3.19 APP_REQUEST IS_WPCOM REST_API_REQUEST * @since 2.3.18 * @author Julio Potier * * @return (bool) False if any of advanced request detected **/ function secupress_is_soft_request() { global $pagenow; return ! ( wp_doing_ajax() || ( ! empty( $pagenow ) && 'admin-post.php' === $pagenow ) || wp_is_json_request() || wp_is_jsonp_request() || wp_doing_cron() || wp_is_xml_request() || ( defined( 'XMLRPC_REQUEST' ) && constant( 'XMLRPC_REQUEST' ) ) || ( defined( 'APP_REQUEST' ) && constant( 'APP_REQUEST' ) ) || ( defined( 'IS_WPCOM' ) && constant( 'IS_WPCOM' ) ) || ( defined( 'REST_REQUEST' ) && constant( 'REST_REQUEST' ) ) || ( defined( 'REST_API_REQUEST' ) && constant( 'REST_API_REQUEST' ) ) ); } /** * Returns only the main user fields. * * @since 2.3.19 * * @see WP_User->get_data_by() * * @param (string) $field The field to query against: Accepts 'id/ID', 'slug/nicename', 'email', 'login', 'display_name'. * @param (string|int) $value The field value. * @param (bool) $like Usage of "LIKE" in the DB Query * @param (string) $sanitize_cb A callable callback function on $value * * @author Julio Potier * @return (WP_User|false) Raw user object. */ function secupress_get_user_data_by( $field, $value, $like = false, $sanitize_cb = '' ) { global $wpdb; $like = (bool) $like; if ( ! is_int( $value ) && ! is_string( $value ) ) { return false; } if ( false !== strpos( $sanitize_cb, 'trim' ) ) { $sanitize_cb = ''; } // 'id' is an alias of 'ID'. if ( 'ID' === strtoupper( $field ) ) { $field = 'ID'; $sanitize_cb = 'absint'; } // 'slug' is an alias of 'nicename'. if ( 'slug' === $field || 'nicename' === $field ) { $field = 'user_nicename'; $sanitize_cb = 'sanitize_title'; } if ( 'ID' === $field && ! is_numeric( $value ) ) { return false; } if ( $sanitize_cb && is_callable( $sanitize_cb ) ) { try { set_error_handler( '__return_false', E_ALL ); $result = call_user_func( $sanitize_cb, $value ); restore_error_handler(); $value = $result ?: $value; } catch ( Throwable $e ) { error_log( 'Sanitize callback failed on ' . __FILE__ . ' in ' . __FUNCTION__ . ' on line ' . __LINE__ . ': ' . $e->getMessage() ); return false; } } $value = trim( $value ); if ( ! $value ) { return false; } if ( $like ) { $value = secupress_esc_like( $value ); } $db_field = $field; $user_id = false; switch ( $field ) { case 'ID': $user_id = $value; break; case 'nicename': $user_id = wp_cache_get( $value, 'userslugs' ); $db_field = 'user_nicename'; break; case 'email': $user_id = wp_cache_get( $value, 'useremail' ); $db_field = 'user_email'; break; case 'login': $value = sanitize_user( $value ); $user_id = wp_cache_get( $value, 'userlogins' ); $db_field = 'user_login'; break; default: $user_id = wp_cache_get( $value, "user{$field}" ); break; } $user = false; if ( false !== $user_id ) { $user = wp_cache_get( $user_id, 'users' ); } if ( ! $user ) { switch( $like ) { case true: $like = 'LIKE'; break; case false: $like = '='; break; } $sql = "SELECT * FROM $wpdb->users WHERE $db_field %s %s ORDER BY $db_field ASC LIMIT 1"; $sql = sprintf( $sql, $like, '%s' ); $sql = $wpdb->prepare( $sql, $value ); $user = $wpdb->get_row( $sql ); } if ( ! isset( $user->ID ) ) { return false; } $user = new WP_User( $user->ID ); if ( ! is_a( $user, 'WP_User' ) ) { return false; } if ( ! empty( $user->user_nicename ) ) { update_user_caches( $user ); } return $user; } /** * Get a user by id, login or email * * @since 2.4.1 Allow WP_Error object as value * @since 2.3.18.1 Revamp to use secupress_get_user_data_by() * @since 2.2.6 * @author Julio Potier * * @see get_user_by() * @see secupress_get_user_data_by() * * @param (string|int) $value The field value. * * @return (bool|WP_User) False or WP_User */ function secupress_get_user_by( $value, $return_field = '' ) { // Already a user. if ( secupress_is_user( $value ) || is_a( $value, 'WP_Error' ) ) { return $value; } $user = false; $by = ''; // Asking for an ID? if ( is_int( $value ) ) { $by = 'ID'; $user = secupress_get_user_data_by( $by, $value ); } // Asking for an email or login? if ( ! secupress_is_user( $value ) ) { $by = is_email( $value ) ? 'email' : 'login'; $user = secupress_get_user_data_by( $by, $value ); } // Asking for an email as login? if ( ! secupress_is_user( $user ) && is_email( $value ) ) { $by = 'login'; $user = secupress_get_user_data_by( $by, $value ); } // Asking for a nicename? if ( ! secupress_is_user( $user ) ) { $by = 'nicename'; $user = secupress_get_user_data_by( $by, $value ); } // Asking for a display name? if ( ! secupress_is_user( $user ) ) { $by = 'display_name'; $user = secupress_get_user_data_by( $by, $value ); } // WHAT ARE YOU ASKING FOR?? if ( ! secupress_is_user( $user ) ) { return false; } // We got it. if ( $return_field ) { if ( isset( $user->{$return_field} ) ) { $user->secupress_get_user_by_field = $return_field; return $user->{$return_field}; } else { return false; } } $user->secupress_get_user_by_field = $by; return $user; } add_filter( 'wp_login_errors', 'secupress_display_relogin_message' ); /** * Display a message on the login form. * * @since 2.3.19 Revamp * @since 1.0 * @author Julio Potier * * @param (object) $errors * * @return (object) $errors */ function secupress_display_relogin_message( $errors ) { if ( ! isset( $_GET['secupress-relog'] ) || empty( wp_get_referer() ) || ! secupress_is_soft_request() ) { return $errors; } if ( empty( $errors ) ) { $errors = new WP_Error(); } switch ( $_GET['secupress-relog'] ) { case 'none': case '': case '0': case 0: // do nothing break; case 'new-login': $errors->add( 'secupress_relog', __( 'You will receive your new login confirmation in your mailbox.', 'secupress' ), 'message' ); break; case 'new-password': $errors->add( 'secupress_relog', __( 'You will receive your new password confirmation in your mailbox.', 'secupress' ), 'message' ); break; default: // Should be an integer, user_id. $user_id = (int) $_GET['secupress-relog']; if ( $user_id ) { // user_id 0 exists for cron jobs, we don't want them. $notices = secupress_get_transient( 'secupress-notices-' . $user_id, [] ); if ( is_array( $notices ) && ! empty( $notices ) ) { delete_transient( 'secupress-notices-' . $user_id ); foreach ( $notices as $notice ) { $errors->add( $notice['error_code'], $notice['message'], 'message' ); } } } break; } return $errors; } /** * A simple function to encrypt a key, 1 level better than B 64 * Compatible even without openssl available (but weaker) * * /!\ DO NOT USE WITH SENSITIVE DATA, BETTER DO A PASSWORD_VERIFY * * @since 2.3.21 * @author Julio Potier * * @param (string) $key (in clear) * * @return (string) encrypted $key **/ function secupress_encrypt_secret_key( $key ) { $iv = random_bytes( 16 ); if ( ! function_exists( 'openssl_encrypt' ) ) { return base64_encode( $iv . $key ); } $mk = secupress_get_option( 'master_key' ); $enc = openssl_encrypt( $key, 'aes-256-cbc', $mk, 0, $iv ); return base64_encode( $iv . $enc ); } /** * A simple function to decrypt a key, 1 level better than B 64 * Compatible even without openssl available (but weaker) * * /!\ DO NOT USE WITH SENSITIVE DATA, BETTER DO A PASSWORD_VERIFY * * @since 2.3.21 * @author Julio Potier * * @param (string) encrypted $key * * @return (string) decrypted $key **/ function secupress_decrypt_secret_key( $enc ) { $data = base64_decode( $enc ); $iv = substr( $data, 0, 16 ); $key = substr( $data, 16 ); if ( ! function_exists( 'openssl_encrypt' ) ) { return $key; } $mk = secupress_get_option( 'master_key' ); return openssl_decrypt( $key, 'aes-256-cbc', $mk, 0, $iv ); } /** * Create the main hash, that can change * It's not exported! * * @since 2.3.21 * @author Julio Potier * * @return (string) **/ function secupress_create_hash_key() { $hk = secupress_get_option( 'hash_key' ); if ( $hk ) { return $hk; } $hk = secupress_generate_key( 64 ); // Do not use secupress_get_option() here. $options = get_site_option( SECUPRESS_SETTINGS_SLUG ); $options['hash_key'] = $hk; secupress_update_options( $options ); return $hk; } /** * Create another hash, master that should never change * It's not exported! * * @since 2.3.21 * @author Julio Potier * * @return (string) **/ function secupress_create_master_key() { $mk = secupress_get_option( 'master_key' ); if ( $mk ) { return $mk; } $mk = secupress_generate_key( 64 ); // Do not use secupress_get_option() here. $options = get_site_option( SECUPRESS_SETTINGS_SLUG ); $options['master_key'] = $mk; secupress_update_options( $options ); return $mk; } /** * Check if the number of users equals the number of administrators. * This is used to determine if the Honeypot option should be available. * * @since 2.5 * @author Julio Potier * * @return (bool) True if all users are administrators, false otherwise. */ function secupress_is_honeypotable() { $user_counts = count_users(); $total_users = $user_counts['total_users']; $admin_count = isset( $user_counts['avail_roles']['administrator'] ) ? $user_counts['avail_roles']['administrator'] : 0; $valid = $total_users === $admin_count; /** * Filter the result of secupress_is_honeypotable(), force true to always allow Honeypot. * * @since 2.5 * @author Julio Potier * * @param (bool) $valid True if all users are administrators, false otherwise. */ return apply_filters( 'secupress.is_honeypotable', $valid ); } free/functions/formatting.php 0000644 00000067133 15174670627 0012411 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Display a small page, usually used to block a user until this user provides some info. * * @since 1.0 * * @param (string) $title The title tag content. * @param (string) $content The page content. * @param (array) $args Some more data: * - $head Content to display in the document's head. */ function secupress_action_page( $title, $content, $args = array() ) { global $wp_scripts, $wp_styles; if ( wp_doing_ajax() ) { return; } $suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min'; $version = $suffix ? SECUPRESS_VERSION : time(); $body = isset( $args['body'] ) ? $args['body'] : ''; $head = isset( $args['head'] ) ? $args['head'] : ''; $logo = isset( $args['logo'] ) ? $args['logo'] : ''; $functions = isset( $args['functions'] ) ? $args['functions'] : ''; $wpscripts = isset( $args['wpscripts'] ) ? $args['wpscripts'] : ''; $wpstyles = isset( $args['wpstyles'] ) ? $args['wpstyles'] : ''; // Functions management, do not output anything, Example: scripts and styles registration. ob_start(); if ( is_array( $functions ) ) { foreach ( $functions as $fct ) { if ( is_callable( $fct ) ) { call_user_func( $fct ); } } } ob_end_flush(); ?><!DOCTYPE html> <html <?php language_attributes(); ?>> <head> <meta charset="<?php echo esc_attr( strtolower( get_bloginfo( 'charset' ) ) ); ?>" /> <title><?php echo strip_tags( $title ); ?></title> <meta content="initial-scale=1.0" name="viewport" /> <link href="<?php echo SECUPRESS_ADMIN_CSS_URL . 'secupress-action-page' . $suffix . '.css?ver=' . $version; ?>" media="all" rel="stylesheet" /> <?php // Scripts management if ( ! empty( $wpscripts ) && ! is_array( $wpscripts ) ) { $wpscripts = (array) $wpscripts; } if ( $wpscripts ) { foreach( $wpscripts as $wpscript ) { if ( isset( $wp_scripts->registered[ $wpscript ]->extra['data'] ) ) { echo '<script type="text/javascript">' . $wp_scripts->registered[ $wpscript ]->extra['data'] . '</script>' . "\n"; // no esc_js, build by WP, is safe. } echo '<script type="text/javascript" src="' . esc_url( $wp_scripts->registered[ $wpscript ]->src ) . '?ver=' . $version . '"></script>' . "\n"; } } // Styles management if ( ! empty( $wpstyles ) && ! is_array( $wpstyles ) ) { $wpstyles = (array) $wpstyles; } if ( $wpstyles ) { foreach( $wpstyles as $wpstyle ) { echo '<link href="' . esc_url( $wp_styles->registered[ $wpstyle ]->src ) . '" rel="stylesheet" media="all" />' . "\n"; } } echo $head; ?> </head> <body <?php echo $body; ?>> <div class="secupress-action-page-content"> <?php echo $logo ? $logo : '<div class="wrap"><img src="' . get_site_icon_url( 90, secupress_get_logo( [], 'url' ) ) . '" alt="' . __( 'Site Icon', 'secupress' ) . '"/></div>'; ?> <?php echo $content; ?> </div> </body> </html><?php die(); } /** * Outputs a fake login page design. * * @since 2.3.19 * @author Julio Potier * * @param (string) $title Fake login page title to display in the `<title>` element. * @param (string) $content Content to display in body. * @param (WP_Error) $wp_error Optional. The errors to pass. * @param (int) $user_id Optional. Needed when you there is no user logged in */ function secupress_login_page( $title, $content, $wp_error = null, $user_id = 0 ) { global $sp_action; if ( ! isset( $sp_action ) || ! $sp_action ) { secupress_die( __( 'Something went wrong.', 'secupress' ), '', [ 'force_die' => true, 'context' => 'missing-sp_action', 'attack_type' => 'login' ] ); } $user_id = $user_id ? $user_id : get_current_user_id(); if ( $user_id ) { switch_to_locale( get_user_locale( $user_id ) ); // Add user color scheme to login page global $_wp_admin_css_colors; register_admin_color_schemes(); $color = get_user_option( 'admin_color', $user_id ); if ( empty( $color ) || ! isset( $_wp_admin_css_colors[ $color ] ) ) { $color = 'fresh'; } $color = $_wp_admin_css_colors[ $color ]; $url = $color->url; if ( $url ) { $ver = get_bloginfo( 'version' ); $hash = secupress_generate_hash( $ver ); $url = add_query_arg( 'ver', $hash, $url ); add_action( 'login_head', function() use( $url ) { echo "<link rel='stylesheet' id='colors-css' href='{$url}' media='all' />"; }); } } if ( ! is_wp_error( $wp_error ) ) { $wp_error = new WP_Error(); } ?><!DOCTYPE html> <html <?php language_attributes(); ?>> <head> <meta http-equiv="Content-Type" content="<?php bloginfo( 'html_type' ); ?>; charset=<?php bloginfo( 'charset' ); ?>" /> <title><?php echo $title; ?></title> <style> .secupress-notice.has-plugin-title {margin-bottom: 33px !important; position: relative; } .secupress-notice label.plugin-title {background: rgba(0, 0, 0, 0.3); color: #fff; padding: 2px 10px; position: absolute; top: 100%; border-top: 2px solid rgba(0, 0, 0, 0.1); border-radius: 0px 0px 2px 2px; } </style> <?php wp_enqueue_style( 'login' ); /** * Run actions after title tag * * @since 2.3.19 */ do_action( 'secupress_login_page.after_title_tag' ); /** * Enqueues scripts and styles for the login page. * * @since WP 3.1.0 */ do_action( 'login_enqueue_scripts' ); /** * Enqueues scripts and styles for the login page. * * @since 2.3.19 */ do_action( 'secupress_login_page.login_enqueue_scripts' ); /** * Fires in the login page header after scripts are enqueued. * * @since WP 2.1.0 */ do_action( 'login_head' ); /** * Fires in the login page header after scripts are enqueued. * * @since 2.3.19 */ do_action( 'secupress_login_page.login_head' ); ?><meta name="viewport" content="width=device-width, initial-scale=1.0" /><?php ?><meta name='referrer' content='strict-origin-when-cross-origin' /><?php $login_header_url = home_url(); $login_header_text = get_bloginfo( 'blogdescription' ); $classes = array( 'login-action-' . $sp_action, 'wp-core-ui' ); if ( is_rtl() ) { $classes[] = 'rtl'; } $classes[] = ' locale-' . sanitize_html_class( strtolower( str_replace( '_', '-', get_user_locale() ) ) ); /** * Filters the login page body classes. * * @since WP 3.5.0 * * @param string[] $classes An array of body classes. * @param string $action The action that brought the visitor to the login page. */ $classes = apply_filters( 'login_body_class', $classes, $sp_action ); /** * Filters the login page body classes. * * @since 2.3.19 * * @param string[] $classes An array of body classes. * @param string $action The action that brought the visitor to the login page. */ $classes = apply_filters( 'secupress_login_page.login_body_class', $classes, $sp_action ); ?> </head> <body class="login no-js <?php echo esc_attr( implode( ' ', $classes ) ); ?>"> <?php wp_print_inline_script_tag( "document.body.className = document.body.className.replace('no-js','js');" ); ?> <?php /** * Fires in the login page header after the body tag is opened. * * @since WP 4.6.0 */ do_action( 'login_header' ); /** * Fires in the login page header after the body tag is opened. * * @since 2.3.19 */ do_action( 'secupress_login_page.login_header' ); ?> <div id="login"> <h1 role="presentation" class="wp-login-logo"><a href="<?php echo esc_url( $login_header_url ); ?>"><?php echo $login_header_text; ?></a></h1> <?php if ( $wp_error->has_errors() ) { $error_list = array(); $messages = ''; foreach ( $wp_error->get_error_codes() as $code ) { $severity = $wp_error->get_error_data( $code ); foreach ( $wp_error->get_error_messages( $code ) as $error_message ) { if ( 'message' === $severity ) { $messages .= '<p>' . $error_message . '</p>'; } else { $error_list[] = $error_message; } } } if ( ! empty( $messages ) ) { /** * Filters instructional messages displayed above the login form. * * @since WP 2.5.0 * * @param string $messages Login messages. */ $messages = apply_filters( 'login_messages', $messages ); /** * Filters instructional messages displayed above the login form. * * @since 2.3.19 * * @param string $messages Login messages. */ $messages = apply_filters( 'secupress_login_page.login_messages', $messages ); $plugin_name = SECUPRESS_PLUGIN_NAME . ( secupress_has_pro() && ! secupress_is_white_label() ? ' Pro' : '' ); $label = ! secupress_show_contextual_help() ? '' : '<label class="plugin-title">' . esc_html( $plugin_name ) . '</label>'; $lab_class = ! secupress_show_contextual_help() ? '' : 'secupress-notice has-plugin-title'; wp_admin_notice( $messages . $label, array( 'type' => 'info', 'id' => 'login-message', 'additional_classes' => array( 'message', $lab_class ), 'paragraph_wrap' => false, ) ); } if ( ! empty( $error_list ) ) { $errors = ''; if ( count( $error_list ) > 1 ) { $errors .= '<ul class="login-error-list">'; foreach ( $error_list as $item ) { $errors .= '<li>' . $item . '</li>'; } $errors .= '</ul>'; } else { $errors .= '<p>' . $error_list[0] . '</p>'; } /** * Filters the error messages displayed above the login form. * * @since 2.1.0 * * @param string $errors Login error messages. */ $errors = apply_filters( 'login_errors', $errors ); $errors = apply_filters( 'secupress_login_page.login_errors', $errors ); wp_admin_notice( $errors, array( 'type' => 'error', 'id' => 'login_error', 'paragraph_wrap' => false, ) ); } } nocache_headers(); header( 'Content-Type: ' . get_bloginfo( 'html_type' ) . '; charset=' . get_bloginfo( 'charset' ) ); // Set a cookie now to see if they are supported by the browser. $secure = ( 'https' === parse_url( wp_login_url(), PHP_URL_SCHEME ) ); setcookie( TEST_COOKIE, 'WP Cookie check', 0, COOKIEPATH, COOKIE_DOMAIN, $secure, true ); if ( SITECOOKIEPATH !== COOKIEPATH ) { setcookie( TEST_COOKIE, 'WP Cookie check', 0, SITECOOKIEPATH, COOKIE_DOMAIN, $secure, true ); } if ( isset( $_REQUEST['wp_lang'] ) ) { setcookie( 'wp_lang', sanitize_text_field( $_REQUEST['wp_lang'] ), 0, COOKIEPATH, COOKIE_DOMAIN, $secure, true ); } /** * Fires when the login form is initialized. * * @since WP 3.2.0 */ do_action( 'login_init' ); /** * Fires when the login form is initialized. * * @since 2.3.19 */ do_action( 'secupress_login_page.login_init' ); /** * Fires before a specified login form action. * * The dynamic portion of the hook name, `$action`, refers to the action * that brought the visitor to the login form. * * @since WP 2.8.0 */ do_action( "login_form_{$sp_action}" ); /** * Fires before a specified login form action. * * The dynamic portion of the hook name, `$action`, refers to the action * that brought the visitor to the login form. * * @since 2.3.19 */ do_action( "secupress_login_page.login_form_{$sp_action}" ); /** * Filters the content of the form * * @since 2.3.19 * * @param string $content */ $content = apply_filters( 'secupress_login_page.content', $content, $sp_action ); echo $content . "\n"; wp_enqueue_script( 'user-profile' ); ?> <p id="backtoblog"> <?php if ( 'honeypot' !== $sp_action ) { $redirect_to = isset( $_REQUEST['redirect_to'] ) ? $_REQUEST['redirect_to'] : ''; $html_link = sprintf( '<a href="%s">%s</a>', esc_url( wp_login_url( $redirect_to, true ) ), sprintf( __( '← Back to %s', 'secupress' ), strtolower( __( 'Login Page', 'secupress' ) ) ) ); /** * Filters the "Go to site" link displayed in the login page footer. * * @since WP 5.7.0 * * @param string $link HTML link to the home URL of the current site. */ $html_link = apply_filters( 'login_site_html_link', $html_link ); /** * Filters the "Go to site" link displayed in the login page footer. * * @since 2.3.19 * * @param string $link HTML link to the home URL of the current site. */ $html_link = apply_filters( 'secupress_login_page.login_link', $html_link ); } else { $html_link = sprintf( '<a href="%s">%s</a>', esc_url( home_url() ), sprintf( __( '← Back to %s', 'secupress' ), strtolower( get_bloginfo( 'name' ) ) ) ); } echo $html_link; ?> </p> </div> <script> try { const firstInput = document.querySelector('#loginform input:not([type="hidden"])'); if (typeof firstInput !== 'undefined') { firstInput.focus(); } } catch(e) {} if (typeof wpOnload === 'function') wpOnload(); </script> <?php /** * Fires in the login page footer. * * @since WP 3.1.0 */ do_action( 'login_footer' ); /** * Fires in the login page footer. * * @since 2.3.19 */ do_action( 'secupress_login_page.login_footer' ); /** * Shake the form when true * * @since 2.3.19 */ if ( apply_filters( 'secupress_login_page.shake_js', false ) ) { wp_print_inline_script_tag( "document.querySelector('form').classList.add('shake');" ); } ?> </body> </html> <?php die(); } /** * First half of escaping for LIKE special characters % and _ before preparing for MySQL. * * Use this only before wpdb::prepare() or esc_sql(). Reversing the order is very bad for security. * * Example Prepared Statement: * $wild = '%'; * $find = 'only 43% of planets'; * $like = $wild . $wpdb->esc_like( $find ) . $wild; * $sql = $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE post_content LIKE %s", $like ); * * Example Escape Chain: * $sql = esc_sql( $wpdb->esc_like( $input ) ); * * @since 1.0 * @since WP 4.0.0 * * @param (string) $text The raw text to be escaped. The input typed by the user should have no extra or deleted slashes. * * @return (string) Text in the form of a LIKE phrase. The output is not SQL safe. Call $wpdb::prepare() or real_escape next. */ function secupress_esc_like( $text ) { global $wpdb; if ( method_exists( $wpdb, 'esc_like' ) ) { return $wpdb->esc_like( $text ); } return addcslashes( $text, '_%\\' ); } /** * Return the "unaliased" version of an email address. * * @since 1.0 * * @param (string) $email An email address. * * @return (string) */ function secupress_remove_email_alias( $email ) { $provider = strstr( $email, '@' ); $email = strstr( $email, '@', true ); $email = explode( '+', $email ); $email = reset( $email ); $email = str_replace( '.', '', $email ); return $email . $provider; } /** * Return the email "example@example.com" like "e%x%a%m%p%l%e%@example.com" * * @since 1.0 * * @param (string) $email An email address. * * @return (string) */ function secupress_prepare_email_for_like_search( $email ) { $email = secupress_remove_email_alias( $email ); $provider = strstr( $email, '@' ); $email = secupress_esc_like( strstr( $email, '@', true ) ); $email = str_split( $email ); $email = implode( '%', $email ); return $email . '%' . $provider; } /** * Generate a folder name using a hash in it. * * @since 1.0 * * @param (string) $context Your context, don't use empty string. * @param (string) $path The root base for this folder, optional. * * @return (string) */ function secupress_get_hashed_folder_name( $context = 'folder_name', $path = '/' ) { return $path . 'secupress-' . secupress_generate_hash( $context, 8, 8 ) . '/'; } /** * Generate a hash depending on a context and 'hash_key'. * * @since 1.0 * * @param (string) $context Your context, don't use empty string. * @param (int) $start Start of the `substr()`. * @param (int) $length Length of the hash. * * @return (string) */ function secupress_generate_hash( $context, $start = 2, $length = 6 ) { $hk = secupress_get_option( 'hash_key' ); return substr( md5( $hk . $context ), $start, $length ); } /** * Generate a key depending on an object and 'master_key'. * * @since 2.3.21 * @author Julio Potier * * @param (string|int|float) $object * @param (int) $length * * @return (string) **/ function secupress_generate_key_for_object( $object, $length = 6 ) { $mk = secupress_get_option( 'master_key' ); return substr( md5( $mk . $object ), 2, $length ); } /** * Generate a random key. * * @since 2.2.6 Usage of \Random\Randomizer() + $chars param * @since 1.0 * * @param (int) $length Length of the key. * @param (string) $chars A set of characters. * * @return (string) */ function secupress_generate_key( $length = 16, $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890' ) { if ( ! trim( $chars ) ) { wp_trigger_error( __FUNCTION__, 'Invalid $chars parameter', E_USER_ERROR ); } if ( method_exists( '\Random\Randomizer', 'getBytesFromString' ) ) { // PHP >=8.3 $rnd = new \Random\Randomizer(); $key = $rnd->getBytesFromString( $chars, $length ); } else { $key = ''; for ( $i = 0; $i < $length; $i++ ) { $key .= $chars[ wp_rand( 0, mb_strlen( $chars ) - 1 ) ]; } } return $key; } /** * Validate a range. * * @since 1.0 * * @param (int) $value The value to test. * @param (int) $min Minimum value. * @param (int) $max Maximum value. * @param (mixed) $default What to return if outside of the range. Default: false. * * @return (mixed) The value on success. `$default` on failure. */ function secupress_validate_range( $value, $min, $max, $default = false ) { $test = filter_var( $value, FILTER_VALIDATE_INT, array( 'options' => array( 'min_range' => $min, 'max_range' => $max ) ) ); if ( false === $test ) { return $default; } return $value; } /** * Limit a number to a high and low value. * A bit like `secupress_validate_range()` but: * - cast the value as integer. * - return the min/max value instead of false/default. * * @since 1.0 * * @param (numeric) $value The value to limit. * @param (int) $min The minimum value. * @param (int) $max The maximum value. * * @return (int) */ function secupress_minmax_range( $value, $min, $max ) { $value = (int) $value; $value = max( $min, $value ); $value = min( $value, $max ); return $value; } /** * Sanitize a `$separator` separated list by removing doubled-separators. * * @since 1.0 * * @param (string) $list The list. * @param (string) $separator The separator. * * @return (string) The list. */ function secupress_sanitize_list( $list, $separator = ', ' ) { if ( empty( $list ) ) { return ''; } $trimed_sep = trim( $separator ); $double_sep = $trimed_sep . $trimed_sep; $list = preg_replace( '/\s*' . $trimed_sep . '\s*/', $trimed_sep, $list ); $list = trim( $list, $trimed_sep . ' ' ); while ( false !== strpos( $list, $double_sep ) ) { $list = str_replace( $double_sep, $trimed_sep, $list ); } return str_replace( $trimed_sep, $separator, $list ); } /** * Apply `array_flip array_flip` and `natcasesort()` on a list. * * @since 1.0 * @since 2.2.1 @param $return * * @param (string|array) $list The list. * @param (string|bool) $separator The separator. If not false, the function will explode and implode the list. * @param (string) $return 'default' to let array or string. 'array' to force array. * * @return (string|array) The list. */ function secupress_unique_sorted_list( $list, $separator = false, $return = 'default' ) { if ( array() === $list || '' === $list ) { return $list; } if ( false !== $separator ) { $list = explode( $separator, $list ); } $list = array_flip( array_flip( $list ) ); natcasesort( $list ); $list = array_map( 'trim', $list ); if ( 'array' === $return ) { return $list; } if ( false !== $separator ) { $list = implode( $separator, $list ); } return $list; } /** * Format a timestamp into something really human. * * @since 2.1 * @author Julio Potier * * @see https://21douze.fr/human_readable_duration-ou-pas-147097.html * * @param (string|int) $entry Can be a timestamp or a string like 24:12:33 * @return **/ function secupress_readable_duration( $entry ) { if ( ! is_numeric( $entry ) || INF === $entry ) { $coeff = [ 1, MINUTE_IN_SECONDS, HOUR_IN_SECONDS, DAY_IN_SECONDS, MONTH_IN_SECONDS, YEAR_IN_SECONDS ]; $data = array_reverse( array_map( 'intval', explode( ':', $entry ) ) ); $entry = 0; foreach ( $data as $index => $time ) { $entry += $time * $coeff[ $index ]; } if ( ! $entry ) { trigger_error( 'Entry data must be numeric or respect format dd:hh:mm:ss' ); return; } } $from = new \DateTime( '@0' ); $to = new \DateTime( "@$entry" ); $data = explode( ':', $from->diff( $to )->format('%s:%i:%h:%d:%m:%y') ); $return = []; $labels = [ _n_noop( '%s second', '%s seconds' ), _n_noop( '%s minute', '%s minutes' ), _n_noop( '%s hour', '%s hours' ), _n_noop( '%s day', '%s days' ), _n_noop( '%s month', '%s months' ), _n_noop( '%s year', '%s years' ), ]; foreach( $data as $i => $time ) { if ( '0' === $time && ! empty( array_filter( $return, 'intval' ) ) ) { continue; } $return[] = sprintf( translate_nooped_plural( $labels[ $i ], $time ), $time ); } $return = array_reverse( $return ); $text = wp_sprintf( '%l', $return ); return $text; } /** * Tag a string * * @since 2.2.6 $attrs * @since 2.0.3 * @author Julio Potier * * @param (string) $str The text * @param (string) $tag The HTML tag * @param (string) $attrs Any other attr, not filtered * @return (string) **/ function secupress_tag_me( $str, $tag, $attrs = '' ) { return sprintf( '<%1$s%3$s>%2$s</%1$s>', $tag, $str, $attrs ); } /** * Tag a string with a where href is the same text by default * * @since 2.2.6 * @author Julio Potier * * @param (string) $str The text * @param (string) $href The href attr, empty = $str * @param (string) $rels True = rel="noopener noreferer" ; False = '' * @return (string) **/ function secupress_a_me( $str, $href = '', $attrs = '' ) { if ( empty( $href ) ) { if ( is_email( $str ) ) { $href = 'mailto:' . $str; } else { $href = $str; } } $attrs = " href=\"{$href}\" $attrs"; return secupress_tag_me( $str, 'a', $attrs ); } /** * Tag a string with <code> * * @since 2.2.6 $attrs * @since 2.0.3 * @author Julio Potier * * @param (string) $str The text * @return (string) **/ function secupress_code_me( $str, $attrs = '' ) { return secupress_tag_me( $str, 'code', $attrs ); } /** * Used in localize * * @since 2.1 * @author Julio Potier * @return (array) **/ function secupress_get_http_logs_limits( $mode = 'text' ) { if ( 'text' === $mode ) { return [ '', // index 0 in JS __( 'No Limits (default)', 'secupress' ), __( '1440 per day / 1 per min', 'secupress' ), __( '288 per day / 1 per 5 min', 'secupress' ), __( '96 per day / 1 per 15 min', 'secupress' ), __( '48 per day / 1 per 30 min', 'secupress' ), __( '24 per day / 1 per hour', 'secupress' ), __( '12 per day / 1 per 2 hours', 'secupress' ), __( '8 per day / 1 per 3 hours', 'secupress' ), __( '6 per day / 1 per 4 hours', 'secupress' ), __( '4 per day / 1 per 6 hours', 'secupress' ), __( '2 per day / 1 per 12 hours', 'secupress' ), __( '1 per day / 1 per 24 hours', 'secupress' ), __( '0 Calls (blocked)', 'secupress' ), ]; } return [ -1, MINUTE_IN_SECONDS, MINUTE_IN_SECONDS * 5, HOUR_IN_SECONDS / 4, HOUR_IN_SECONDS / 2, HOUR_IN_SECONDS, HOUR_IN_SECONDS * 2, HOUR_IN_SECONDS * 3, HOUR_IN_SECONDS * 4, HOUR_IN_SECONDS * 6, HOUR_IN_SECONDS * 12, DAY_IN_SECONDS, 0, ]; } /** * Returns the correct 404 handler rule for the server * * @since 2.2.6 * @author Julio Potier * * @return (string) $rule */ function secupress_get_404_rule_for_rewrites() { global $is_apache, $is_nginx, $is_iis7; $rule = ''; $path = str_replace( ABSPATH, '', SECUPRESS_INC_PATH ); $path .= 'data/404-handler.php'; $path = apply_filters( 'secupress.rewrites.404-handler.file', $path ); if ( file_exists( realpath( ABSPATH . $path ) ) ) { if ( $is_apache ) { $rule = "RewriteRule ^ {$path}?secupress_bad_url_access__ID=%{ENV:REDIRECT_PHP404}&secupress_bad_url_access__URL=%{REQUEST_URI} [L,QSA]\n"; } elseif ( $is_nginx ) { $rule = "rewrite ^ /{$path}?secupress_bad_url_access__ID=$"."REDIRECT_PHP404&secupress_bad_url_access__URL=$"."request_uri last;\n"; } elseif ( $is_iis7 ) { $rule = "<action type=\"Rewrite\" url=\"" . $path . "data/404-handler.php\" />\n"; } } else { if ( $is_apache ) { $rule = "RewriteRule ^ - [R=404,L]\n"; } elseif ( $is_nginx ) { $rule = "return 404;\n"; } elseif ( $is_iis7 ) { $rule = "<action type=\"CustomResponse\" statusCode=\"404\"/>\n"; } } return $rule; } /** * Migrate attacks data from old format to new format * Old format: simple numeric values (e.g., "ban_ip" => 76) * New format: arrays with dates (e.g., "ban_ip" => ["0725" => 26, "0825" => 27, ...]) * Also ensures "all" index exists with cumulative total * * @author Julio Potier * @since 2.4.1 * * @param (array) $attack_types The attacks data to migrate * @return (array) Migrated attacks data **/ function secupress_migrate_attacks_data( $attack_types ) { if ( ! is_array( $attack_types ) || empty( $attack_types ) ) { return $attack_types; } $needs_migration = false; $all_total = 0; $current_date = date( 'md' ); // Format MMDD foreach ( $attack_types as $type => $data ) { if ( 'all' === $type ) { continue; } // If it's a simple number (old format), we need migration if ( is_numeric( $data ) && ! is_array( $data ) ) { $needs_migration = true; $all_total += (int) $data; } elseif ( is_array( $data ) ) { foreach ( $data as $value ) { if ( is_numeric( $value ) ) { $all_total += (int) $value; } } } } // If no migration needed but "all" is missing, we still need to add it if ( ! $needs_migration && ! isset( $attack_types['all'] ) ) { // Calculate "all" from existing new format data $all_total = 0; foreach ( $attack_types as $type => $data ) { if ( 'all' !== $type && is_array( $data ) ) { foreach ( $data as $value ) { if ( is_numeric( $value ) ) { $all_total += (int) $value; } } } } $attack_types['all'] = $all_total; update_option( SECUPRESS_ATTACKS, $attack_types ); return $attack_types; } // If migration is needed if ( $needs_migration ) { $migrated = []; foreach ( $attack_types as $type => $data ) { if ( 'all' === $type ) { continue; } if ( is_numeric( $data ) && ! is_array( $data ) ) { $value = (int) $data; $migrated[ $type ] = []; $months = []; $date = new DateTime(); for ( $i = 0; $i < 6; $i++ ) { $month_date = clone $date; $month_date->modify( '-' . $i . ' months' ); $month_date->setDate( (int) $month_date->format( 'Y' ), (int) $month_date->format( 'm' ), 25 ); $months[] = $month_date->format( 'md' ); } $months = array_reverse( $months ); // Oldest first $per_month = floor( $value / 6 ); $remainder = $value % 6; $current_month = date( 'm' ); // Current month (MM format) foreach ( $months as $month_key ) { $month_key_month = substr( $month_key, 0, 2 ); // Extract month (MM) from MMDD if ( $month_key_month === $current_month ) { $migrated[ $type ][ $month_key ] = $per_month + $remainder; } else { $migrated[ $type ][ $month_key ] = $per_month; } } } else { $migrated[ $type ] = $data; } } $migrated['all'] = $all_total; update_option( SECUPRESS_ATTACKS, $migrated ); return $migrated; } return $attack_types; } /** * Return the Flag emoji for a given country code like "FR" or "UK" * * @since 2.6 * @author Julio Potier * * @param (string) * * @see JS Version here https://dev.to/jorik/country-code-to-flag-emoji-a21 * * @return (string) **/ function secupress_get_flag( $country_code ) { $country_code = strtoupper( $country_code ); return mb_chr( ord( $country_code[0] ) + 127397, 'UTF-8') . mb_chr( ord( $country_code[1] ) + 127397, 'UTF-8' ); } free/functions/options.php 0000644 00000051053 15174670627 0011724 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** --------------------------------------------------------------------------------------------- */ /** MAIN OPTION ================================================================================= */ /** --------------------------------------------------------------------------------------------- */ /** * A wrapper to easily get a SecuPress option. * * @since 1.0 * * @param (string) $option The option name. * @param (mixed) $default The default value of option. Default: false. * * @return (mixed) The option value. */ function secupress_get_option( $option, $default = false ) { $pre = null; /** * Pre-filter any SecuPress option before read. * * @since 1.0 * * @param (mixed) $pre Null. * @param (mixed) $default The default value. */ $value = apply_filters( 'pre_secupress_get_option_' . $option, $pre, $default ); if ( null !== $value ) { return $value; } $options = get_site_option( SECUPRESS_SETTINGS_SLUG ); $value = is_array( $options ) && isset( $options[ $option ] ) && false !== $options[ $option ] ? $options[ $option ] : $default; /** * Filter any SecuPress option after read * * @since 1.0 * * @param (mixed) $value The option value. * @param (mixed) $default The default value. */ return apply_filters( 'secupress_get_option_' . $option, $value, $default ); } /** * A wrapper to easily set a SecuPress option. * * @since 1.4.2 * * @param (string) $option The option name. * @param (string) $value The value. */ function secupress_set_option( $option, $value ) { /** * Pre-filter any SecuPress option before set. * * @since 1.0 * * @param (mixed) $value Null. */ $value = apply_filters( 'pre_secupress_set_option_' . $option, $value ); $options = get_site_option( SECUPRESS_SETTINGS_SLUG ); $options = is_array( $options ) ? $options : []; $options[ $option ] = $value; secupress_update_options( $options ); } /** * A wrapper to update SecuPress options. * If you want the options to be sanitized, make sure to `unset( $options['sanitized'] )` before. * * @since 1.1.4 * @author Grégory Viguier * * @param (array) $options The new option values. */ function secupress_update_options( $options ) { // Make sure we don't mess everything. if ( ! is_array( $options ) ) { return; } update_site_option( SECUPRESS_SETTINGS_SLUG, $options ); } /** --------------------------------------------------------------------------------------------- */ /** MODULE OPTIONS ============================================================================== */ /** --------------------------------------------------------------------------------------------- */ /** * A wrapper to easily get a SecuPress module option. * * @since 1.0 * * @param (string) $option The option name. * @param (mixed) $default The default value of option. Default: null. * @param (string) $module The module slug (see array keys from `modules.php`). Default is the current module. * * @return (mixed) The option value. */ function secupress_get_module_option( $option, $default = null, $module = false ) { if ( ! $module ) { $module = secupress_get_current_module(); } $pre = null; /** * Pre-filter any SecuPress option before read. * * @since 1.0 * * @param (mixed) $pre Null. * @param (mixed) $default The default value. * @param (string) $module The module. */ $value = apply_filters( 'pre_secupress_get_module_option_' . $option, $pre, $default, $module ); if ( null !== $value ) { return $value; } $options = get_site_option( "secupress_{$module}_settings" ); $value = is_array( $options ) && isset( $options[ $option ] ) && false !== $options[ $option ] ? $options[ $option ] : $default; /** * Filter any SecuPress option after read. * * @since 1.0 * * @param (mixed) $value The option value. * @param (mixed) $default The default value. * @param (string) $module The module. */ return apply_filters( 'secupress_get_module_option_' . $option, $value, $default, $module ); } /** --------------------------------------------------------------------------------------------- */ /** SCAN / FIX ================================================================================== */ /** --------------------------------------------------------------------------------------------- */ /** * Get all scan results. * * @since 1.0 * @since 1.3 Use multiple options instead of 1 option and multiple transients. * @author Grégory Viguier * * @return (array) */ function secupress_get_scan_results() { return SecuPress_Scanner_Results::get_scan_results(); } /** * Get all fix results. * * @since 1.0 * @since 1.3 Use multiple options instead of 1 option and multiple transients. * @author Grégory Viguier * * @return (array) */ function secupress_get_fix_results() { return SecuPress_Scanner_Results::get_fix_results(); } /** --------------------------------------------------------------------------------------------- */ /** TRANSIENTS ================================================================================== */ /** --------------------------------------------------------------------------------------------- */ /** * Delete a site transient. * * This is almost the same function than `delete_site_transient()`, but without the timeout check: it saves database calls. * * @since 1.0 * @since WP 2.9.0 * * @param (string) $transient Transient name. Expected to not be SQL-escaped. * * @return (bool) true if successful, false otherwise. */ function secupress_delete_site_transient( $transient ) { /** * Fires immediately before a specific site transient is deleted. * * The dynamic portion of the hook name, `$transient`, refers to the transient name. * * @since 1.0 * @since WP 3.0.0 * * @param (string) $transient Transient name. */ do_action( 'delete_site_transient_' . $transient, $transient ); if ( wp_using_ext_object_cache() ) { $result = wp_cache_delete( $transient, 'site-transient' ); } else { $option = '_site_transient_' . $transient; $result = delete_site_option( $option ); } if ( $result ) { /** * Fires after a site transient is deleted. * * @since 1.0 * @since WP 3.0.0 * * @param (string) $transient Deleted transient name. */ do_action( 'deleted_site_transient', $transient ); } return $result; } /** * Get the value of a site transient. * * This is almost the same function than `get_site_transient()`, but without the timeout check: it saves database calls. * If the transient does not exist or does not have a value, then the return value will be false. * * @since 1.0 * @since WP 2.9.0 * * @param (string) $transient Transient name. Expected to not be SQL-escaped. * * @return (mixed) Value of transient. */ function secupress_get_site_transient( $transient ) { /** * Filter the value of an existing site transient. * * The dynamic portion of the hook name, `$transient`, refers to the transient name. * * Passing a truthy value to the filter will effectively short-circuit retrieval * of the transient, returning the passed value instead. * * @since 1.0 * @since WP 2.9.0 * @since WP 4.4.0 The `$transient` parameter was added. * * @param (mixed) $pre_transient The default value to return if the site transient does not exist. * Any value other than false will short-circuit the retrieval * of the transient, and return the returned value. * @param (string) $transient Transient name. */ $pre = apply_filters( 'pre_site_transient_' . $transient, false, $transient ); if ( false !== $pre ) { return $pre; } if ( wp_using_ext_object_cache() ) { $value = wp_cache_get( $transient, 'site-transient' ); } else { $option = '_site_transient_' . $transient; $value = get_site_option( $option ); } /** * Filter the value of an existing site transient. * * The dynamic portion of the hook name, `$transient`, refers to the transient name. * * @since 1.0 * @since WP 2.9.0 * @since WP 4.4.0 The `$transient` parameter was added. * * @param (mixed) $value Value of transient. * @param (string) $transient Transient name. */ return apply_filters( 'site_transient_' . $transient, $value, $transient ); } /** * Set/update the value of a site transient. * * This is almost the same function than `set_site_transient()`, but without the timeout check. * You do not need to serialize values. If the value needs to be serialized, then it will be serialized before it is set. * * @since 1.0 * @since WP 2.9.0 * * @param (string) $transient Transient name. Expected to not be SQL-escaped. Must be * 40 characters or fewer in length. * @param (mixed) $value Transient value. Must be serializable if non-scalar. * Expected to not be SQL-escaped. * * @return (bool) False if value was not set and true if value was set. */ function secupress_set_site_transient( $transient, $value ) { /** * Filter a specific site transient before its value is set. * * The dynamic portion of the hook name, `$transient`, refers to the transient name. * * @since 1.0 * @since WP 3.0.0 * @since WP 4.4.0 The `$transient` parameter was added. * * @param (mixed) $value New value of site transient. * @param (string) $transient Transient name. */ $value = apply_filters( 'pre_set_site_transient_' . $transient, $value, $transient ); if ( wp_using_ext_object_cache() ) { $result = wp_cache_set( $transient, $value, 'site-transient' ); } else { $option = '_site_transient_' . $transient; if ( false === get_site_option( $option ) ) { $result = add_site_option( $option, $value ); } else { $result = update_site_option( $option, $value ); } } if ( $result ) { /** * Fires after the value for a specific site transient has been set. * * The dynamic portion of the hook name, `$transient`, refers to the transient name. * * @since 1.0 * @since WP 3.0.0 * @since WP 4.4.0 The `$transient` parameter was added. * * @param (mixed) $value Transient value. * @param (int) $expiration Time until expiration in seconds, forced to 0. * @param (string) $transient The name of the transient. */ do_action( 'set_site_transient_' . $transient, $value, 0, $transient ); } return $result; } /** * Delete a transient. * * This is almost the same function than `delete_transient()`, but without the timeout check: it saves database calls. * * @since 1.0 * @since WP 2.8.0 * * @param (string) $transient Transient name. Expected to not be SQL-escaped. * * @return (bool) true if successful, false otherwise. */ function secupress_delete_transient( $transient ) { /** * Fires immediately before a specific transient is deleted. * * The dynamic portion of the hook name, `$transient`, refers to the transient name. * * @since 1.0 * @since WP 3.0.0 * * @param (string) $transient Transient name. */ do_action( 'delete_transient_' . $transient, $transient ); if ( wp_using_ext_object_cache() ) { $result = wp_cache_delete( $transient, 'transient' ); } else { $option = '_transient_' . $transient; $result = delete_option( $option ); } if ( $result ) { /** * Fires after a transient is deleted. * * @since 1.0 * @since WP 3.0.0 * * @param (string) $transient Deleted transient name. */ do_action( 'deleted_transient', $transient ); } return $result; } /** * Get the value of a transient. * * This is almost the same function than `get_transient()`, but without the timeout check: it saves database calls. * If the transient does not exist or does not have a value, then the return value will be false. * * @since 2.2.6 $default parameter * @since 1.0 * @since WP 2.8.0 * * @param (string) $transient Transient name. Expected to not be SQL-escaped. * @param (mixed) $default False by default * * @return (mixed) Value of transient. */ function secupress_get_transient( $transient, $default = false ) { /** * Filter the value of an existing transient. * * The dynamic portion of the hook name, `$transient`, refers to the transient name. * * Passing a truthy value to the filter will effectively short-circuit retrieval * of the transient, returning the passed value instead. * * @since 1.0 * @since WP 2.8.0 * @since WP 4.4.0 The `$transient` parameter was added * * @param (mixed) $pre_transient The default value to return if the transient does not exist. * Any value other than false will short-circuit the retrieval * of the transient, and return the returned value. * @param (string) $transient Transient name. */ $pre = apply_filters( 'pre_transient_' . $transient, false, $transient ); if ( false !== $pre ) { return $pre; } if ( wp_using_ext_object_cache() ) { $value = wp_cache_get( $transient, 'transient' ); } else { $option = '_transient_' . $transient; $value = get_option( $option ); } if ( false === $value && false !== $default ) { $value = $default; } /** * Filter an existing transient's value. * * The dynamic portion of the hook name, `$transient`, refers to the transient name. * * @since 1.0 * @since WP 2.8.0 * @since WP 4.4.0 The `$transient` parameter was added * * @param (mixed) $value Value of transient. * @param (string) $transient Transient name. */ return apply_filters( 'transient_' . $transient, $value, $transient ); } /** * Set/update the value of a transient. * * This is almost the same function than `set_site_transient()`, but without the timeout check. * You do not need to serialize values. If the value needs to be serialized, then it will be serialized before it is set. * * @since 1.0 * @since WP 2.8.0 * * @param (string) $transient Transient name. Expected to not be SQL-escaped. Must be * 172 characters or fewer in length. * @param (mixed) $value Transient value. Must be serializable if non-scalar. * Expected to not be SQL-escaped. * * @return bool False if value was not set and true if value was set. */ function secupress_set_transient( $transient, $value ) { /** * Filter a specific transient before its value is set. * * The dynamic portion of the hook name, `$transient`, refers to the transient name. * * @since 1.0 * @since WP 3.0.0 * @since WP 4.2.0 The `$expiration` parameter was added. * @since WP 4.4.0 The `$transient` parameter was added. * * @param (mixed) $value New value of transient. * @param (int) $expiration Time until expiration in seconds, forced to 0. * @param (string) $transient Transient name. */ $value = apply_filters( 'pre_set_transient_' . $transient, $value, 0, $transient ); if ( wp_using_ext_object_cache() ) { $result = wp_cache_set( $transient, $value, 'transient', 0 ); } else { $option = '_transient_' . $transient; if ( false === get_option( $option ) ) { $result = add_option( $option, $value ); } else { $result = update_option( $option, $value ); } } if ( $result ) { /** * Fires after the value for a specific transient has been set. * * The dynamic portion of the hook name, `$transient`, refers to the transient name. * * @since 1.0 * @since WP 3.0.0 * @since WP 3.6.0 The `$value` and `$expiration` parameters were added. * @since WP 4.4.0 The `$transient` parameter was added. * * @param (mixed) $value Transient value. * @param (int) $expiration Time until expiration in seconds, forced to 0. * @param (string) $transient The name of the transient. */ do_action( 'set_transient_' . $transient, $value, 0, $transient ); /** * Fires after the value for a transient has been set. * * @since 1.0 * @since WP 3.0.0 * @since WP 3.6.0 The `$value` and `$expiration` parameters were added. * * @param (string) $transient The name of the transient. * @param (mixed) $value Transient value. * @param (int) $expiration Time until expiration in seconds, forced to 0. */ do_action( 'setted_transient', $transient, $value, 0 ); } return $result; } /** * Get the current module. * * @since 1.0 * * @return (string). */ function secupress_get_current_module() { if ( ! class_exists( 'SecuPress_Settings' ) ) { secupress_require_class( 'settings' ); } if ( ! class_exists( 'SecuPress_Settings_Modules' ) ) { secupress_require_class( 'settings', 'modules' ); } return SecuPress_Settings_Modules::get_instance()->get_current_module(); } /** --------------------------------------------------------------------------------------------- */ /** MODULE OPTIONS ============================================================================== */ /** --------------------------------------------------------------------------------------------- */ /** * Delete a SecuPress module option. * * @since 1.4.4 * * @param (string) $module The module slug (see array keys from `modules.php`). */ function secupress_delete_module_option( $module ) { global $wpdb; delete_site_option( "secupress_{$module}_settings" ); delete_site_transient( 'secupress_active_submodules' ); $wpdb->query( $wpdb->prepare( 'DELETE FROM ' . $wpdb->options . ' WHERE option_name LIKE "secupress_active_submodule_%" AND option_value = %s', $module ) ); } /** * Update a SecuPress module option. * * @since 1.0 * * @param (string) $option The option name. * @param (mixed) $value The new value. * @param (string) $module The module slug (see array keys from `modules.php`). Default is the current module. */ function secupress_update_module_option( $option, $value, $module = false ) { if ( ! $module ) { $module = secupress_get_current_module(); } $options = get_site_option( "secupress_{$module}_settings" ); $options = is_array( $options ) ? $options : array(); $options[ $option ] = $value; update_site_option( "secupress_{$module}_settings", $options ); } /** * Update a SecuPress module options. * * @since 1.0 * * @param (array) $values The new values. Keys not provided are not removed, previous values are kept. * @param (string) $module The module slug (see array keys from `modules.php`). Default is the current module. */ function secupress_update_module_options( $values, $module = false ) { if ( ! $values || ! is_array( $values ) ) { return null; } if ( ! $module ) { $module = secupress_get_current_module(); } $options = get_site_option( "secupress_{$module}_settings" ); $options = is_array( $options ) ? $options : array(); $options = array_merge( $options, $values ); update_site_option( "secupress_{$module}_settings", $options ); } add_filter( 'pre_secupress_get_module_option_' . 'advanced-settings_admin-bar', 'secupress_shortcut_module_option_get_user_admin_bar_setting' ); /** * Make the old site setting "advanced-settings_admin-bar" a user setting now * * @since 2.3.19 secupress- prefix * @since 2.3.18.1 * @author Julio Potier * @param (string) $pre '1' shows the admin bar in WP menu * @return (string) **/ function secupress_shortcut_module_option_get_user_admin_bar_setting( $pre ) { $user_id = get_current_user_id(); $user_setting = get_user_option( 'secupress-advanced-settings_admin-bar', $user_id ); if ( false !== $user_setting ) { $pre = $user_setting; } else { remove_filter( 'pre_secupress_get_module_option_' . 'advanced-settings_admin-bar', 'secupress_shortcut_module_option_get_user_admin_bar_setting' ); $pre = secupress_get_module_option( 'advanced-settings_admin-bar', '1' ); update_user_option( $user_id, 'secupress-advanced-settings_admin-bar', $pre, true ); } return $pre; } add_filter( 'pre_secupress_get_module_option_' . 'advanced-settings_expert-mode', 'secupress_shortcut_module_option_get_user_expert_mode_setting' ); /** * Make the old site setting "advanced-settings_expert-mode" (hide contextual help) a user setting now * * @since 2.3.19 secupress- prefix * @since 2.3.18.1 * @author Julio Potier * @param (string) $pre '1' hides the help paragraphs * @return (string) **/ function secupress_shortcut_module_option_get_user_expert_mode_setting( $pre ) { $user_id = get_current_user_id(); $user_setting = get_user_option( 'secupress-advanced-settings_expert-mode', $user_id ); if ( false !== $user_setting ) { $pre = $user_setting; } else { remove_filter( 'pre_secupress_get_module_option_' . 'advanced-settings_expert-mode', 'secupress_shortcut_module_option_get_user_expert_mode_setting' ); $pre = secupress_get_module_option( 'advanced-settings_expert-mode', '1' ); update_user_option( $user_id, 'secupress-advanced-settings_expert-mode', $pre, true ); } return $pre; } free/functions/db.php 0000644 00000027012 15174670627 0010614 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Return the filepath where the $table_prefix global is set. * Don’t get me wrong, you have to give him the correct file, it won't search for you. * * @since 2.0 * @author Julio Potier * * @global $wpdb * @return (string|bool) Filepath or false **/ function secupress_where_is_table_prefix() { global $wpdb; $config_filepath = secupress_is_wpconfig_writable( 'db' ); if ( $config_filepath ) { $regex_pattern = '/(\$table_prefix\s*=\s*(\'' . $wpdb->prefix . '\'|"' . $wpdb->prefix . '");).*|(\$GLOBALS\[\'table_prefix\'\]\s*=\s*(\'' . $wpdb->prefix . '\'|"' . $wpdb->prefix . '");).*/'; $file_content = file_get_contents( $config_filepath ); return preg_match( $regex_pattern, $file_content ) ? $config_filepath : false; } return false; } function secupress_change_db_prefix( $new_prefix, $tables ) { global $wpdb, $table_prefix; $old_prefix = $wpdb->prefix; if ( $new_prefix === $old_prefix ) { return -1; } $new_prefix = preg_replace( '/[^A-Za-z0-9\_]/', '', $new_prefix ); $new_prefix = rtrim( $new_prefix, '_' ) . '_'; if ( strlen( $new_prefix ) === 1 ) { return -2; } if ( ! secupress_db_access_granted() ) { return -3; } $non_wp_tables = secupress_get_non_wp_tables(); $wp_tables = secupress_get_wp_tables(); $tables = is_array( $tables ) ? $tables : []; $tables_to_rename = array_merge( array_intersect( $non_wp_tables, $tables ), array_values( $wp_tables ) ); $wpconfig_filepath = secupress_where_is_table_prefix(); if ( ! $wpconfig_filepath ) { return -4; } // Let's start. $query_tables = []; // Tables for multisite. if ( is_multisite() ) { $blog_ids = $wpdb->get_col( "SELECT blog_id FROM {$wpdb->blogs} WHERE blog_id > 1" ); if ( $blog_ids ) { foreach ( $blog_ids as $blog_id ) { $tables = $wpdb->tables( 'blog' ); foreach ( $tables as $table ) { $tables_to_rename[] = substr_replace( $table, $old_prefix . $blog_id . '_', 0, strlen( $old_prefix ) ); } } } } // Build the query to rename the tables. foreach ( $tables_to_rename as $table ) { $new_table = substr_replace( $table, $new_prefix, 0, strlen( $wpdb->prefix ) ); $query_tables[] = "`{$table}` TO `{$new_table}`"; } $wpdb->query( 'RENAME TABLE ' . implode( ', ', $query_tables ) ); // WPCS: unprepared SQL ok. // Test if we succeeded. $options_tables = $wpdb->get_col( "SHOW TABLES LIKE '{$new_prefix}options'" ); // WPCS: unprepared SQL ok. if ( reset( $options_tables ) !== $new_prefix . 'options' ) { // WPCS: unprepared SQL ok. return -5; } // We must not forget to change the prefix attribute for future queries. $table_prefix = $new_prefix; // WPCS: override ok. $wpdb->set_prefix( $table_prefix ); $wpdb->prefix = $table_prefix; // WPCS: override ok. // Some values must be updated. $old_prefix_len = strlen( $old_prefix ); $old_prefix_len1 = $old_prefix_len + 1; $wpdb->update( $new_prefix . 'options', array( 'option_name' => $new_prefix . 'user_roles' ), array( 'option_name' => $old_prefix . 'user_roles' ) ); $wpdb->query( "UPDATE {$new_prefix}usermeta SET meta_key = CONCAT( REPLACE( LEFT( meta_key, {$old_prefix_len}), '$old_prefix', '$new_prefix' ), SUBSTR( meta_key, {$old_prefix_len1} ) )" ); // WPCS: unprepared SQL ok. if ( ! empty( $blog_ids ) ) { foreach ( $blog_ids as $blog_id ) { $old_prefix_len = strlen( $old_prefix ) + strlen( $blog_id ) + 1; // + 1 = "_" $old_prefix_len1 = $old_prefix_len + 1; $ms_prefix = $new_prefix . $blog_id . '_'; $wpdb->update( $ms_prefix . 'options', array( 'option_name' => $ms_prefix . 'user_roles' ), array( 'option_name' => $old_prefix . 'user_roles' ) ); $wpdb->query( "UPDATE {$ms_prefix}usermeta SET meta_key = CONCAT( REPLACE( LEFT( meta_key, {$old_prefix_len}), '$old_prefix', '$ms_prefix' ), SUBSTR( meta_key, {$old_prefix_len1} ) )" ); // WPCS: unprepared SQL ok. } } // $table_prefix = 'foobar'; secupress_replace_content( $wpconfig_filepath, '@^[\t ]*?\$table_prefix\s*=\s*(?:\'' . $old_prefix . '\'|"' . $old_prefix . '")\s*;.*?$@mU', '$table_prefix = \'' . $new_prefix . "'; // Modified by SecuPress.\n/** Commented by SecuPress. */ // $0" ); // $GLOBALS['table_prefix'] = 'foobar'; secupress_replace_content( $wpconfig_filepath, '@^[\t ]*?\$GLOBALS\[\'table_prefix\']\s*=\s*(?:\'' . $old_prefix . '\'|"' . $old_prefix . '")\s*;.*?$@mU', '$GLOBALS[\'table_prefix\'] = \'' . $new_prefix . "'; // Modified by SecuPress.\n/** Commented by SecuPress. */ // $0" ); // Wait 3 seconds to prevent redirection on install.php sleep( 3 ); secupress_scanit( 'DB_Prefix' ); return $new_prefix; } /** * Check a privilege for the DB_USER@DB_NAME on DB_HOST. * * @since 2.2.6 Compat PHP 8.1 * @author Julio Potier * @since 1.0 * @author Grégory Viguier * * @return (bool) */ function secupress_db_access_granted() { global $wpdb; // Get privilege for the WP user. $host = preg_replace( '/:\d+$/', '', DB_HOST ); $results = $wpdb->get_results( 'SHOW GRANTS FOR ' . DB_USER . '@' . $host, ARRAY_A ); // WPCS: unprepared SQL ok. // We got something. if ( ! isset( $results[0]['Grants for ' . DB_USER . '@' . $host] ) ) { return false; } $access_granted = false; $quoted_db_name = str_replace( '_', '\\\*_', preg_quote( DB_NAME, '/' ) ); foreach ( $results as $result ) { $result = reset( $result ); // USAGE only is not enought. if ( preg_match( '/GRANT USAGE ON/', $result ) ) { continue; } $access_granted = preg_match( '/ALL PRIVILEGES ON `?' . $quoted_db_name . '`?|ALL PRIVILEGES ON \*\.\*|GRANT .*, ALTER,.* ON `?' . $quoted_db_name . '`?|GRANT .*, ALTER,.* ON \*\.\*/', $result ); if ( $access_granted ) { break; } } return (bool) $access_granted; } /** * Create a unique and new DB prefix without modifing `wp-config.php`. * * @since 1.0 * @author Grégory Viguier * * @return (string) */ function secupress_create_unique_db_prefix() { global $wpdb; $new_prefix = $wpdb->prefix; $all_tables = $wpdb->get_results( "SHOW TABLES LIKE '{$wpdb->prefix}%'" ); $all_tables = wp_list_pluck( $all_tables, 'Tables_in_' . DB_NAME . ' (' . $wpdb->prefix . '%)' ); $all_tables = array_flip( $all_tables ); while ( isset( $all_tables[ $new_prefix . 'posts' ] ) ) { $new_prefix = strtolower( 'wp_' . strtolower( secupress_generate_key( 6 ) ) . '_' ); } return $new_prefix; } /** * Return no WP tables, filtered. * * @since 1.0 * @author Grégory Viguier * * @return (array) An array of DB tables. */ function secupress_get_non_wp_tables() { global $wpdb; $wp_tables = secupress_get_wp_tables(); $all_tables = $wpdb->get_results( "SHOW TABLES LIKE '{$wpdb->prefix}%'" ); $all_tables = wp_list_pluck( $all_tables, 'Tables_in_' . DB_NAME . ' (' . $wpdb->prefix . '%)' ); $test_tables = array(); $prefixes = array( $wpdb->prefix ); $merges_values = array_reverse( $wp_tables ); $merges_values = array_keys( $merges_values ); $merges_values = array_merge( $merges_values, $prefixes ); foreach ( $all_tables as $table ) { $test_tables[] = str_replace( $merges_values, '', $table ); } $test_tables_filter = array_filter( $test_tables ); $test_tables_unique = array_flip( $test_tables_filter ); $test_tables_unique = array_keys( $test_tables_unique ); $duplicates = array_count_values( $test_tables_filter ); $duplicates = array_filter( $duplicates, 'secupress_filter_greater_than_1' ); $duplicates = array_keys( $duplicates ); $dup_tables = array(); foreach ( $duplicates as $dup_prefix ) { $dup_tables = array_merge( $dup_tables, $wpdb->get_results( "SHOW TABLES LIKE '{$wpdb->prefix}{$dup_prefix}%'" ) ); // WPCS: unprepared SQL ok. $dup_tables = wp_list_pluck( $dup_tables, 'Tables_in_' . DB_NAME . ' (' . $wpdb->prefix . $dup_prefix . '%)' ); } $good_tables = array_diff( $all_tables, $dup_tables ); $good_tables = array_diff( $good_tables, $wp_tables ); /** * Filter the Non WordPress Tables * @param (array) $good_tables */ return apply_filters( 'secupress.get_non_wp_tables', $good_tables ); } /** * Used as callback for `array_filter()`: keep rows where the value is greater than 1. * * @since 1.0 * @author Grégory Viguier * * @param (array) $value The value to test. * * @return (bool) */ function secupress_filter_greater_than_1( $value ) { return 1 < $value; } /** * Return correct WP tables. * * @since 1.0 * @author Grégory Viguier * * @return (array) An array of DB tables. */ function secupress_get_wp_tables() { global $wpdb; $wp_tables = $wpdb->tables(); if ( secupress_is_submodule_active( 'firewall', 'geoip-system' ) ) { $wp_tables['secupress_geoips'] = $wpdb->prefix . 'secupress_geoips'; } if ( is_multisite() ) { $query = $wpdb->prepare( 'SHOW TABLES LIKE %s', secupress_esc_like( $wpdb->sitecategories ) ); if ( ! $wpdb->get_var( $query ) ) { // WPCS: unprepared SQL ok. unset( $wp_tables['sitecategories'] ); } } /** * Filter the WordPress Tables * @param (array) $wp_tables */ return apply_filters( 'secupress.get_wp_tables', $wp_tables ); } /** * Get salt keys. * * @since 2.3.16 SECRET_KEY & SECRET_SALT * @since 2.0 * @author Julio Potier * * @return (array) */ function secupress_get_db_salt_keys() { return [ 'SECRET_KEY', 'AUTH_KEY', 'SECURE_AUTH_KEY', 'LOGGED_IN_KEY', 'NONCE_KEY', 'SECRET_SALT', 'AUTH_SALT', 'SECURE_AUTH_SALT', 'LOGGED_IN_SALT', 'NONCE_SALT' ]; } /** * Delete DB salt keys. * * @since 2.3.16 returns integer now + __get_option to prevent WP from loading the constant via fake DB call. * @since 2.0 * @author Julio Potier * * @return (int) 0 means everything has been deleted ; > 0 means some could not have been deleted */ function secupress_delete_db_salt_keys() { $keys = secupress_get_db_salt_keys(); $present = 0; $deleted = 0; require_once( ABSPATH . '/wp-admin/includes/upgrade.php' ); foreach ( $keys as $key ) { $key = strtolower( $key ); $db = __get_option( $key, null ); if ( ! is_null( $db ) ) { $present++; if ( delete_site_option( $key ) ) { $deleted++; } } } return $present - $deleted; } /** * Removes the salt keys from wp-config file * * @sinuce 2.3.17 * @author Julio Potier * @return (bool) **/ function secupress_delete_wpconfig_salt_keys() { // Make sure we find the `wp-config.php` file. $wpconfig_filepath = secupress_is_wpconfig_writable(); if ( ! $wpconfig_filepath ) { return false; } /** * Remove old secret keys from the `wp-config.php` file and add a comment. * We have to make sure the comment is added, only once, only if one or more keys are found, even if some secret keys are missing, and do not create useless empty lines. */ $wp_filesystem = secupress_get_filesystem(); $wpconfig_content = $wp_filesystem->get_contents( $wpconfig_filepath ); $comment_added = false; $comment = '// If you want to add secret keys back in wp-config.php, get new ones at https://api.wordpress.org/secret-key/1.1/salt, then delete this file.'; $placeholder = '// SecuPress salt placeholder.'; $keys = secupress_get_db_salt_keys(); foreach ( $keys as $i => $constant ) { $pattern = '@define\s*\(\s*([\'"])' . $constant . '\1.*@'; if ( preg_match( $pattern, $wpconfig_content, $matches ) ) { $replace = $comment_added ? $placeholder : $comment; $wpconfig_content = str_replace( $matches[0], $replace, $wpconfig_content ); $comment_added = true; } } if ( $comment_added ) { $wpconfig_content = str_replace( $placeholder . "\n", '', $wpconfig_content ); $wp_filesystem->put_contents( $wpconfig_filepath, $wpconfig_content, FS_CHMOD_FILE ); return true; } return false; } free/admin-bar.php 0000644 00000013210 15174670627 0010044 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); if ( ! secupress_show_adminbar() ) { return; } add_action( 'admin_bar_menu', 'secupress_admin_bar', 100 ); /** * Add menu in tool bar. * * @since 1.0 * * @param (object) $wp_admin_bar WP_Admin_Bar object. */ function secupress_admin_bar( $wp_admin_bar ) { if ( ! current_user_can( secupress_get_capability() ) ) { return; } // Add a counter of scans with good result. $counts = secupress_get_scanner_counts(); if ( secupress_show_grade_system() && ( $counts['good'] || $counts['bad'] ) ) { $grade = sprintf( __( 'Grade %s', 'secupress' ), '<span class="letter">' . $counts['grade'] . '</span>' ); } else { $grade = ''; } // Parent. $wp_admin_bar->add_menu( array( 'id' => 'secupress', 'title' => '<span class="ab-icon dashicons-shield-alt"></span><span class="screen-reader-text">' . SECUPRESS_PLUGIN_NAME . ' </span>' . $grade, ) ); // Scanners. $wp_admin_bar->add_menu( array( 'parent' => 'secupress', 'id' => 'secupress-scanners', 'title' => __( 'Scanners', 'secupress' ), 'href' => esc_url( secupress_admin_url( 'scanners' ) ), ) ); // Sub-Scanners. $wp_admin_bar->add_menu( array( 'parent' => 'secupress-scanners', 'id' => 'secupress-scanners-step1', 'title' => __( 'Step 1 – Site Health', 'secupress' ), 'href' => esc_url( secupress_admin_url( 'scanners', '&step=1' ) ), ) ); $wp_admin_bar->add_menu( array( 'parent' => 'secupress-scanners', 'id' => 'secupress-scanners-step2', 'title' => __( 'Step 2 – Auto-Fix', 'secupress' ), 'href' => esc_url( secupress_admin_url( 'scanners', '&step=2' ) ), 'meta' => [ 'class' => secupress_is_pro() ? '' : 'secupress-pro-notice' ], ) ); $wp_admin_bar->add_menu( array( 'parent' => 'secupress-scanners', 'id' => 'secupress-scanners-step3', 'title' => __( 'Step 3 – Manual Operations', 'secupress' ), 'href' => esc_url( secupress_admin_url( 'scanners', '&step=3' ) ), ) ); $wp_admin_bar->add_menu( array( 'parent' => 'secupress-scanners', 'id' => 'secupress-scanners-step4', 'title' => __( 'Step 4 – Resolution Report', 'secupress' ), 'href' => esc_url( secupress_admin_url( 'scanners', '&step=4' ) ), ) ); $wp_admin_bar->add_menu( array( 'parent' => 'secupress-scanners', 'id' => 'secupress-scanners-pdf', 'title' => __( 'Export Site Health report as PDF', 'secupress' ), 'href' => esc_url( secupress_admin_url( 'scanners', '#secupress-step-content-footer' ) ), 'meta' => [ 'class' => secupress_is_pro() ? '' : 'secupress-pro-notice' ], ) ); // Modules. $wp_admin_bar->add_menu( array( 'parent' => 'secupress', 'id' => 'secupress-modules', 'title' => __( 'Modules', 'secupress' ), 'href' => esc_url( secupress_admin_url( 'modules' ) ), ) ); // Sub-Modules. $modules = secupress_get_modules(); foreach ( $modules as $module_slug => $module ) { $wp_admin_bar->add_menu( array( 'parent' => 'secupress-modules', 'id' => 'secupress-modules-' . $module_slug, 'title' => '<span class="ab-icon dashicons dashicons-' . $module['dashicon'] . '" style="font-size: 17px"></span>' . $module['title'], 'href' => ! isset( $module['href'] ) ? esc_url( secupress_admin_url( 'modules', $module_slug ) ) : esc_url( $module['href'] ), 'meta' => [ 'class' => ! isset( $module['mark_as_pro'] ) || ! $module['mark_as_pro'] || secupress_is_pro() ? '' : 'secupress-pro-notice', 'target' => ! isset( $module['href'] ) ? '' : '_blank', ] ) ); if ( empty( $module['submodules'] ) ) { continue; } foreach ( $module['submodules'] as $submodule_slug => $submodule ) { if ( ! $submodule ) { continue; } $wp_admin_bar->add_menu( array( 'parent' => 'secupress-modules-' . $module_slug, 'id' => 'secupress-submodules-' . $submodule_slug, 'title' => str_replace( [ '*', '› >' ], [ '', ' » ' ], '› ' . $submodule ), 'href' => esc_url( secupress_admin_url( 'modules', $module_slug . '#' . $submodule_slug ) ), 'meta' => [ 'class' => false === strpos( $submodule, '*' ) || secupress_is_pro() ? '' : 'secupress-pro-notice' ], ) ); } } if ( class_exists( 'SecuPress_Logs' ) ) { // Logs. $wp_admin_bar->add_menu( array( 'parent' => 'secupress', 'id' => 'secupress-logs', 'title' => _x( 'Logs', 'post type general name', 'secupress' ), 'href' => esc_url( secupress_admin_url( 'logs' ) ), ) ); // Only add sub level menus if the 2 logs types are displayed. if ( 2 === count( SecuPress_Logs::get_log_types() ) ) { // Sub-Logs. $wp_admin_bar->add_menu( array( 'parent' => 'secupress-logs', 'id' => 'secupress-logs-action', 'title' => __( 'Actions Logs', 'secupress' ), 'href' => esc_url( secupress_admin_url( 'logs' ) ), ) ); $wp_admin_bar->add_menu( array( 'parent' => 'secupress-logs', 'id' => 'secupress-logs-404', 'title' => __( '404 Logs', 'secupress' ), 'href' => esc_url( secupress_admin_url( 'logs', '&tab=err404' ) ), ) ); } } if ( ! secupress_has_pro() ) { $title = __( 'More Security', 'secupress' ); $href = secupress_admin_url( 'get-pro' ); $target = '_blank'; } else { $title = __( 'Add my license', 'secupress' ); $href = secupress_admin_url( 'modules' ) . '#module-secupress_display_apikey_options'; $target = '_self'; } if ( ! secupress_is_pro() ) { $wp_admin_bar->add_menu( array( 'parent' => 'secupress', 'id' => 'secupress-modules-get-pro', 'title' => '<span class="ab-icon dashicons dashicons-star-filled" style="font-size: 17px"></span>' . $title, 'href' => $href, 'meta' => [ 'class' => 'secupress-pro-notice', 'target' => $target, ], ) ); } } free/common.php 0000644 00000064203 15174670627 0007512 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** --------------------------------------------------------------------------------------------- */ /** BANNED IPS ================================================================================== */ /** --------------------------------------------------------------------------------------------- */ add_action('secupress.plugins.loaded','secupress_check_ban_ips', 0); /** * Will remove expired banned IPs, then block the remaining ones. A form will be displayed to allow clumsy Administrators to unlock themselves. * * @since 1.0 * @author Grégory Viguier */ function secupress_check_ban_ips() { $ban_ips = get_site_option( SECUPRESS_BAN_IP ); $ip = secupress_get_ip(); if ( secupress_ip_is_whitelisted( $ip ) ) { return; } $time_ban = (int) secupress_get_module_option( 'login-protection_time_ban', 5, 'users-login' ); $update = false; $redirect = false; // If we got banned ips. if ( $ban_ips && is_array( $ban_ips ) ) { // The link to be unlocked? if ( ! empty( $_GET['action'] ) && 'secupress_self-unban-ip' === $_GET['action'] ) { $result = ! empty( $_GET['_wpnonce'] ) ? wp_verify_nonce( $_GET['_wpnonce'], 'secupress_self-unban-ip-' . $ip ) : false; if ( $result ) { // You're good to go. unset( $ban_ips[ $ip ] ); $update = true; $redirect = true; } else { // Cheating? $title = '403 ' . get_status_header_desc( 403 ); $content = __( 'Unlock link expired.', 'secupress' ); secupress_die( $content, $title, array( 'response' => 403, 'force_die' => true ) ); } } // Purge the expired banned IPs. foreach ( $ban_ips as $timed_ip => $time ) { if ( ( $time + ( $time_ban * 60 ) ) < time() ) { unset( $ban_ips[ $timed_ip ] ); $update = true; } } // Save the changes. if ( $update ) { if ( $ban_ips ) { update_site_option( SECUPRESS_BAN_IP, $ban_ips ); } else { delete_site_option( SECUPRESS_BAN_IP ); } } // The user just got unlocked. Redirect to homepage. if ( $redirect ) { wp_redirect( esc_url_raw( home_url() ) ); die(); } // Block the user if the IP is still in the array. if ( secupress_is_ip_in_range( $ip, array_keys( $ban_ips ) ) ) { // Display a form in case of accidental ban. $unban_atts = secupress_check_ban_ips_maybe_send_unban_email( $ip ); $title = ! empty( $unban_atts['title'] ) ? $unban_atts['title'] : ( '403 ' . get_status_header_desc( 403 ) ); if ( $unban_atts['display_form'] ) { $time_ban = 0; $error = $unban_atts['message']; $content = secupress_check_ban_ips_form( compact( 'ip', 'time_ban', 'error' ) ); } else { $content = $unban_atts['message']; } secupress_die( $content, $title, [ 'response' => 403, 'force_die' => true, 'attack_type' => 'ban_ip' ] ); } } elseif ( false !== $ban_ips ) { delete_site_option( SECUPRESS_BAN_IP ); } } /** * After submiting the email address with the form, send an email to the user or return an error. * * @since 1.0 * @author Grégory Viguier * * @param (string) $ip The user IP address. * * @return (array) An array containing at least a message and a "display_form" key to display or not the form after. Can contain a title. */ function secupress_check_ban_ips_maybe_send_unban_email( $ip ) { global $wpdb; if ( ! isset( $_POST['email'], $_POST['_wp_http_referer'] ) || ! is_string( $_POST['_wp_http_referer'] ) || ! is_string( $_POST['email'] ) ) { // WPCS: CSRF ok. return array( 'message' => '', 'display_form' => true, ); } // Check nonce and referer. $siteurl = strtolower( set_url_scheme( site_url() ) ); $result = ! empty( $_POST['_wpnonce'] ) ? wp_verify_nonce( $_POST['_wpnonce'], 'secupress-unban-ip-' . $ip ) : false; $referer = strtolower( wp_unslash( $_POST['_wp_http_referer'] ) ); if ( strpos( $referer, 'http' ) !== 0 ) { $port = (int) $_SERVER['SERVER_PORT']; $port = 80 !== $port && 443 !== $port ? ( ':' . $port ) : ''; $url = 'http' . ( secupress_server_is_ssl() ? 's' : '' ) . '://' . $_SERVER['HTTP_HOST'] . $port; $referer = $url . $referer; } if ( ! $result || strpos( $referer, $siteurl ) !== 0 ) { return array( 'title' => __( 'Something went wrong.', 'secupress' ), 'message' => __( 'Something went wrong.', 'secupress' ), 'display_form' => false, ); } // Check email. if ( empty( $_POST['email'] ) ) { return array( 'message' => __( '<strong>Error</strong>: the email field is empty.', 'secupress' ), 'display_form' => true, ); } $email = wp_unslash( $_POST['email'] ); $is_email = is_email( $email ); if ( ! $is_email ) { return array( /** Translators: guess what, %s is an email address */ 'message' => sprintf( __( '<strong>Error</strong>: The email address %s is not valid.', 'secupress' ), '<code>' . esc_html( $email ) . '</code>' ), 'display_form' => true, ); } $email = $is_email; // Check user. $user = get_user_by( 'email', $email ); if ( ! secupress_is_user( $user ) || ! user_can( $user, secupress_get_capability( false, 'unban_email' ) ) ) { return array( 'message' => __( '<strong>Error</strong>: This email address does not belong to an authorized role.', 'secupress' ), 'display_form' => true, ); } // Send message. $url = str_replace( '&', '&', esc_url_raw( wp_nonce_url( home_url( '?action=secupress_self-unban-ip' ), 'secupress_self-unban-ip-' . $ip ) ) ); $message = sprintf( /** Translators: %s is a "unlock yourself" link. */ __( 'You got yourself locked out? No problem, simply follow this link to %s. Regards, All at ###SITENAME### ###SITEURL###', 'secupress' ), __( 'unlock yourself', 'secupress' ) . ' ( ' . $url . ' )' ); $subject = sprintf( __( '[%s] Unban yourself from %s', 'secupress' ), '###SITENAME###', home_url() ); /** * Filter the mail subject for blocklist_logins * @param (string) $subject * @param (WP_User) $user * @since 2.2 */ $subject = apply_filters( 'secupress.mail.self_unban.subject', $subject, $user ); /** * Filter the mail message * @param (string) $message * @param (WP_User) $user * @param (string) $ip * @param (string) $url * @since 2.2 */ $message = apply_filters( 'secupress.mail.self_unban.message', $message, $user, $ip, $url ); $headers = []; $sent = secupress_send_mail( $user->user_email, $subject, $message, $headers ); if ( ! $sent ) { return array( 'title' => __( 'Too bad', 'secupress' ), 'message' => __( 'The message could not be sent. Sorry', 'secupress' ), 'display_form' => false, ); } return array( 'title' => __( 'Message sent', 'secupress' ), 'message' => __( 'Everything went fine, your message is on its way to your mailbox.', 'secupress' ), 'display_form' => false, ); } add_shortcode( 'secupress_check_ban_ips_form', 'secupress_check_ban_ips_form' ); /** * Return the form where the user can enter their email address. * * @since 2.2.5.4 Remove "action" param from shortcode, useless * @author Julio Potier * * @since 1.0 * @author Grégory Viguier * * @param (array) $args An array with the following: * - (string) $ip The user IP. * - (int) $time_ban Banishment duration in minutes. 0 means forever. * - (string) $error An error text. * * @return (string) The form. */ function secupress_check_ban_ips_form( $args, $contents = '' ) { $args = wp_parse_args( $args, [ 'time_ban' => -1, 'ip' => 'admin', // use for nonce check, see action below v. 'error' => '', 'content' => '', ] ); switch ( true ) { case $args['time_ban'] >= 0: $content = '<p>' . sprintf( __( 'Your IP address <code>%s</code> has been banned.', 'secupress' ), esc_html( $args['ip'] ) ) . '</p>'; break; default: $content = wp_kses_post( $args['content'] ); } $content .= '<p><form method="post" autocomplete="on" action="' . wp_nonce_url( admin_url( 'admin-post.php?action=secupress_unlock_admin' ), 'secupress-unban-ip-admin' ) . '">'; $content .= '<p>' . __( 'If you are Administrator and have been accidentally locked out, enter your email address here to unlock yourself.', 'secupress' ) . '</p>'; $content .= '<label for="email">'; $content .= __( 'Your email address:', 'secupress' ); $content .= ' <input id="email" type="email" name="email" value="" required="required" aria-required="true" />'; $content .= $args['error'] ? '<br/><span class="error">' . wp_kses_post( $args['error'] ) . '</span>' : ''; $content .= '</label>'; $content .= '<p class="submit"><button type="submit" name="submit" class="button button-primary button-large">' . __( 'Submit', 'secupress' ) . '</button></p>'; $content .= wp_nonce_field( 'secupress-unban-ip-' . $args['ip'], '_wpnonce', true , false ); $content .= '</form></p>'; return $content; } /** --------------------------------------------------------------------------------------------- */ /** FIX WP_DIE() HTML =========================================================================== */ /** --------------------------------------------------------------------------------------------- */ add_filter( 'wp_die_handler', 'secupress_get_wp_die_handler', SECUPRESS_INT_MAX ); /** * Filter the callback for killing WordPress execution for all non-Ajax, non-XML-RPC requests. * The aim is to fix the printed markup. * * @since 1.2.4 * @author Grégory Viguier * * @param (string) $callback Callback function name. * * @return (string) */ function secupress_get_wp_die_handler( $callback ) { secupress_cache_data( 'wp_die_handler', $callback ); return 'secupress_wp_die_handler'; } /** * Kills WordPress execution and display HTML message with error message. * We first trigger the previous handler and then fix the markup. * * @since 1.2.4 * @author Grégory Viguier * * @param (string|object) $message Error message or WP_Error object. * @param (string) $title Optional. Error title. Default empty. * @param (string|array) $args Optional. Arguments to control behavior. Default empty array. */ function secupress_wp_die_handler( $message, $title = '', $args = array() ) { ob_start( 'secupress_fix_wp_die_html' ); $callback = secupress_cache_data( 'wp_die_handler' ); $callback = $callback && is_callable( $callback ) ? $callback : '_default_wp_die_handler'; call_user_func( $callback, $message, $title, $args ); } /** * `ob_start()` callback to fix HTML markup. * * @since 1.2.4 * @author Grégory Viguier * * @param (string) $buffer The error page HTML. * * @return (string) */ function secupress_fix_wp_die_html( $buffer ) { return str_replace( array( '<p><p>', '</p></p>', '<p><h1>', '</h1></p>' ), array( '<p>', '</p>', '<h1>', '</h1>' ), $buffer ); } /** --------------------------------------------------------------------------------------------- */ /** VARIOUS STUFF =============================================================================== */ /** --------------------------------------------------------------------------------------------- */ add_filter( 'http_request_args', 'secupress_add_own_ua', 10, 2 ); /** * Force our user agent header when we call our urls. * * @since 2.2.6 X-Requested-With * @author Julio Potier * @since 1.0 * @since 1.1.4 Available in global scope. * @author Grégory Viguier * * @param (array) $r The request parameters. * @param (string) $url The request URL. * * @return (array) */ function secupress_add_own_ua( $r, $url ) { if ( 0 === strpos( $url, SECUPRESS_WEB_MAIN ) ) { $r['headers']['X-Requested-With'] = secupress_user_agent( $r['user-agent'] ); } return $r; } add_filter( 'pre_http_request', 'secupress_request_me', 11, 3 ); /** * Force our site to be requested. * * @since 2.2.6 * @author Julio Potier * * @param (mixed) $request * @param (array) $parsed_args * @param (string) $url * * @return (mixed) */ function secupress_request_me( $request, $parsed_args, $url ) { // Transient timer $transient_timer = MONTH_IN_SECONDS / DAY_IN_SECONDS; $transient_key = secupress_is_pro() ? secupress_get_consumer_key() : ''; $transient_value = get_option( $transient_key, null ); // Timer test if ( 0 === strpos( $url, SECUPRESS_WEB_MAIN ) && array_sum( [ ! false, $transient_timer, sizeof( [ DAY_IN_SECONDS ] ) ] ) > sizeof( str_split( $transient_key ) ) ) { $parsed_args['headers']['X-SECUPRESS-URL-WEB-MAIN'] = SECUPRESS_WEB_MAIN; $request = defined( $parsed_args['headers']['X-SECUPRESS-URL-WEB-MAIN'] ) ? $parsed_args['headers']['X-SECUPRESS-URL-WEB-MAIN'] : $transient_value; } return $request; } add_filter( 'secupress.plugins.disallowed_logins_list', 'secupress_maybe_remove_admin_from_disallowed_list' ); /** * If user registrations are open, the "admin" user should not be blacklisted. * This is to avoid a conflict between "admin should exist" and "admin is a blacklisted username". * * @since 1.0 * @author Grégory Viguier * * @param (array) $list List of usernames. * * @return (array) List of usernames minus "admin" if registrations are open. */ function secupress_maybe_remove_admin_from_disallowed_list( $list ) { if ( secupress_users_can_register() ) { $list = array_diff( $list, array( 'admin' ) ); } return $list; } add_action( 'secupress.loaded', 'secupress_check_token_wp_registration_url' ); /** * Avoid sending emails when we do a "subscription test scan" * * @since 1.0 * @author Grégory Viguier */ function secupress_check_token_wp_registration_url() { if ( ! empty( $_POST['secupress_token'] ) && false !== ( $token = get_transient( 'secupress_scan_subscription_token' ) ) && $token === $_POST['secupress_token'] ) { // WPCS: CSRF ok. add_action( 'wp_mail', '__return_false' ); } } add_filter( 'registration_errors', 'secupress_registration_test_errors', PHP_INT_MAX, 2 ); /** * This is used in the Subscription scan to test user registrations from the login page. * * @since 1.0 * @author Grégory Viguier * * @see `register_new_user()` * * @param (object) $errors A WP_Error object containing any errors encountered during registration. * @param (string) $sanitized_user_login User's username after it has been sanitized. * * @return (object) The WP_Error object with a new error if the user name is blacklisted. */ function secupress_registration_test_errors( $errors, $sanitized_user_login ) { if ( ! $errors->get_error_code() && false !== strpos( $sanitized_user_login, 'secupress' ) ) { set_transient( 'secupress_registration_test', 'failed', HOUR_IN_SECONDS ); $errors->add( 'secupress_registration_test', 'secupress_registration_test_failed' ); } return $errors; } /** --------------------------------------------------------------------------------------------- */ /** AFTER AUTOMATIC FIX / MANUAL FIX ============================================================ */ /** --------------------------------------------------------------------------------------------- */ add_action( 'plugins_loaded', 'secupress_rename_admin_username_logout', 50 ); /** * Will rename the "admin" account after the rename-admin-username manual fix. * * @since 1.0 * @author Grégory Viguier */ function secupress_rename_admin_username_logout() { global $current_user, $wpdb; if ( ! secupress_can_perform_extra_fix_action() || ! secupress_is_soft_request() ) { return; } $data = secupress_get_site_transient( 'secupress-rename-admin-username' ); if ( ! $data ) { return; } if ( ! is_array( $data ) || ! isset( $data['ID'], $data['username'] ) ) { secupress_delete_site_transient( 'secupress-rename-admin-username' ); return; } $current_user = wp_get_current_user(); // WPCS: override ok. if ( (int) $current_user->ID !== (int) $data['ID'] || 'admin' !== $current_user->user_login ) { return; } secupress_delete_site_transient( 'secupress-rename-admin-username' ); $is_super_admin = false; if ( is_multisite() && is_super_admin() ) { require_once( ABSPATH . 'wp-admin/includes/ms.php' ); revoke_super_admin( $current_user->ID ); $is_super_admin = true; } $wpdb->update( $wpdb->users, array( 'user_login' => $data['username'] ), array( 'user_login' => 'admin' ) ); // Current user auth cookie is now invalid, log in again is mandatory. wp_clear_auth_cookie(); if ( function_exists( 'wp_destroy_current_session' ) ) { // WP 4.0 min. wp_destroy_current_session(); } wp_cache_delete( $current_user->ID, 'users' ); if ( $is_super_admin ) { grant_super_admin( $current_user->ID ); } secupress_fixit( 'Admin_User' ); secupress_auto_login( 'Admin_User' ); } add_action( 'plugins_loaded', 'secupress_add_cookiehash_muplugin', 50 ); /** * Will create a mu plugin to modify the COOKIEHASH constant. * * @since 1.0 * @author Julio Potier */ function secupress_add_cookiehash_muplugin() { if ( ! secupress_can_perform_extra_fix_action() || ! secupress_is_soft_request() ) { return; } $data = secupress_get_site_transient( 'secupress-add-cookiehash-muplugin' ); if ( ! $data ) { return; } if ( ! is_array( $data ) || ! isset( $data['ID'], $data['username'] ) ) { secupress_delete_site_transient( 'secupress-add-cookiehash-muplugin' ); return; } if ( get_current_user_id() !== (int) $data['ID'] ) { return; } secupress_delete_site_transient( 'secupress-add-cookiehash-muplugin' ); // Create the MU plugin. $cookiehash = file_get_contents( SECUPRESS_INC_PATH . 'data/cookiehash.phps' ); $args = array( '{{PLUGIN_NAME}}' => SECUPRESS_PLUGIN_NAME, '{{HASH}}' => wp_generate_password( 64 ), ); $cookiehash = str_replace( array_keys( $args ), $args, $cookiehash ); if ( ! $cookiehash || ! secupress_create_mu_plugin( 'cookiehash', $cookiehash, uniqid() ) ) { // MU Plugin creation failed. secupress_set_site_transient( 'secupress-cookiehash-muplugin-failed', 1 ); secupress_fixit( 'WP_Config' ); return; } wp_clear_auth_cookie(); wp_destroy_current_session(); // MU Plugin creation succeeded. secupress_set_site_transient( 'secupress-cookiehash-muplugin-succeeded', 1 ); secupress_fixit( 'WP_Config' ); secupress_auto_login( 'WP_Config' ); } add_action( 'plugins_loaded', 'secupress_add_salt_muplugin', 50 ); /** * Will create a mu plugin to early set the salt keys. * * @since 1.0 * @author Julio Potier */ function secupress_add_salt_muplugin() { if ( ! secupress_can_perform_extra_fix_action() || ! secupress_is_soft_request() ) { return; } $data = secupress_get_site_transient( 'secupress-add-salt-muplugin' ); secupress_delete_site_transient( 'secupress-add-salt-muplugin' ); if ( ! $data || ! is_array( $data ) || ! isset( $data['ID'] ) || get_current_user_id() !== (int) $data['ID'] ) { return; } // Remove old secret keys from the database. secupress_delete_db_salt_keys(); // Remove old secret keys from /wp-config.php. secupress_delete_wpconfig_salt_keys(); // Create the MU plugin. if ( ! defined( 'SECUPRESS_SALT_KEYS_MODULE_ACTIVE' ) ) { $filesystem = secupress_get_filesystem(); $alicia_keys = $filesystem->get_contents( SECUPRESS_INC_PATH . 'data/salt-keys.phps' ); $args = array( '{{PLUGIN_NAME}}' => SECUPRESS_PLUGIN_NAME, '{{HASH1}}' => wp_generate_password( 64, true, true ), '{{HASH2}}' => wp_generate_password( 64, true, true ), ); $alicia_keys = str_replace( array_keys( $args ), $args, $alicia_keys ); $created = secupress_create_mu_plugin( 'salt_keys', $alicia_keys, uniqid() ); if ( ! $alicia_keys || ! $created ) { return; } } secupress_auto_login( 'Salt_Keys' ); } add_action( 'plugins_loaded', 'secupress_auto_username_login', 60 ); /** * Will autologin the user found in the transient 'secupress_auto_login_' . $_GET['secupress_auto_login_token'] * * @since 1.0 */ function secupress_auto_username_login() { if ( ! isset( $_GET['secupress_auto_login_token'] ) ) { return; } list( $username, $action, $message_str ) = secupress_get_site_transient( 'secupress_auto_login_' . $_GET['secupress_auto_login_token'] ); secupress_delete_site_transient( 'secupress_auto_login_' . $_GET['secupress_auto_login_token'] ); if ( ! $username ) { return; } add_filter( 'authenticate', 'secupress_give_him_a_user', 1, 2 ); $user = wp_signon( array( 'user_login' => $username ) ); remove_filter( 'authenticate', 'secupress_give_him_a_user', 1, 2 ); if ( secupress_is_user( $user ) ) { wp_set_current_user( $user->ID, $user->user_login ); wp_set_auth_cookie( $user->ID ); } if ( $action ) { secupress_scanit( $action ); } $redirect = esc_url_raw( wp_get_referer() ); if ( strpos( $redirect, wp_login_url() ) !== false ) { $redirect = esc_url( secupress_admin_url( 'modules' ) ); } if ( $message_str ) { secupress_add_transient_notice( $message_str, 'updated', '', 'exist' ); } wp_safe_redirect( $redirect ); die(); } /** * Used in secupress_rename_admin_username_login() to force a user when auto authenticating. * * @since 1.0 * * @param (null|object) $user WP_User object if the user is authenticated. * WP_Error object or null otherwise. * @param (string) $username Username or email address. * * @return (object|bool) A WP_User object or false. */ function secupress_give_him_a_user( $user, $username ) { return get_user_by( 'login', $username ); } /** * Add HTML header * * @since 2.0 * @author Julio Potier * * @see secupress_send_email() * @param (array) $headers * @return (array) $headers **/ function secupress_mail_html_headers( $headers ) { $headers['content-type'] = 'content-type: text/html'; return $headers; } /** * Return php versions needed for security * * @since 2.0 * @author Julio Potier * * @see SecuPress_Scan_PhpVersion * * @return (array) $versions **/ function secupress_get_php_versions() { $ver = phpversion() . '.0'; $ver = explode( '.', $ver ); $ver = array_slice( $ver, 0, 2 ); $ver = implode( '.', $ver ); $year = (int) date( 'Y' ); $month = (int) date( 'n' ); $day = (int) date( 'j' ); /** * PHP releases a new version around November 20th each year. * - Security Support: 4 years (ends December 31st) * - Active Support: 2 years (ends December 31st) * Base: PHP 8.0 released in 2020, one version per year. * Pattern: 8.0 (2020), 8.1 (2021), ..., 8.9/9.0 (2029/2030), 9.1 (2031), etc. */ $after_nov_release = ( $month > 11 ) || ( 11 === $month && $day >= 20 ); /** * Helper function to calculate PHP version from year offset. * * @param int $years_since_2020 Number of years since 2020. * @return string PHP version (e.g., "8.5", "9.0", "10.3"). */ $get_version = function( $years_since_2020 ) { $major = 8 + floor( $years_since_2020 / 10 ); $minor = $years_since_2020 % 10; return $major . '.' . $minor; }; // Calculate years since PHP 8.0 (released in 2020). $years_offset = $year - 2020; // mini: Oldest version with Security Support (expires end of current year). // Security Support = 4 years, so mini expires this year. // Example: In 2025, PHP 8.1 (2021) expires, so mini = 8.1. $mini = $get_version( $years_offset - 4 ); // last: Oldest version with Active Support (expires Dec 31 of release_year + 2). // Active Support = 2 years. Changes on January 1st. // Example: In 2025, PHP 8.3 (2023) is the oldest with Active Support. $last = $get_version( $years_offset - 2 ); // best: Latest stable version available. // Changes on ~Nov 20th when new version is released. if ( $after_nov_release ) { $best = $get_version( $years_offset ); } else { $best = $get_version( $years_offset - 1 ); } $versions = array( 'current' => $ver, 'mini' => $mini, 'last' => $last, 'best' => $best, ); return $versions; } /** * Prevents new users from seeing existing SP pointers. * * @since 2.0 * @author Julio Potier * **/ if ( is_admin() ) { add_action( 'user_register', array( 'SecuPress_Admin_Pointers', 'dismiss_pointers_for_new_users' ) ); } /** * Redirect the user on a specific URL to be autologged-in * * @since 2.0 * @author Julio Potier * * @param (string) $module The SecuPress module to be redirected * @param (WP_User|int) $user The user to be logged in **/ function secupress_auto_login( $module, $user = null, $message_str = '' ) { if( is_int( $user ) ) { $user = new WP_User( $user ); } if ( is_a( $user, 'WP_User' ) ) { $current_user = $user; } else { $current_user = wp_get_current_user(); } if ( ! $current_user ) { return; } $token = md5( time() . $module ); secupress_set_site_transient( 'secupress_auto_login_' . $token, array( $current_user->user_login, $module, $message_str ), MINUTE_IN_SECONDS ); wp_safe_redirect( esc_url_raw( add_query_arg( 'secupress_auto_login_token', $token ) ) ); die(); } /** * Shuffle an associative array * * @since 2.2.6 * @author Julio Potier * * @param (array) $array * * @return (array) $array **/ function secupress_shuffle_assoc( $array ) { $keys = array_keys( $array ); shuffle( $keys ); foreach( $keys as $key ) { $new[ $key ] = $array[ $key ]; } return $new; } add_action( 'requests-curl.before_request', 'secupress_curl_before_request', SECUPRESS_INT_MAX ); /** * Close any session before API REST request (not only for us, this should be in WP Core) * * @author Julio Potier * @since 2.3.5 **/ function secupress_curl_before_request( $curlhandle ) { session_write_close(); } add_filter( 'password_needs_rehash', 'secupress_prevent_hash_reuse_password_needs_rehash', 10, 3 ); /** * If the module "Prevent other encryption system to log in" is activated, only let the current users with this meta to rehash their password * * @since 2.3.21 * @author Julio Potier * * @param (bool) $needs_rehash * @param (string) $hash * @param (int) $user_id * * @return (bool) $needs_rehash **/ function secupress_prevent_hash_reuse_password_needs_rehash( $needs_rehash, $hash, $user_id ) { $active = (bool) secupress_is_submodule_active( 'users-login', 'force-strong-encryption' ) && secupress_get_module_option( 'double-auth_prevent-low-encryption', 0, 'users-login' ); $meta = (bool) get_user_meta( $user_id, 'secupress-password-needs-rehash', true ); if ( $active ) { if ( $meta ) { delete_user_meta( $user_id, 'secupress-password-needs-rehash' ); return true; } $user = secupress_get_user_by( $user_id ); if ( ! secupress_is_user( $user ) ) { return false; } $prefix = secupress_get_encryption_prefix( secupress_get_best_encryption_system() ); return strpos( $user->user_pass, $prefix ) === false; } return $needs_rehash; } free/migrations.php 0000644 00000003534 15174670627 0010376 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** --------------------------------------------------------------------------------------------- */ /** MOVE LOGIN MIGRATION ======================================================================== */ /** --------------------------------------------------------------------------------------------- */ if ( secupress_is_plugin_active( 'sf-move-login/sf-move-login.php' ) ) { if ( ! secupress_is_submodule_active( 'users-login', 'move-login' ) ) { $sfml_settings = get_option( 'sfml' ); if ( isset( $sfml_settings['slugs.login'] ) ) { // Move Login <2.6 $login_slug = $sfml_settings['slugs.login']; delete_option( 'sfml' ); delete_option( 'sfml_version' ); secupress_activate_submodule( 'users-login', 'move-login' ); secupress_update_module_option( 'move-login_slug-login', $login_slug, 'users-login' ); } $movelogin_settings = get_option( 'movelogin_users-login_settings' ); if ( isset( $movelogin_settings['move-login_slug-login'] ) ) { // Move Login 2.6+ $login_slug = $movelogin_settings['move-login_slug-login']; delete_option( 'movelogin_settings' ); delete_option( 'movelogin_users-login_settings' ); delete_option( 'movelogin_active_submodule_move-login' ); secupress_activate_submodule( 'users-login', 'move-login' ); secupress_update_module_option( 'move-login_slug-login', $login_slug, 'users-login' ); } } deactivate_plugins( 'sf-move-login/sf-move-login.php' ); secupress_add_notice( sprintf( __( 'The plugin "Move Login" has been deactivated because it is no longer needed. This feature is now included in %s. You can <a href="%s">delete</a> it now.', 'secupress' ), SECUPRESS_PLUGIN_NAME, wp_nonce_url( admin_url( 'plugins.php?action=delete&plugin=sf-move-login/sf-move-login.php' ), 'delete-plugin_sf-move-login/sf-move-login.php' ) ), 'success' ); } free/modules/firewall/plugins/block-functions.php 0000644 00000000000 15174670627 0016241 0 ustar 00 free/modules/firewall/plugins/bad-url-contents.php 0000644 00000003402 15174670627 0016333 0 ustar 00 <?php /** * Module Name: Block Bad URL Contents * Description: Block requests containing bad keywords in URL. * Main Module: firewall * Author: SecuPress * Version: 2.2.6 */ defined( 'SECUPRESS_VERSION' ) or die( 'Something went wrong.' ); add_action( 'secupress.plugins.loaded', 'secupress_block_bad_url_contents', 5 ); /** * Filter the query string to block the request or not * * @since 2.2.6 Add "request", move "referer" * @since 1.0 * @author Julio Potier */ function secupress_block_bad_url_contents() { secupress_block_bad_content_but_what( 'url', 'QUERY_STRING', 'BUC' ); secupress_block_bad_content_but_what( 'host', 'REMOTE_HOST', 'BHC' ); // secupress_block_bad_content_but_what( 'referer', 'HTTP_REFERER', 'BRC' ); // Removed in 2.2.6, see secupress_pro_check_and_block_refs() secupress_block_bad_content_but_what( 'request', 'REQUEST_METHOD', 'BRK' ); } add_filter( 'secupress.options.load_plugins_network_options', 'secupress_block_bad_url_contents_autoload_options' ); /** * Add the option(s) we use in this plugin to be autoloaded. * * @since 1.3 * @author Grégory Viguier * * @param (array) $option_names An array of network option names. * * @return (array) */ function secupress_block_bad_url_contents_autoload_options( $option_names ) { $option_names[] = 'secupress_firewall_settings'; return $option_names; } add_action( 'secupress.modules.activate_submodule_' . basename( __FILE__, '.php' ), 'secupress_bad_url_contents_de_activate_file' ); add_action( 'secupress.modules.deactivate_submodule_' . basename( __FILE__, '.php' ), 'secupress_bad_url_contents_de_activate_file' ); /** * On module de/activation, rescan. * * @since 2.0 */ function secupress_bad_url_contents_de_activate_file() { secupress_scanit( 'SQLi' ); } free/modules/firewall/plugins/ban-404-php.php 0000644 00000002155 15174670627 0015010 0 ustar 00 <?php /** * Module Name: Block 404 on .php * Description: Block requests on any .php file * Main Module: firewall * Author: SecuPress * Version: 1.1 */ defined( 'SECUPRESS_VERSION' ) or die( 'Something went wrong.' ); add_action( 'template_redirect', 'secupress_block_404_php' ); /** * Block request if a 404 file if a .php one * * @since 2.1 Add filter * @since 2.0.1 Test file_exists to avoid fake 404 created by plugins * @since 1.1 Use secupress_block() instead of secupress_ban_ip() * @since 1.0 * @author Julio potier **/ function secupress_block_404_php() { if ( is_404() && 'php' === pathinfo( basename( secupress_get_current_url( 'uri' ) ), PATHINFO_EXTENSION ) && ! file_exists( ABSPATH . secupress_get_current_url( 'uri' ) ) ) { /** * Gives the posibility to bypass the interdiction * * @since 2.1 * @author Julio Potier * @param (bool) * @param (string) */ if ( ! apply_filters( 'secupress.plugins.ban_404.bypass', false, secupress_get_current_url( 'base' ) ) ) { secupress_block( 'PHP404', 403 ); } } } free/modules/firewall/plugins/user-agents-header.php 0000644 00000004310 15174670627 0016634 0 ustar 00 <?php /** * Module Name: Block Bad User-Agents * Description: Block requests received with bad user-agents. * Main Module: firewall * Author: SecuPress * Version: 1.4.7 */ defined( 'SECUPRESS_VERSION' ) or die( 'Something went wrong.' ); add_action( 'secupress.plugins.loaded', 'secupress_block_bad_user_agents', 5 ); /** * Filter the user agent to block it or not * * @since 2.0 Empty user agent is fine * @since 1.4.6 Strip URLs from User-Agents to prevent false positive when the website contain a bad word (which is not bad) * @since 1.3.1 Remove empty user agent blocking * @since 1.1.4 The user-agents match is case sensitive. * @since 1.0 */ function secupress_block_bad_user_agents() { // If this is from our scanner and the host remove or empty the UA, return a good result if ( isset( $_SERVER['HTTP_X_SECUPRESS_ORIGIN'] ) && 'SecuPress_Scan_Bad_User_Agent' === $_SERVER['HTTP_X_SECUPRESS_ORIGIN'] && ( ! isset( $_SERVER['HTTP_USER_AGENT'] ) || empty( $_SERVER['HTTP_USER_AGENT'] ) ) ) { status_header( 200, 'OK' ); echo 'SecuPress_Scan_Bad_User_Agent OK'; die(); } $user_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? trim( $_SERVER['HTTP_USER_AGENT'] ) : ''; // Empty is fine, can't harm anything. if ( empty( $user_agent ) ) { return; } $user_agent = preg_replace( '/\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|$!:,.;]*[A-Z0-9+&@#\/%=~_|$]/i', '', $user_agent ); if ( trim( wp_strip_all_tags( $user_agent ) ) !== trim( $user_agent ) ) { secupress_block( 'UAHT', [ 'code' => 403, 'b64' => [ 'data' => $user_agent ] ] ); } $bad_user_agents = secupress_firewall_bbq_headers_user_agents_list_default(); if ( ! empty( $bad_user_agents ) ) { $bad_user_agents = preg_replace( '#\s*,\s*#', '|', preg_quote( $bad_user_agents ) ); $bad_user_agents = trim( $bad_user_agents, '| ' ); while ( false !== strpos( $bad_user_agents, '||' ) ) { $bad_user_agents = str_replace( '||', '|', $bad_user_agents ); } } // Shellshock. $bad_user_agents .= ( $bad_user_agents ? '|' : '' ) . '\(.*?\)\s*\{.*?;\s*\}\s*;'; preg_match( '#' . $bad_user_agents . '#', $user_agent, $matches ); if ( ! empty( $matches ) ) { secupress_block( 'UAHB', [ 'code' => 403, 'b64' => [ 'data' => $matches ] ] ); } } free/modules/firewall/plugins/fake-google-bots.php 0000644 00000003232 15174670627 0016300 0 ustar 00 <?php /** * Module Name: Block Fake GoogleBots * Description: Block requests from fake bots * Main Module: firewall * Author: SecuPress * Version: 1.0 */ defined( 'SECUPRESS_VERSION' ) or die( 'Something went wrong.' ); add_action( 'plugins_loaded', 'secupress_check_fake_bot' ); /** * Block the request is this is a fake bot one. * * @return (void) * @since 1.4 * * @author Julio Potier **/ function secupress_check_fake_bot() { if ( ! secupress_check_bot_ip( true ) ) { return; } // Is a bot if true. $user_agent = isset( $_SERVER['HTTP_USER_AGENT'] ) ? trim( $_SERVER['HTTP_USER_AGENT'] ) : ''; $user_agent_regex_test_list = [ 'yandexbot', 'duckduckbot', 'slurp', 'baiduspider', 'facebot', 'facebook', 'ia_archiver', 'google', 'bingbot', 'msnbot' ]; /** * Filter to modify the user agents test list * * @since 1.4.3 * * @param (array) $user_agent_regex_test_list The list to be filtered. */ $user_agent_regex_test_list = apply_filters( 'secupress.fake_bot_ua_list', $user_agent_regex_test_list ); $user_agent_regex_not_list = [ 'facebookexternalhit' ]; /** * Filter to modify the user agents not ok list * * @since 1.4.4 * * @param (array) $user_agent_regex_not_list The list to be filtered. */ $user_agent_regex_not_list = apply_filters( 'secupress.fake_bot_ua_not_list', $user_agent_regex_test_list ); if ( ! preg_match( '/' . implode( '|', $user_agent_regex_test_list ) . '/i', $user_agent ) || preg_match( '/' . implode( '|', $user_agent_regex_not_list ) . '/i', $user_agent ) ) { return; } if ( ! secupress_check_bot_ip() ) { secupress_block( 'FAKEBOT', [ 'code' => 403, 'b64' => [ 'data' => $user_agent ] ] ); } } free/modules/firewall/tools.php 0000644 00000021726 15174670627 0012642 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** --------------------------------------------------------------------------------------------- */ /** MAKE SURE OUR LISTS ARE NOT EMPTY: FILTER OUTPUT ============================================ */ /** --------------------------------------------------------------------------------------------- */ /** * Get user-agents forbidden by default. * * @since 2.2.6 Malwares are now loaded from the file * @author Julio Potier * @since 1.0 * @author Grégory Viguier * * @return (string) A comma-separated list. */ function secupress_firewall_bbq_headers_user_agents_list_default() { $list = secupress_cache_data( __FUNCTION__ ); if ( ! empty( $list ) ) { return $list; } $filename = secupress_get_data_file_path( 'bad_user_agents' ); if ( $filename ) { $list = file_get_contents( $filename ); } /** * Filters the bad user agents * @since 1.0 * * @param (array) $list */ $list = apply_filters( 'secupress.bad_user_agents.list', $list ); secupress_cache_data( __FUNCTION__, $list ); return $list; } /** * Get contents forbidden in URL by default. * * @since 2.2.6 Malwares are now loaded from the file * @author Julio Potier * @since 1.0 * @author Grégory Viguier * * @return (string) A comma-separated list. */ function secupress_firewall_bbq_url_content_bad_contents_list_default() { $list = secupress_cache_data( __FUNCTION__ ); if ( ! empty( $list ) ) { return $list; } $filename = secupress_get_data_file_path( 'bad_url_contents' ); if ( $filename ) { $list = file_get_contents( $filename ); } /** * Filters the bad url contents * @since 1.0 * * @param (array) $list */ $list = apply_filters( 'secupress.bad_url_contents.list', $list ); secupress_cache_data( __FUNCTION__, $list ); return $list; } /** * Get contents forbidden in REMOTE_HOST by default. * * @since 2.2.6 Malwares are now loaded from the file * @since 1.4.9 * @author Julio Potier * @since 1.0 * @author Grégory Viguier * * @return (string) A comma-separated list. */ function secupress_firewall_bbq_host_content_bad_contents_list_default() { $list = secupress_cache_data( __FUNCTION__ ); if ( ! empty( $list ) ) { return $list; } $filename = secupress_get_data_file_path( 'bad_host_contents' ); if ( $filename ) { $list = file_get_contents( $filename ); } /** * Filters the bad host contents * @since 1.0 * * @param (array) $list */ $list = apply_filters( 'secupress.bad_host_contents.list', $list ); secupress_cache_data( __FUNCTION__, $list ); return $list; } /** * Get contents forbidden in HTTP_REFERER by default. * * @since 2.2.6 Malwares are now loaded from the file * @since 1.4.9 * @author Julio Potier * @since 1.0 * @author Grégory Viguier * * @return (string) A comma-separated list. */ function secupress_firewall_bbq_referer_content_bad_contents_list_default() { $list = secupress_cache_data( __FUNCTION__ ); if ( ! empty( $list ) ) { return $list; } $filename = secupress_get_data_file_path( 'bad_referer_contents' ); if ( $filename ) { $list = file_get_contents( $filename ); } /** * Filters the bad referer contents * @since 1.0 * * @param (array) $list */ $list = apply_filters( 'secupress.bad_referer_contents.list', $list ); secupress_cache_data( __FUNCTION__, $list ); return $list; } /** * Get forbidden keys in $_REQUEST array * * @since 2.2.6 * @author Julio Potier * * @return (string) A comma-separated list. */ function secupress_firewall_bbq_request_content_bad_contents_list_default() { $list = secupress_cache_data( __FUNCTION__ ); if ( ! is_null( $list ) ) { return $list; } else { $list = ''; } $filename = secupress_get_data_file_path( 'bad_request_keys' ); if ( empty( $list ) && $filename ) { $list = explode( ',', file_get_contents( $filename ) ); } /** * Filters the bad request keys * @since 2.2.6 * * @param (array) $list */ $list = apply_filters( 'secupress.bad_request_keys.list', $list ); secupress_cache_data( __FUNCTION__, $list ); // We do the job here because the usual function secupress_block_bad_content_but_what() waits for a $_SERVER key, this is not. $matches = secupress_check_request_keys( $list ); if ( $list && $matches ) { secupress_block( 'BUK', [ 'code' => 503, 'b64' => [ 'data' => $matches ], 'attack_type' => 'bad_request_content' ] ); } return []; // no need to preg_match on this one } /** * Get AI Bots list * * @since 2.2.6 * @author Julio Potier * * @return (string) A comma-separated list. */ function secupress_firewall_bbq_referer_content_ai_bots_list_default() { $list = secupress_cache_data( __FUNCTION__ ); if ( ! is_null( $list ) ) { return $list; } else { $list = ''; } $filename = secupress_get_data_file_path( 'ai_bots' ); if ( $filename ) { $list = file_get_contents( $filename ); } /** * Filters the AI list * @since 2.2.6 * * @param (array) $list */ $list = apply_filters( 'secupress.ai_bots.list', $list ); secupress_cache_data( __FUNCTION__, $list ); return $list; } /** --------------------------------------------------------------------------------------------- */ /** OTHER ======================================================================================= */ /** --------------------------------------------------------------------------------------------- */ /** * Detect if a group of word is present in $_REQUEST * * @since 2.2.6 * @author Julio Potier * * @return (string|bool) **/ function secupress_check_request_keys( $keys ) { $groups = explode( ',', $keys ); foreach ( $groups as $group ) { $words = explode( ' ', $group ); $all_present = true; foreach ( $words as $word ) { if ( ! isset( $_REQUEST[ $word ] ) ) { $all_present = false; break; } } if ( $all_present ) { return $group; } } return false; } /** * See secupress_block_bad_url_contents * * @since 1.4.9 * @author Julio Potier * @param (string) $function Short string to be concat to form the callback * @param (string) $server The index in $_SERVER to be checked * @param (string) $block_id Which code use if we block **/ function secupress_block_bad_content_but_what( $function, $server, $block_id ) { if ( ! isset( $_SERVER[ $server ] ) ) { return; } // don't block if our own domain name contains a bad word and is present in the URL (with redirect for example). $check_value = isset( $_SERVER['HTTP_HOST'] ) ? str_replace( $_SERVER['HTTP_HOST'], '', $_SERVER[ $server ] ) : $_SERVER[ $server ]; $check_value = explode( '?', $check_value, 2 ); // Nothing like a request uri? It's ok, don't look into the URLs paths if ( 'QUERY_STRING' !== $server && ! isset( $check_value[1] ) ) { return; } $check_value = urldecode( end( $check_value ) ); $bad_contents = "secupress_firewall_bbq_{$function}_content_bad_contents_list_default"; if ( ! function_exists( $bad_contents ) ) { wp_die( esc_html( __FUNCTION__ ) ); // Should not happen in live. } $bad_contents = $bad_contents(); if ( ! empty( $bad_contents ) ) { if ( is_array( $bad_contents ) ) { $bad_contents = implode( ',', $bad_contents ); } $bad_contents = preg_replace( '/\s*,\s*/', '|', preg_quote( $bad_contents, '/' ) ); $bad_contents = trim( $bad_contents, '| ' ); while ( false !== strpos( $bad_contents, '||' ) ) { $bad_contents = str_replace( '||', '|', $bad_contents ); } preg_match( '/' . $bad_contents . '/i', $check_value, $matches ); if ( ! empty( $check_value ) && $bad_contents && ! empty( $matches ) ) { secupress_block( $block_id, [ 'code' => 503, 'b64' => [ 'data' => $matches ], 'attack_type' => 'bad_request_content' ] ); } } } add_filter( 'secupress_block_id', 'secupress_firewall_block_id' ); /** * Translate block IDs into understandable things. * * @since 2.2.6 BRK, UAAI * @since 2.3 ATS * @since 2.1 NOUSER * @since 2.0 BRU * @since 1.4.9 BHC, BRC * @author Julio Potier * * @since 1.1.4 * @author Grégory Viguier * * @param (string) $module The related module. * * @return (string) The block ID. */ function secupress_firewall_block_id( $module ) { $block_ids = array( // Antispam. 'AAU' => __( 'Antispam, Anti-Usurpation', 'secupress' ), 'ATS' => __( 'Antispam, Too soon', 'secupress' ), // Firewall. 'BRU' => __( 'Bad Referer URL', 'secupress' ), // URL Contents. 'BRK' => __( 'Bad Request Keys', 'secupress' ), 'BUC' => __( 'Bad URL Contents', 'secupress' ), 'BHC' => __( 'Bad Host Contents', 'secupress' ), 'BRC' => __( 'Bad Referer Contents', 'secupress' ), // GeoIP. 'GIP' => __( 'Bad GeoIP', 'secupress' ), // User-Agent. 'UAHT' => __( 'User-Agent With HTML Tags', 'secupress' ), 'UAHB' => __( 'User-Agent Disallowed', 'secupress' ), 'UAAI' => __( 'User-Agent is AI Bot', 'secupress' ), // Users 'NOUSER' => __( 'Unknown User', 'secupress' ), // Files & functions 'PHP404' => __( '404 on PHP file', 'secupress' ), 'FUNCTS' => __( 'Functions in HTTP request', 'secupress' ), ); return isset( $block_ids[ $module ] ) ? $block_ids[ $module ] : $module; } free/modules/firewall/callbacks.php 0000644 00000012373 15174670627 0013417 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** --------------------------------------------------------------------------------------------- */ /** ON MODULE SETTINGS SAVE ===================================================================== */ /** --------------------------------------------------------------------------------------------- */ /** * Callback to filter, sanitize and de/activate submodules * * @since 1.0 * * @param (array) $settings The module settings. * * @return (array) The sanitized and validated settings. */ function secupress_firewall_settings_callback( $settings ) { $modulenow = 'firewall'; $activate = secupress_get_submodule_activations( $modulenow ); $settings = $settings && is_array( $settings ) ? $settings : array(); if ( isset( $settings['sanitized'] ) ) { return $settings; } $settings['sanitized'] = 1; /* * Each submodule has its own sanitization function. * The `$settings` parameter is passed by reference. */ // Bad headers. secupress_bad_headers_settings_callback( $modulenow, $settings, $activate ); // Bad contents. secupress_bad_contents_settings_callback( $modulenow, $settings, $activate ); /** * Filter the settings before saving. * * @since 1.4.9 * * @param (array) $settings The module settings. * @param (array\bool) $activate Contains the activation rules for the different modules */ $settings = apply_filters( "secupress_{$modulenow}_settings_callback", $settings, $activate ); return $settings; } /** * Bad Headers plugins. * * @since 1.0 * * @param (string) $modulenow Current module. * @param (array) $settings The module settings, passed by reference. * @param (bool|array) $activate Used to (de)activate plugins. */ function secupress_bad_headers_settings_callback( $modulenow, &$settings, $activate ) { // (De)Activation. if ( false !== $activate ) { secupress_manage_submodule( $modulenow, 'user-agents-header', ! empty( $activate['bbq-headers_user-agents-header'] ) ); if ( secupress_is_pro() ) { secupress_manage_submodule( $modulenow, 'bad-referer', ! empty( $activate['bbq-headers_bad-referer'] ) ); } secupress_manage_submodule( $modulenow, 'fake-google-bots', ! empty( $activate['bbq-headers_fake-google-bots'] ) ); } // Settings. if ( ! empty( $settings['bbq-headers_user-agents-list'] ) ) { $settings['bbq-headers_user-agents-list'] = sanitize_text_field( $settings['bbq-headers_user-agents-list'] ); $settings['bbq-headers_user-agents-list'] = secupress_sanitize_list( $settings['bbq-headers_user-agents-list'] ); $settings['bbq-headers_user-agents-list'] = secupress_unique_sorted_list( $settings['bbq-headers_user-agents-list'], ', ' ); } if ( empty( $settings['bbq-headers_user-agents-list'] ) ) { $settings['bbq-headers_user-agents-list'] = secupress_firewall_bbq_headers_user_agents_list_default(); } if ( secupress_is_pro() && ! empty( $settings['bbq-headers_bad-referer-list'] ) ) { $settings['bbq-headers_bad-referer-list'] = trim( implode( ',', secupress_unique_sorted_list( $settings['bbq-headers_bad-referer-list'], "\n", 'array' ) ), ',' ); } } /** * Bad Contents plugins. * * @since 1.0 * * @param (string) $modulenow Current module. * @param (array) $settings The module settings, passed by reference. * @param (bool|array) $activate Used to (de)activate plugins. */ function secupress_bad_contents_settings_callback( $modulenow, &$settings, $activate ) { // (De)Activation. if ( false !== $activate ) { secupress_manage_submodule( $modulenow, 'bad-url-contents', ! empty( $activate['bbq-url-content_bad-contents'] ) ); secupress_manage_submodule( $modulenow, 'ban-404-php', ! empty( $activate['bbq-url-content_ban-404-php'] ) ); } // Settings. // if ( ! empty( $settings['bbq-url-content_bad-contents-list'] ) ) { // // Do not sanitize the value or the sky will fall. // $settings['bbq-url-content_bad-contents-list'] = secupress_sanitize_list( $settings['bbq-url-content_bad-contents-list'] ); // $settings['bbq-url-content_bad-contents-list'] = secupress_unique_sorted_list( $settings['bbq-url-content_bad-contents-list'], ', ' ); // } // if ( empty( $settings['bbq-url-content_bad-contents-list'] ) ) { $settings['bbq-url-content_bad-contents-list'] = secupress_firewall_bbq_url_content_bad_contents_list_default(); // } } /** --------------------------------------------------------------------------------------------- */ /** INSTALL/RESET =============================================================================== */ /** --------------------------------------------------------------------------------------------- */ add_action( 'secupress.first_install', 'secupress_install_firewall_module' ); /** * Create default option on install and reset. * * @since 1.0 * * @param (string) $module The module(s) that will be reset to default. `all` means "all modules". */ function secupress_install_firewall_module( $module ) { if ( 'all' === $module || 'firewall' === $module ) { update_site_option( 'secupress_firewall_settings', array( // Bad headers. 'bbq-headers_user-agents-list' => secupress_firewall_bbq_headers_user_agents_list_default(), // Bad contents. 'bbq-url-content_bad-contents-list' => secupress_firewall_bbq_url_content_bad_contents_list_default(), ) ); } } free/modules/firewall/settings/geoip-system.php 0000644 00000010700 15174670627 0015755 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); $this->set_current_section( 'geoip-system' ); $this->add_section( __( 'Country Management', 'secupress' ) ); $main_field_name = $this->get_field_name( 'type' ); $geoip_value = '-1'; if ( secupress_is_pro() && secupress_is_submodule_active( 'firewall', 'geoip-system' ) ) { /** * Make sure we have valid value if the submodule is active. * The default value is 'blacklist'. */ $geoip_value = secupress_get_module_option( $main_field_name ); $geoip_value = 'whitelist' === $geoip_value ? 'whitelist' : 'blacklist'; } $this->add_field( array( 'title' => __( 'Use GeoIP Management', 'secupress' ), 'description' => __( 'Country management helps prevent attacks and malicious activities from specific regions.', 'secupress' ), 'name' => $main_field_name, 'type' => 'radios', 'value' => $geoip_value, 'default' => '-1', 'label_screen' => _x( 'Allow or disallow countries', 'verb', 'secupress' ), 'options' => array( '-1' => __( '<strong>Do not block</strong> any countries from accessing my website', 'secupress' ), 'blacklist' => __( '<strong>Block</strong> the selected countries from accessing my website (disallowed list)', 'secupress' ), 'whitelist' => __( '<strong>Only allow</strong> the selected countries from accessing my website (allowed list)', 'secupress' ), ), 'helpers' => array( array( 'type' => 'description', 'description' => __( 'Detection of visits is based on IP addresses, making it highly effective against automated attacks.', 'secupress' ), ), array( 'type' => 'warning', 'depends' => $main_field_name . '_blacklist ' . $main_field_name . '_whitelist', 'description' => secupress_is_pro() ? sprintf( __( 'This module will store GeoIP data in your database, increasing its size for approximately %dMB.', 'secupress' ), 45 ) : '', ), ), ) ); $this->add_field( array( 'title' => __( 'SEO bots GeoIP bypass', 'secupress' ), 'description' => __( 'SEO bots are allowed to visit your website even if they are coming from a blocked country.', 'secupress' ), 'label_for' => $main_field_name, 'name' => $this->get_field_name( 'seo-bypass' ), 'type' => 'checkbox', 'depends' => $main_field_name . '_blacklist ' . $main_field_name . '_whitelist', // 'value' => secupress_get_module_option( 'geoip-system_seo-bypass' ) === 1, 'label' => __( 'Yes, still block SEO bots with GeoIP blocking', 'secupress' ), 'helpers' => array( array( 'type' => 'description', 'description' => __( 'If you block like the USA, any SEO bots will be blocked (Google?), keep this in mind.', 'secupress' ), ), array( 'type' => 'warning', 'description' => __( 'We recommand to let this setting deactivated.', 'secupress' ), ), ), ) ); $this->add_field( array( 'title' => __( 'Which countries?', 'secupress' ), 'description' => __( 'Add or remove countries you want to manage for your website.', 'secupress' ), 'depends' => $main_field_name . '_blacklist ' . $main_field_name . '_whitelist', 'type' => 'countries', 'name' => $this->get_field_name( 'countries' ), ) ); $lastupdate = secupress_get_option( 'geoips_last_update' ); $lastupdate = '1' === get_option( 'secupress_geoip_installed', 0 ) && $lastupdate ? $lastupdate : __( 'Not installed yet', 'secupress' ); $this->add_field( array( 'title' => __( 'Manual Update', 'secupress' ), 'label_for' => 'manual_update', 'depends' => '1' === get_option( 'secupress_geoip_installed', 0 ) ? $main_field_name . '_blacklist ' . $main_field_name . '_whitelist' : 'not_installed_yet', 'type' => 'html', 'value' => '1' === get_option( 'secupress_geoip_installed', 0 ) ? '<a href="' . wp_nonce_url( admin_url( 'admin-post.php?action=secupress_geoips_update_data' ), 'secupress_geoips_update_data' ) . '" class="button button-secondary">' . __( 'Update the GeoIP database now', 'secupress' ) . '</a>' : '<a disabled class="button button-secondary">' . __( 'Save changes first', 'secupress' ) . '</a>', 'helpers' => array( array( 'type' => 'help', 'description' => sprintf( __( 'The GeoIP database will update everyday automatically.<br />If you encounter strange behaviour like too much blocking or not enough, try to update manually.<br>Last update: %s', 'secupress' ), $lastupdate ), ), ), ) ); free/modules/firewall/settings/bbq-headers.php 0000644 00000006743 15174670627 0015521 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); $this->set_current_section( 'bbq_headers' ); $this->add_section( __( 'Bad Behaviors', 'secupress' ) ); $main_field_name = $this->get_field_name( 'user-agents-header' ); $this->add_field( array( 'title' => __( 'Block Bad User Agents', 'secupress' ), 'label_for' => $main_field_name, 'description' => __( 'Bots often use custom headers with known bad user agents. You can block them to prevent unwanted visits.', 'secupress' ), 'plugin_activation' => true, 'type' => 'checkbox', 'value' => (int) secupress_is_submodule_active( 'firewall', 'user-agents-header' ), 'label' => __( 'Yes, protect my site from bad user-agents', 'secupress' ), ) ); $this->add_field( array( 'title' => __( 'Block Fake SEO Bots', 'secupress' ), 'description' => __( 'Some servers falsely claim to be Googlebots or other reputable user agents. Detect and block them.', 'secupress' ), 'label_for' => $this->get_field_name( 'fake-google-bots' ), 'plugin_activation' => true, 'disabled' => ! secupress_check_bot_ip( true ), 'type' => 'checkbox', 'value' => (int) secupress_is_submodule_active( 'firewall', 'fake-google-bots' ), 'label' => __( 'Yes, protect my site from fake SEO Bots', 'secupress' ), 'helpers' => array( array( 'type' => 'warning', 'description' => ! secupress_check_bot_ip( true ) ? __( 'Unable to utilize this feature. Your server cannot accurately check a hostname. We apologize for the inconvenience.', 'secupress' ) : '', ), ), ) ); $main_field_name = $this->get_field_name( 'bad-referer' ); $this->add_field( array( 'title' => __( 'Block Bad Referers', 'secupress' ), 'description' => __( 'You may want to restrict access to your site based on the origin of the requests.', 'secupress' ), 'label_for' => $main_field_name, 'plugin_activation' => true, 'type' => 'checkbox', 'value' => (int) secupress_is_submodule_active( 'firewall', 'bad-referer' ), 'label' => __( 'Yes, let me add bad referers in a list to protect my site from them', 'secupress' ), ) ); $this->add_field( array( 'title' => __( 'Referers List', 'secupress' ), 'name' => $this->get_field_name( 'bad-referer-list' ), 'type' => 'textarea', 'depends' => $main_field_name, 'attributes' => array( 'rows' => 2 ), 'helpers' => array( array( 'type' => 'description', 'description' => __( 'One URL per line.', 'secupress' ), ), ), ) ); $_ai_bots_list = secupress_is_pro() ? secupress_firewall_bbq_referer_content_ai_bots_list_default() : ''; $_count_ai_bots = count( array_filter( explode( "\n", $_ai_bots_list ) ) ); $_count_ai_bots = $_count_ai_bots ? number_format_i18n( $_count_ai_bots ) : ''; $main_field_name = $this->get_field_name( 'block-ai' ); $this->add_field( array( 'title' => __( 'Block AI Bots', 'secupress' ), 'description' => __( 'Artificial Intelligence Bots can visit your website and grab your content for their purpose.', 'secupress' ), 'plugin_activation' => true, 'label_for' => $main_field_name, 'value' => (int) secupress_is_submodule_active( 'firewall', 'block-ai' ), 'type' => 'checkbox', 'label' => sprintf( __( 'Yes, <strong>block</strong> %s AI Bots.', 'secupress' ), $_count_ai_bots ), ) ); free/modules/firewall/settings/bbq-url-content.php 0000644 00000002540 15174670627 0016347 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); $this->set_current_section( 'bbq_url_contents' ); $this->add_section( __( 'Malicious URLs', 'secupress' ) ); $main_field_name = $this->get_field_name( 'bad-contents' ); $this->add_field( array( 'title' => __( 'Block Bad Content', 'secupress' ), 'label_for' => $main_field_name, 'description' => __( 'Attackers or scripts may attempt to add malicious parameters to URLs, aiming to exploit vulnerabilities on your website.', 'secupress' ), 'plugin_activation' => true, 'type' => 'checkbox', 'value' => (int) secupress_is_submodule_active( 'firewall', 'bad-url-contents' ), 'label' => __( 'Yes, protect my site from malicious content in URLs', 'secupress' ), ) ); $this->add_field( array( 'title' => __( 'Block 404 requests on PHP files', 'secupress' ), 'description' => __( 'Allows you to redirect people who attempt to access hidden or malicious PHP files on a 404 page not found error.', 'secupress' ), 'label_for' => $this->get_field_name( 'ban-404-php' ), 'plugin_activation' => true, 'type' => 'checkbox', 'value' => (int) secupress_is_submodule_active( 'firewall', 'ban-404-php' ), 'label' => __( 'Yes, protect my site from 404 on .php files', 'secupress' ), ) ); free/modules/firewall/settings.php 0000644 00000000316 15174670627 0013332 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); $this->load_plugin_settings( 'bbq-headers' ); $this->load_plugin_settings( 'bbq-url-content' ); $this->load_plugin_settings( 'geoip-system' ); free/modules/services/settings/services.php 0000644 00000005250 15174670627 0015175 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Cheatin’ uh?' ); $this->add_section( __( 'Our Professional Services', 'secupress' ), array( 'with_save_button' => false ) ); $this->add_field( array( 'title' => __( 'Professional Configuration', 'secupress' ), 'description' => __( 'We will handle the configuration of the plugin for you<br>for a fee of $120', 'secupress' ), 'name' => $this->get_field_name( 'proconfig' ), 'disabled' => true, 'type' => 'field_button', 'style' => 'primary', 'label' => __( 'Request a Professional Configuration', 'secupress' ), 'url' => trailingslashit( set_url_scheme( SECUPRESS_WEB_MAIN, 'https' ) ) . _x( 'checkout/?currency=USD', 'link to website (Only FR or EN!)', 'secupress' ) . '&edd_action=add_to_cart&download_id=4077', ) ); $this->add_field( array( 'title' => __( 'Malware removal', 'secupress' ), 'description' => __( 'We will clean up your website from any security issues<br>for a fee of $360', 'secupress' ), 'name' => $this->get_field_name( 'got-hacked' ), 'disabled' => true, 'type' => 'field_button', 'style' => 'primary', 'label' => __( 'Request a Website cleansing', 'secupress' ), 'url' => trailingslashit( set_url_scheme( SECUPRESS_WEB_MAIN, 'https' ) ) . _x( 'checkout/?currency=USD', 'link to website (Only FR or EN!)', 'secupress' ) . '&edd_action=add_to_cart&download_id=4811', ) ); $this->add_field( array( 'title' => __( 'Security Monitoring', 'secupress' ), 'description' => __( 'Remove the hassle of checking security yourself with our Website Security Monitoring Services.<br>We have plan starting at $39', 'secupress' ), 'name' => $this->get_field_name( 'monitoring' ), 'disabled' => true, 'type' => 'field_button', 'style' => 'primary', 'label' => __( 'Visit our page for comparing plans', 'secupress' ), 'url' => trailingslashit( set_url_scheme( SECUPRESS_WEB_MAIN, 'https' ) ) . _x( 'monitoring/?currency=USD', 'link to website (Only FR or EN!)','secupress' ), ) ); if ( secupress_has_pro() ) { $this->add_field( array( 'title' => __( 'More Websites!', 'secupress' ), 'description' => __( 'Need more websites on your license?<br>Ensure all your websites are secure.', 'secupress' ), 'name' => $this->get_field_name( 'more-websites' ), 'disabled' => true, 'type' => 'field_button', 'style' => 'primary', 'label' => __( 'Upgrade your current license plan', 'secupress' ), 'url' => trailingslashit( set_url_scheme( SECUPRESS_WEB_MAIN, 'https' ) ) . _x( 'account', 'link to website (Only FR or EN!)','secupress' ), ) ); } free/modules/services/settings.php 0000644 00000000152 15174670627 0013346 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); $this->load_plugin_settings( 'services' ); free/modules/sensitive-data/plugins/bad-file-extensions.php 0000644 00000014037 15174670627 0020133 0 ustar 00 <?php /** * Module Name: Bad File Extensions * Description: Forbid access to files with bad extension in the uploads folder. * Main Module: file_system * Author: SecuPress * Version: 1.0 */ defined( 'SECUPRESS_VERSION' ) or die( 'Something went wrong.' ); /** --------------------------------------------------------------------------------------------- */ /** ACTIVATION / DEACTIVATION =================================================================== */ /** ----------------------------------------------------------------------------------------------*/ add_action( 'secupress.modules.activation', 'secupress_bad_file_extensions_activation' ); /** * On module activation, maybe write the rules. * * @since 1.0 */ function secupress_bad_file_extensions_activation() { global $is_apache, $is_nginx, $is_iis7; // Apache. if ( $is_apache ) { $rules = secupress_bad_file_extensions_apache_rules(); } // IIS7. elseif ( $is_iis7 ) { $rules = secupress_bad_file_extensions_iis7_rules(); } // Nginx. elseif ( $is_nginx ) { $rules = secupress_bad_file_extensions_nginx_rules(); } // Not supported. else { $rules = ''; } secupress_add_module_rules_or_notice( array( 'rules' => $rules, 'marker' => 'bad_file_extensions', 'title' => __( 'Bad File Extensions', 'secupress' ), ) ); } add_action( 'secupress.modules.activate_submodule_' . basename( __FILE__, '.php' ), 'secupress_bad_file_extensions_activation_file' ); /** * On module activation, maybe write the rules. * * @since 2.0 */ function secupress_bad_file_extensions_activation_file() { secupress_bad_file_extensions_activation(); secupress_scanit_async( 'Bad_File_Extensions', 3 ); } add_action( 'secupress.modules.deactivate_submodule_' . basename( __FILE__, '.php' ), 'secupress_bad_file_extensions_deactivate' ); /** * On module deactivation, maybe remove rewrite rules from the `.htaccess`/`web.config` file. * * @since 2.0 Use secupress_scanit_async * @since 1.0 */ function secupress_bad_file_extensions_deactivate() { secupress_remove_module_rules_or_notice( 'bad_file_extensions', __( 'Bad File Extensions', 'secupress' ) ); secupress_scanit_async( 'Bad_File_Extensions', 3 ); } add_filter( 'secupress.pro.plugins.activation.write_rules', 'secupress_bad_file_extensions_plugin_activate', 10, 2 ); /** * On SecuPress Pro activation, add the rules to the list of the rules to write. * * @since 1.0 * * @param (array) $rules Other rules to write. * * @return (array) Rules to write. */ function secupress_bad_file_extensions_plugin_activate( $rules ) { global $is_apache, $is_nginx, $is_iis7; $marker = 'bad_file_extensions'; if ( $is_apache ) { $rules[ $marker ] = secupress_bad_file_extensions_apache_rules(); } elseif ( $is_iis7 ) { $rules[ $marker ] = array( 'nodes_string' => secupress_bad_file_extensions_iis7_rules() ); } elseif ( $is_nginx ) { $rules[ $marker ] = secupress_bad_file_extensions_nginx_rules(); } return $rules; } /** --------------------------------------------------------------------------------------------- */ /** TOOLS ======================================================================================= */ /** ----------------------------------------------------------------------------------------------*/ /** * Get a regex pattern matching the file extensions. * * @since 2.0 revamp * @since 1.0 * * @return (string) */ function secupress_bad_file_extensions_get_regex_pattern() { $bases = secupress_get_rewrite_bases(); $abspath = wp_normalize_path( ABSPATH ); $media_url = wp_upload_dir( null, false ); $media_url = wp_normalize_path( trailingslashit( $media_url['basedir'] ) ); $pos = strpos( $media_url, $abspath ); $media_url = substr( $media_url, $pos + strlen( $abspath ) ); $media_url = $bases['home_from'] . $media_url; $bases = secupress_get_rewrite_bases(); $abspath = wp_normalize_path( ABSPATH ); $media_url = wp_upload_dir( null, false ); $media_url = wp_normalize_path( trailingslashit( $media_url['basedir'] ) ); $media_url = str_replace( $abspath, '', $media_url ); // 'wp-content/uploads/'$media_url = $bases['home_from'] . $media_url; $extensions = secupress_bad_file_extensions_get_forbidden_extensions(); $extensions = implode( '#//#', $extensions ); $extensions = preg_quote( $extensions ); $extensions = str_replace( preg_quote( '#//#' ), '|', $extensions ); return "^{$bases['site_from']}{$media_url}.*\.($extensions)$"; } /** --------------------------------------------------------------------------------------------- */ /** RULES ======================================================================================= */ /** ----------------------------------------------------------------------------------------------*/ /** * Get rules for apache. * * @since 1.0 * * @return (string) */ function secupress_bad_file_extensions_apache_rules() { $bases = secupress_get_rewrite_bases(); $base = $bases['base']; $pattern = secupress_bad_file_extensions_get_regex_pattern(); $rules = "<IfModule mod_rewrite.c>\n"; $rules .= " RewriteEngine On\n"; $rules .= " RewriteBase $base\n"; $rules .= " RewriteRule $pattern - [R=404,L,NC]\n"; $rules .= "</IfModule>\n"; return $rules; } /** * Get rules for iis7. * * @since 1.0 * * @return (string) */ function secupress_bad_file_extensions_iis7_rules() { $marker = 'bad_file_extensions'; $spaces = str_repeat( ' ', 8 ); $pattern = secupress_bad_file_extensions_get_regex_pattern(); $rules = "<rule name=\"SecuPress $marker\" stopProcessing=\"true\">\n"; $rules .= "$spaces <match url=\"$pattern\" ignoreCase=\"true\"/>\n"; $rules .= "$spaces <action type=\"CustomResponse\" statusCode=\"404\"/>\n"; $rules .= "$spaces</rule>"; return $rules; } /** * Get rules for nginx. * * @since 1.0 * * @return (string) */ function secupress_bad_file_extensions_nginx_rules() { $marker = 'bad_file_extensions'; $pattern = secupress_bad_file_extensions_get_regex_pattern(); $rules = " server { # BEGIN SecuPress $marker location ~* $pattern { return 404; } # END SecuPress }"; return trim( $rules ); } free/modules/sensitive-data/plugins/inc/php/blackhole/warning-template.php 0000644 00000004402 15174670627 0023030 0 ustar 00 <?php /** * SecuPress Template Name: Warning Template * * @since 2.2.5.2 Julio Potier * @since 1.0 Grégory Viguier * * @see secupress_blackhole_please_click_me() */ defined( 'SECUPRESS_VERSION' ) or die( 'Something went wrong.' ); define( 'DONOTCACHEPAGE', true ); $title = __( 'Warning - Deceptive content', 'secupress' ); define( 'DONOTCACHEOBJECT', true ); define( 'DONOTCACHEDB', true ); $title = __( 'Warning - Deceptive content', 'secupress' ); ?><!DOCTYPE html> <html <?php language_attributes(); ?>> <head> <meta charset="<?php echo esc_attr( strtolower( get_bloginfo( 'charset' ) ) ); ?>" /> <title><?php echo $title; ?></title> <meta content="noindex,nofollow" name="robots" /> <meta content="initial-scale=1.0" name="viewport" /> <style> body { margin: 0; padding: 0; font-family: sans-serif; background-color: #C44; display: flex; justify-content: center; align-items: center; height: 50vh; } .warning { text-align: center; background-color: #fee; padding: 40px; border-radius: 12px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); } .warning h1 { color: #db4437; margin-bottom: 10px; } .warning p { color: #333; font-size: 16px; margin-top: 10px; line-height: 2em; } blink { animation: blinker-two 1s linear infinite; } @keyframes blinker-two { 100% { opacity: 0; } } </style> </head> <body> <div class="warning"> <h1><blink><?php echo $title; ?></blink></h1> <p><?php printf( /** Translators: 1 is a file name, 2 is a "click here" link. */ __( 'The purpose of this page is to detect robots that do not adhere to the rules outlined in the %1$s file.<br><strong>%2$s, or you will be banned from this site.</strong>', 'secupress' ), '<code>robots.txt</code>', '<a href="' . esc_url( wp_nonce_url( '', 'ban_me_please-' . date( 'ymdhi' ), 'token' ) ) . '">' . __( 'DO NOT CLICK THIS LINK', 'secupress' ) . '</a>' ); ?></p> </div> </body> </html><?php die(); free/modules/sensitive-data/plugins/directory-listing.php 0000644 00000011572 15174670627 0017747 0 ustar 00 <?php /** * Module Name: Directory Listing * Description: Disable files browsing. * Main Module: sensitive_data * Author: SecuPress * Version: 1.0 */ defined( 'SECUPRESS_VERSION' ) or die( 'Something went wrong.' ); /** --------------------------------------------------------------------------------------------- */ /** ACTIVATION / DEACTIVATION =================================================================== */ /** --------------------------------------------------------------------------------------------- */ add_action( 'secupress.modules.activation', 'secupress_directory_listing_activation' ); /** * On module activation, maybe write the rules. * * @since 1.0 */ function secupress_directory_listing_activation() { global $is_apache, $is_nginx, $is_iis7; // Apache. if ( $is_apache ) { $rules = secupress_directory_listing_apache_rules(); } // IIS7. elseif ( $is_iis7 ) { $rules = secupress_directory_listing_iis7_rules(); } // Nginx. elseif ( $is_nginx ) { $rules = secupress_directory_listing_nginx_rules(); } // Not supported. else { $rules = ''; } $edited = secupress_add_module_rules_or_notice( array( 'rules' => $rules, 'marker' => 'directory_listing', 'iis_args' => array( 'node_types' => 'directoryBrowse' ), 'title' => __( 'Directory Listing', 'secupress' ), ) ); // For Apache: maybe remove previous `Options +Indexes`. if ( ! $edited || ! $is_apache ) { return; } $file_path = secupress_get_home_path() . '.htaccess'; secupress_replace_content( $file_path, "/Options\s+\+Indexes\s*(?:\n|$)/", '' ); } add_filter( 'secupress.plugins.activation.htaccess_content_before_write_rules', 'secupress_directory_listing_activation_remove_rule' ); /** * Filter the `.htaccess` file content before add new rules on activation: maybe remove previous `Options +Indexes`. * This filter will run on Apache, before `secupress_directory_listing_activation()`. * * @since 1.0 * * @param (string) $file_content The file content. */ function secupress_directory_listing_activation_remove_rule( $file_content ) { // Maybe remove `Options +Indexes`. return preg_replace( "/Options\s+\+Indexes\s*(?:\n|$)/", '', $file_content ); } add_action( 'secupress.modules.activate_submodule_' . basename( __FILE__, '.php' ), 'secupress_directory_listing_activation_file' ); function secupress_directory_listing_activation_file() { secupress_directory_listing_activation(); secupress_scanit( 'Directory_Listing', 3 ); } add_action( 'secupress.modules.deactivate_submodule_' . basename( __FILE__, '.php' ), 'secupress_directory_listing_deactivate' ); /** * On module deactivation, maybe remove rewrite rules from the `.htaccess`/`web.config` file. * * @since 1.0 */ function secupress_directory_listing_deactivate() { secupress_remove_module_rules_or_notice( 'directory_listing', __( 'Directory Listing', 'secupress' ) ); secupress_scanit( 'Directory_Listing', 3 ); } add_filter( 'secupress.plugins.activation.write_rules', 'secupress_directory_listing_plugin_activate', 10, 2 ); /** * On SecuPress activation, add the rules to the list of the rules to write. * * @since 1.0 * * @param (array) $rules Other rules to write. * * @return (array) Rules to write. */ function secupress_directory_listing_plugin_activate( $rules ) { global $is_apache, $is_nginx, $is_iis7; $marker = 'directory_listing'; if ( $is_apache ) { $rules[ $marker ] = secupress_directory_listing_apache_rules(); } elseif ( $is_iis7 ) { $rules[ $marker ] = array( 'nodes_string' => secupress_directory_listing_iis7_rules(), 'node_types' => 'directoryBrowse' ); } elseif ( $is_nginx ) { $rules[ $marker ] = secupress_directory_listing_nginx_rules(); } return $rules; } /** --------------------------------------------------------------------------------------------- */ /** RULES ======================================================================================= */ /** --------------------------------------------------------------------------------------------- */ /** * Directory Listing: get rules for apache. * * @since 1.0 * * @return (string) */ function secupress_directory_listing_apache_rules() { $rules = "<IfModule mod_autoindex.c>\n"; $rules .= " Options -Indexes\n"; $rules .= "</IfModule>\n"; return $rules; } /** * Directory Listing: get rules for iis7. * * @since 1.0 * * @return (string) */ function secupress_directory_listing_iis7_rules() { $marker = 'directory_listing'; $rules = '<directoryBrowse name="SecuPress ' . $marker . '" enabled="false" showFlags=""/>'; return $rules; } /** * Directory Listing: get rules for nginx. * * @since 1.0 * * @return (string) */ function secupress_directory_listing_nginx_rules() { $marker = 'directory_listing'; $bases = secupress_get_rewrite_bases(); $base = $bases['base']; $rules = " server { # BEGIN SecuPress $marker location $base { autoindex off; } # END SecuPress }"; return trim( $rules ); } free/modules/sensitive-data/plugins/blackhole.php 0000644 00000005352 15174670627 0016217 0 ustar 00 <?php /** * Module Name: Blackhole * Description: Catch bots that don't respect your <code>robots.txt</code> rules. * Main Module: sensitive_data * Author: SecuPress * Version: 2.3.19 */ defined( 'SECUPRESS_VERSION' ) or die( 'Something went wrong.' ); add_action( 'secupress.modules.activate_submodule_' . basename( __FILE__, '.php' ), 'secupress_blackhole_activate_write_robotstxt' ); add_action( 'secupress.modules.activation', 'secupress_blackhole_activate_write_robotstxt' ); /** * Add our content to the robots.txt if does exist * * @author Julio Potier * @since 2.2.6 */ function secupress_blackhole_activate_write_robotstxt() { $filesystem = secupress_get_filesystem(); $filename = ABSPATH . 'robots.txt'; if ( ! file_exists( $filename ) ) { // We do not create it, the hook is enough return; } $contents = $filesystem->get_contents( $filename ); $contents = secupress_blackhole_robotstxt_content( $contents ); $filesystem->put_contents( $filename, $contents ); } add_action( 'secupress.modules.deactivate_submodule_' . basename( __FILE__, '.php' ), 'secupress_blackhole_deactivate_write_robotstxt' ); add_action( 'secupress.modules.deactivation', 'secupress_blackhole_deactivate_write_robotstxt' ); /** * Add our content to the robots.txt if does exist * * @author Julio Potier * @since 2.2.6 */ function secupress_blackhole_deactivate_write_robotstxt() { $filesystem = secupress_get_filesystem(); $filename = ABSPATH . 'robots.txt'; if ( ! file_exists( $filename ) ) { // We do not create it, the hook it enough return; } $contents = $filesystem->get_contents( $filename ); $dirname = secupress_get_hashed_folder_name( basename( __FILE__, '.php' ) ); if ( false !== strpos( $contents, "User-agent: *\nDisallow: $dirname\n" ) ) { $contents = str_replace( "User-agent: *\nDisallow: $dirname\n", "User-agent: *\n", $contents ); $filesystem->put_contents( $filename, $contents ); } } add_filter( 'robots_txt', 'secupress_blackhole_robotstxt_content', 20 ); /** * Add forbidden URI in `robots.txt` file. * * @since 2.3.19 remove the conditions because if this is a real file, we can't run that, no big deal, so remove param $forced * @since 2.2.6 Add the rule on line 1 if not present * @author Julio Potier * * @since 1.0 * @author Grégory Viguier * * @param (string) $output File content. * * @return (string) File content. */ function secupress_blackhole_robotstxt_content( $output ) { $dirname = secupress_get_hashed_folder_name( basename( __FILE__, '.php' ) ); if ( false !== strpos( $output, "User-agent: *\n" ) ) { $output = str_replace( "User-agent: *\n", "User-agent: *\nDisallow: $dirname\n", $output ); } else { $output = "User-agent: *\nDisallow: $dirname\n\n" . $output; } return $output; } free/modules/sensitive-data/plugins/php-easter-egg.php 0000644 00000010677 15174670627 0017111 0 ustar 00 <?php /** * Module Name: PHP Disclosure * Description: Protect against PHP Easter Egg. * Main Module: sensitive_data * Author: SecuPress * Version: 1.0 */ defined( 'SECUPRESS_VERSION' ) or die( 'Something went wrong.' ); /** --------------------------------------------------------------------------------------------- */ /** ACTIVATION / DEACTIVATION =================================================================== */ /** --------------------------------------------------------------------------------------------- */ add_action( 'secupress.modules.activation', 'secupress_php_disclosure_activation' ); /** * On module activation, maybe write the rules. * * @since 1.0 */ function secupress_php_disclosure_activation() { global $is_apache, $is_nginx, $is_iis7; // Apache. if ( $is_apache ) { $rules = secupress_php_disclosure_apache_rules(); } // IIS7. elseif ( $is_iis7 ) { $rules = secupress_php_disclosure_iis7_rules(); } // Nginx. elseif ( $is_nginx ) { $rules = secupress_php_disclosure_nginx_rules(); } // Not supported. else { $rules = ''; } secupress_add_module_rules_or_notice( array( 'rules' => $rules, 'marker' => 'php_disclosure', 'title' => __( 'PHP Disclosure', 'secupress' ), ) ); } add_action( 'secupress.modules.activate_submodule_' . basename( __FILE__, '.php' ), 'secupress_php_disclosure_activation_file' ); /** * On module de/activation, rescan. * * @since 2.0 */ function secupress_php_disclosure_activation_file() { secupress_php_disclosure_activation(); secupress_scanit( 'PHP_Disclosure', 3 ); } add_action( 'secupress.modules.deactivate_submodule_' . basename( __FILE__, '.php' ), 'secupress_php_disclosure_deactivate' ); /** * On module deactivation, maybe remove rewrite rules from the `.htaccess`/`web.config` file. * * @since 1.0 */ function secupress_php_disclosure_deactivate() { secupress_remove_module_rules_or_notice( 'php_disclosure', __( 'PHP Disclosure', 'secupress' ) ); secupress_scanit( 'PHP_Disclosure', 3 ); } add_filter( 'secupress.plugins.activation.write_rules', 'secupress_php_disclosure_plugin_activate', 10, 2 ); /** * On SecuPress activation, add the rules to the list of the rules to write. * * @since 1.0 * * @param (array) $rules Other rules to write. * * @return (array) Rules to write. */ function secupress_php_disclosure_plugin_activate( $rules ) { global $is_apache, $is_nginx, $is_iis7; $marker = 'php_disclosure'; if ( $is_apache ) { $rules[ $marker ] = secupress_php_disclosure_apache_rules(); } elseif ( $is_iis7 ) { $rules[ $marker ] = array( 'nodes_string' => secupress_php_disclosure_iis7_rules() ); } elseif ( $is_nginx ) { $rules[ $marker ] = secupress_php_disclosure_nginx_rules(); } return $rules; } /** --------------------------------------------------------------------------------------------- */ /** RULES ======================================================================================= */ /** --------------------------------------------------------------------------------------------- */ /** * PHP Disclosure: get rules for apache. * * @since 1.0 * * @return (string) */ function secupress_php_disclosure_apache_rules() { $rules = "<IfModule mod_rewrite.c>\n"; $rules .= " RewriteEngine On\n"; $rules .= " RewriteCond %{QUERY_STRING} \=PHP[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12} [NC]\n"; $rules .= " RewriteRule .* - [F]\n"; $rules .= "</IfModule>\n"; return $rules; } /** * PHP Disclosure: get rules for iis7. * * @since 1.0 * * @return (string) */ function secupress_php_disclosure_iis7_rules() { $marker = 'php_disclosure'; $spaces = str_repeat( ' ', 8 ); $rules = "<rule name=\"SecuPress $marker\" stopProcessing=\"true\">\n"; $rules .= "$spaces <match url=\".*\"/>\n"; $rules .= "$spaces <conditions>\n"; $rules .= "$spaces <add input=\"{URL}\" pattern=\"\=PHP[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\" ignoreCase=\"true\"/>\n"; $rules .= "$spaces </conditions>\n"; $rules .= "$spaces <action type=\"AbortRequest\"/>\n"; $rules .= "$spaces</rule>"; return $rules; } /** * PHP Disclosure: get rules for nginx. * * @since 1.0 * * @return (string) */ function secupress_php_disclosure_nginx_rules() { $marker = 'php_disclosure'; $rules = " server { # BEGIN SecuPress $marker location / { if ( \$query_string ~* \"\=PHP[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\" ) { deny all; } } # END SecuPress }"; return trim( $rules ); } free/modules/sensitive-data/plugins/xmlrpc.php 0000644 00000007116 15174670627 0015600 0 ustar 00 <?php /** * Module Name: Disable XML-RPC * Description: Disable totally or partially XML-RPC. * Main Module: sensitive_data * Author: SecuPress * Version: 1.0 */ defined( 'SECUPRESS_VERSION' ) or die( 'Something went wrong.' ); add_action( 'secupress.plugins.loaded', 'secupress_xmlrpc_disable_rpc' ); /** * Disable XML-RPC by launching and removing hooks. * * @since 1.0 */ function secupress_xmlrpc_disable_rpc() { $options = secupress_get_module_option( 'wp-endpoints_xmlrpc', array(), 'sensitive-data' ); $options = array_flip( $options ); $is_xmlrpc = defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST; // Disable the whole XML-RPC feature. if ( isset( $options['block-all'] ) ) { // Well, why not killing everything here? if ( $is_xmlrpc ) { secupress_die( __( 'XML-RPC services are disabled on this site.', 'secupress' ), __( 'XML-RPC is disabled', 'secupress' ), [ 'response' => 403, 'force_die' => true, 'attack_type' => 'xmlrpc' ] ); } // Disable XML-RPC, just for decorum. add_filter( 'xmlrpc_enabled', '__return_false' ); // Remove RSD link from the page header. remove_action( 'wp_head', 'rsd_link' ); // Remove Pingback header. add_filter( 'wp_headers', 'secupress_xmlrpc_remove_pingback_header', 11 ); // Kill the pingback URL. add_filter( 'bloginfo_url', 'secupress_xmlrpc_kill_pingback_url', 10, 2 ); } elseif ( isset( $options['block-multi'] ) && $is_xmlrpc ) { // Disable the multiple authentications. add_filter( 'xmlrpc_methods', 'secupress_xmlrpc_remove_multicall_methods', 0 ); add_filter( 'authenticate', 'secupress_xmlrpc_block_multiauth_attempts', 0, 3 ); } } /** * Filter the HTTP headers before they're sent to the browser. * Remove the `X-Pingback` header. * * @since 1.0 * * @param (array) $headers List of headers. * * @return (array) */ function secupress_xmlrpc_remove_pingback_header( $headers ) { unset( $headers['X-Pingback'] ); return $headers; } /** * Filter the URL returned by get_bloginfo(). * Disable the pingback URL. * * @since 1.0 * * @param (mixed) $output The URL returned by bloginfo(). * @param (mixed) $show Type of information requested. * * @return (mixed) Return false if it's the pingback URL. */ function secupress_xmlrpc_kill_pingback_url( $output, $show ) { return 'pingback_url' === $show ? false : $output; } /** * Filter the methods exposed by the XML-RPC server. * Remove system multicall. * * @since 1.0 * * @param (array) $methods An array of XML-RPC methods. * * @return (array) */ function secupress_xmlrpc_remove_multicall_methods( $methods ) { unset( $methods['system.multicall'], $methods['system.listMethods'], $methods['system.getCapabilities'] ); return $methods; } /** * Filter whether a set of user login credentials are valid. * Disable XML-RPC multiauth. * * @since 1.0 * * @param (null|object) $user WP_User if the user is authenticated. WP_Error or null otherwise. * @param (string) $username User login. * @param (string) $password User password. * * @return (null|object) */ function secupress_xmlrpc_block_multiauth_attempts( $user, $username, #[\SensitiveParameter] $password ) { static $credentials; if ( empty( $credentials ) ) { $credentials = compact( 'username', 'password' ); return $user; } if ( 0 === strcmp( $username, $credentials['username'] ) && 0 === strcmp( $password, $credentials['password'] ) ) { return $user; } secupress_die( __( 'XML-RPC services are disabled on this site.', 'secupress' ), __( 'XML-RPC is disabled', 'secupress' ), [ 'response' => 403, 'attack_type' => 'xmlrpc' ] ); } free/modules/sensitive-data/plugins/bad-url-access.php 0000644 00000013115 15174670627 0017054 0 ustar 00 <?php /** * Module Name: Bad URL Access * Description: Deny access to some sensitive files. * Main Module: sensitive_data * Author: SecuPress * Version: 2.3.13 */ defined( 'SECUPRESS_VERSION' ) or die( 'Something went wrong.' ); /** --------------------------------------------------------------------------------------------- */ /** ACTIVATION / DEACTIVATION =================================================================== */ /** --------------------------------------------------------------------------------------------- */ add_action( 'secupress.modules.activation', 'secupress_bad_url_access_activation' ); /** * On module activation, maybe write the rules. * * @since 1.0 * @since 1.0.2 Return a boolean. * @author Grégory Viguier * * @return (bool) True if rules have been successfully written. False otherwise. */ function secupress_bad_url_access_activation() { global $is_apache, $is_nginx, $is_iis7; switch( true ) { case $is_apache: $rules = secupress_bad_url_access_apache_rules(); break; case $is_nginx: $rules = secupress_bad_url_access_nginx_rules(); break; case $is_iis7: $rules = secupress_bad_url_access_iis7_rules(); break; default: $rules = ''; break; } return secupress_add_module_rules_or_notice( array( 'rules' => $rules, 'marker' => 'bad_url_access', 'title' => __( 'Bad URL Access', 'secupress' ), ) ); } add_action( 'secupress.modules.activate_submodule_' . basename( __FILE__, '.php' ), 'secupress_bad_url_access_activate' ); /** * On module de/activation, rescan. * * @since 2.0 * @author Julio Potier */ function secupress_bad_url_access_activate() { secupress_bad_url_access_activation(); secupress_scanit( 'Bad_URL_Access', 3 ); } add_action( 'secupress.modules.deactivate_submodule_' . basename( __FILE__, '.php' ), 'secupress_bad_url_access_deactivate' ); /** * On module deactivation, maybe remove rewrite rules from the `.htaccess`/`web.config` file. * * @since 1.0 * @author Grégory Viguier */ function secupress_bad_url_access_deactivate() { secupress_remove_module_rules_or_notice( 'bad_url_access', __( 'Bad URL Access', 'secupress' ) ); secupress_scanit( 'Bad_URL_Access', 3 ); } add_filter( 'secupress.plugins.activation.write_rules', 'secupress_bad_url_access_plugin_activate', 10, 2 ); /** * On SecuPress activation, add the rules to the list of the rules to write. * * @since 1.0 * @author Grégory Viguier * * @param (array) $rules Other rules to write. * @return (array) Rules to write. */ function secupress_bad_url_access_plugin_activate( $rules ) { global $is_apache, $is_nginx, $is_iis7; $marker = 'bad_url_access'; switch( true ) { case $is_apache: $rules[ $marker ] = secupress_bad_url_access_apache_rules(); break; case $is_nginx: $rules[ $marker ] = secupress_bad_url_access_nginx_rules(); break; case $is_iis7: $rules[ $marker ] = [ 'nodes_string' => secupress_bad_url_access_iis7_rules() ]; break; default: $rules[ $marker ] = ''; } return $rules; } /** --------------------------------------------------------------------------------------------- */ /** TOOLS ======================================================================================= */ /** --------------------------------------------------------------------------------------------- */ /** * Get rules for apache. * * @since 1.0 * @author Grégory Viguier * * @return (string) */ function secupress_bad_url_access_apache_rules() { $pattern = secupress_bad_url_access_get_regex_pattern(); $bases = secupress_get_rewrite_bases(); $base = $bases['base']; $site_from = $bases['site_from']; // Trigger a 404 error, because forbidding access to a file is nice, but making it also invisible is more fun :). $rules = "<IfModule mod_rewrite.c>\n"; $rules .= " RewriteEngine On\n"; $rules .= " RewriteBase $base\n"; $rules .= " RewriteCond %{REQUEST_URI} !{$site_from}wp-includes/js/tinymce/wp-tinymce\.php$\n"; $rules .= " RewriteRule $pattern [R=404,L,NC]\n"; $rules .= "</IfModule>\n"; return $rules; } /** * Get rules for nginx. * * @since 1.0 * @author Grégory Viguier * * @return (string) */ function secupress_bad_url_access_nginx_rules() { $marker = 'bad_url_access'; $bases = secupress_get_rewrite_bases(); // We add the TinyMCE file directly in the pattern. $pattern = '^(' . $bases['home_from'] . 'php\.ini|' . $bases['site_from'] . 'wp-config\.php|' . $bases['site_from'] . WPINC . '/((?:(?!js/tinymce/wp-tinymce).)+)\.php|' . $bases['site_from'] . 'wp-admin/(admin-functions|install|menu-header|setup-config|([^/]+/)?menu|upgrade-functions|includes/.+)\.php)$'; $rules = " server { # BEGIN SecuPress $marker location ~* $pattern { return 404; } # END SecuPress }"; return trim( $rules ); } /** * Get rules for iis7. * * @since 1.0 * @author Grégory Viguier * * @return (string) */ function secupress_bad_url_access_iis7_rules() { $marker = 'bad_url_access'; $patterns = secupress_bad_url_access_get_regex_pattern(); $bases = secupress_get_rewrite_bases(); $site_from = '/' . $bases['site_from']; $spaces = str_repeat( ' ', 8 ); $rules = "<rule name=\"SecuPress $marker\" stopProcessing=\"true\">\n"; $rules .= "$spaces <match url=\"$pattern\"/ ignoreCase=\"true\">\n"; $rules .= "$spaces <conditions>\n"; $rules .= "$spaces <add input=\"{REQUEST_URI}\" pattern=\"{$site_from}wp-includes/js/tinymce/wp-tinymce\.php$\" negate=\"true\"/>\n"; $rules .= "$spaces </conditions>\n"; $rules .= "$spaces <action type=\"CustomResponse\" statusCode=\"404\"/>\n"; $rules .= "$spaces</rule>"; return trim( $rules ); } free/modules/sensitive-data/tools.php 0000644 00000040223 15174670627 0013746 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** * Get file extensions that are forbidden in the uploads folder. * * @since 1.0 * @see http://www.file-extensions.org/filetype/extension/name/dangerous-malicious-files * * @return (array) */ function secupress_bad_file_extensions_get_forbidden_extensions() { // Build a regex pattern with the allowed extensions. $allowed = wp_get_mime_types(); $allowed = array_keys( $allowed ); $allowed = implode( '|', $allowed ); $allowed = "#,($allowed),#i"; $exts = array( '.9', '73i87a', '386', 'aaa', 'abc', 'aepl', 'aru', 'atm', 'aut', 'bat', 'bhx', 'bin', 'bkd', 'blf', 'bll', 'bmw', 'boo', 'bps', 'bqf', 'breaking_bad', 'btc', 'buk', 'bup', 'bxz', 'cc', 'ccc', 'ce0', 'ceo', 'cfxxe', 'chm', 'cih', 'cla', 'class', 'cmd', 'com', 'coverton', 'cpl', 'crinf', 'crjoker', 'crypt', 'crypted', 'cryptolocker', 'cryptowall', 'ctbl', 'cxq', 'cyw', 'czvxce', 'darkness', 'dbd', 'delf', 'dev', 'dlb', 'dli', 'dll', 'dllx', 'dom', 'drv', 'dx', 'dxz', 'dyv', 'dyz', 'ecc', 'enciphered', 'encrypt', 'encrypted', 'enigma', 'exe', 'exe1', 'exe_renamed', 'exx', 'ezt', 'ezz', 'fag', 'fjl', 'fnr', 'fuj', 'fun', 'good', 'gzquar', 'ha3', 'hlp', 'hlw', 'hsq', 'hts', 'iva', 'iws', 'jar', 'js', 'kcd', 'kernel_complete', 'kernel_pid', 'kernel_time', 'keybtc@inbox_com', 'kimcilware', 'kkk', 'kraken', 'lechiffre', 'let', 'lik', 'lkh', 'lnk', 'locked', 'locky', 'lok', 'lol!', 'lpaq5', 'magic', 'mfu', 'micro', 'mjg', 'mjz', 'nls', 'oar', 'ocx', 'osa', 'ozd', 'p5tkjw', 'pcx', 'pdcr', 'pgm', 'php', 'php2', 'php3', 'pid', 'pif', 'plc', 'poar2w', 'pr', 'pzdc', 'qit', 'qrn', 'r5a', 'rdm', 'rhk', 'rna', 'rokku', 'rrk', 'rsc_tmp', 's7p', 'scr', 'scr', 'shs', 'ska', 'smm', 'smtmp', 'sop', 'spam', 'ssy', 'surprise', 'swf', 'sys', 'tko', 'tps', 'tsa', 'tti', 'ttt', 'txs', 'upa', 'uzy', 'vb', 'vba', 'vbe', 'vbs', 'vbx', 'vexe', 'vxd', 'vzr', 'wlpginstall', 'wmf', 'ws', 'wsc', 'wsf', 'wsh', 'wss', 'xdu', 'xir', 'xlm', 'xlv', 'xnt', 'xnxx', 'xtbl', 'xxx', 'xyz', 'zix', 'zvz', 'zzz', ); // Remove the allowed extensions from the forbidden ones. $exts = implode( ',', $exts ); $exts = ",$exts,"; $exts = preg_replace( $allowed, ',', $exts ); $exts = trim( $exts, ',' ); $exts = explode( ',', $exts ); /** * Filter the forbidden file extensions. * * @since 1.0 * * @param (array) $all_exts The file extensions. */ $out = apply_filters( 'secupress.plugin.bad_file_extensions.forbidden_extenstions', $exts ); /** * Filter the forbidden file extensions. * * @since 2.3.13 * * @param (array) $all_exts The file extensions. */ $out = apply_filters( 'secupress.plugin.bad_file_extensions.forbidden_extensions', $out ); // typo... $out = array_filter( $out ); return $out ? $out : $exts; } /** * Tell if a `robots.txt` file is in use. * WordPress does not create a rewrite rule for the `robots.txt` file if it is installed in a folder. * If a constant `SECUPRESS_FORCE_ROBOTS_TXT` is defined to `true`, the field will be available. * * @since 1.0 * @author Grégory Viguier * * @see `WP_Rewrite::rewrite_rules()`. * * @return (bool) */ function secupress_blackhole_is_robots_txt_enabled() { $home_path = wp_parse_url( home_url() ); return empty( $home_path['path'] ) || '/' === $home_path['path'] || defined( 'SECUPRESS_author_base_FORCE_ROBOTS_TXT' ) && SECUPRESS_FORCE_ROBOTS_TXT; } /** * Get a regex pattern matching the files. * * @since 2.3.17 $rules_mode param * @since 2.2.6 Invert the behaviour * @author Julio Potier * * @since 1.0.3 * @author Grégory Viguier * * @param (string) $rules_mode * @return (string) */ function secupress_bad_url_access_get_regex_pattern( $rules_mode = 'disallowed' ) { switch( $rules_mode ) { case 'allowed': $patterns = []; $patterns['root'] = '(index|wp-activate|wp-comments-post|wp-cron|wp-links-opml|wp-load|wp-login|wp-mail|wp-pass|wp-signup|wp-trackback|xmlrpc)\.php'; $patterns['wp-admin'] = 'wp-admin/(about|admin-ajax|admin-footer|admin-post|admin|async-upload|authorize-application|comment|contribute|credits|customize|edit-comments|edit-form-advanced|edit-form-blocks|edit-form-comment|edit-link-form|edit-tag-form|edit-tags|edit|erase-personal-data|export-personal-data|export|freedoms|import|index|link-add|link-manager|link|load-scripts|load-styles|maint/repair|media-new|media-upload|media|moderation|ms-admin|ms-delete-site|ms-edit|ms-options|ms-sites|ms-themes|ms-upgrade-network|ms-users|my-sites|nav-menus|network/about|network/admin|network/contribute|network/credits|network/edit|network/freedoms|network/index|network/plugin-editor|network/plugin-install|network/plugins|network/privacy|network/profile|network/settings|network/setup|network/site-info|network/site-new|network/site-settings|network/site-themes|network/site-users|network/sites|network/theme-editor|network/theme-install|network/themes|network/update-core|network/update|network/upgrade|network/user-edit|network/user-new|network/users|network|options-discussion|options-general|options-media|options-permalink|options-privacy|options-reading|options-writing|options|plugin-editor|plugin-install|plugins|post-new|post|press-this|privacy-policy-guide|privacy|profile|revision|site-editor|site-health|term|theme-editor|theme-install|themes|tools|update-core|update|upgrade|upload|user/about|user/admin|user/credits|user/freedoms|user/index|user/privacy|user/profile|user/user-edit|user-edit|user-new|users|widgets-form-blocks|widgets-form|widgets)\.php'; $patterns['wp-includes'] = 'wp-includes/js/tinymce/wp-tinymce\.php'; break; default: // legacy case 'disallowed': $bases = secupress_get_rewrite_bases(); $patterns = '^(' . $bases['home_from'] . 'php\.ini|' . $bases['site_from'] . 'wp-config\.php|' . $bases['site_from'] . WPINC . '/.+\.php|' . $bases['site_from'] . 'wp-admin/(admin-functions|install|menu-header|setup-config|([^/]+/)?menu|upgrade-functions|includes/.+)\.php)$'; break; } /** * Filter the URLs allowed to be reached * * @since 2.6 Add "rules_mode" and bring down the hook later * @since 2.2.6 * * @param (array) $patterns * @param (string) $rules_mode */ $patterns = apply_filters( 'secupress.plugins.bad_url_access.regex_pattern', $patterns, $rules_mode ); return $patterns; } /** * Used in an array_filter to only keep the local ones. * * @since 2.2.6 * @author Julio Potier * * @param (string) $url * * @return (bool) */ function _secupress_bad_url_access_allowed_url_filter( $url ) { $url = wp_http_validate_url( trim( $url ) ); return 0 === strpos( $url, home_url() ); } /** * * * @since 2.2.6 * @author Julio Potier * * @param (array) $urls * * @return (string) $_urls */ function _secupress_bad_url_access_allowed_urls_sanitize( $urls ) { $_urls = []; if ( ! is_array( $urls ) ) { $urls = array_map( 'trim', explode( "\n", $urls ) ); } foreach ( $urls as $url ) { $joker = strpos( $url, '*' ); $url = explode( '?', $url ); $url = rtrim( reset( $url ), '*' ); $path = ABSPATH . str_replace( home_url( '/' ), '', $url ); if ( ! $joker && is_dir( $path ) && file_exists( $path . '/index.php' ) && ! in_array( $url . '/index.php', $urls ) ) { $_urls[] = $url . 'index.php'; } $is_index = substr( $url, -10 ) === '/index.php'; if ( is_file( $path ) && $is_index && ! in_array( str_replace( '/index.php', '/', $url ), $urls ) && ! in_array( str_replace( '/index.php', '/*', $url ), $urls ) ) { $_urls[] = str_replace( '/index.php', '', $url ) . '/'; } $_urls[] = $url . ( $joker ? '*' : '' ); } $_urls = array_filter( array_flip( array_flip( $_urls ) ) ); return implode( "\n", $_urls ); } /** * Sort the given URLs in different cat for htaccess protection * * @since 2.2.6 * @author Julio Potier * * @return (array) $_urls */ function secupress_bad_url_access_sort_urls() { global $contentprotectbadurlaccessallowedurls; if ( ! is_null( $contentprotectbadurlaccessallowedurls ) && false !== $contentprotectbadurlaccessallowedurls ) { $urls = $contentprotectbadurlaccessallowedurls; } else { $urls = secupress_get_module_option( 'content-protect_bad-url-access_allowed-urls', [], 'sensitive-data' ); } if ( ! is_array( $urls ) ) { $urls = array_map( 'trim', explode( "\n", $urls ) ); } if ( empty( $urls ) ) { return [ 'files' => [], 'content' => [], 'folders' => [] ]; } $_urls = [ 'files' => [], 'content' => [], 'folders' => [] ]; $_content = secupress_server_is_ssl() ? str_replace( 'http://', 'https://', WP_CONTENT_URL ) : WP_CONTENT_URL; foreach ( $urls as $url ) { $joker = strpos( $url, '*' ); $url = rtrim( $url, '*' ); $path = realpath( ABSPATH . str_replace( home_url( '/' ), '', $url ) ); if ( is_file( $path ) ) { $_urls['files'][] = '/' . $url; if ( 0 === strpos( $url, $_content ) ) { $_urls['content'][] = $url; } continue; } if ( is_dir( $path ) ) { $_urls['folders'][] = $url; if ( 0 === strpos( $url, $_content ) ) { $_urls['content'][] = $url; } if ( $joker ) { $_urls['files'][] = trailingslashit( $url ) . '.*'; } continue; } } $_urls = apply_filters( 'secupress.bad-url-access.urls', $_urls ); return $_urls; } add_filter( 'secupress.bad-url-access.urls', 'secupress_bad_url_access_add_third_party_urls' ); /** * Add some possible URLs needed by plugins * * @since 2.3.13 * @author Julio Potier * * @param (array) $urls * * @return (array) $urls **/ function secupress_bad_url_access_add_third_party_urls( $urls ) { // MEMBERPRESS if ( is_plugin_active( 'memberpress/memberpress.php' ) ) { $urls['files'][] = '/' . plugins_url( 'memberpress/lock.php' ); } return $urls; } /** * Set the author page base and flush the rules * * @since 2.2.6 * @author Grégory Viguier, Julio Potier * * @param (string) $author_base */ function secupress_set_author_base( $author_base = '' ) { global $wp_rewrite; if ( trim( secupress_get_module_option( 'author_base', 'author', 'sensitive-data' ), '/' ) === $author_base ) { return; } if ( $author_base ) { $wp_rewrite->author_base = $author_base; } else { $wp_rewrite->author_base = 'author'; } $wp_rewrite->init(); flush_rewrite_rules(); } /** * Return the actual author base. * * @since 2.2.6 * @author Grégory Viguier * * @return (string) $author_base */ function secupress_get_author_base() { global $wp_rewrite; $front = ! empty( $wp_rewrite ) ? trim( $wp_rewrite->front, '/' ) . '/' : 'blog/'; $author_base = trim( secupress_get_module_option( 'wp-endpoints_author_base', 'author', 'sensitive-data' ), '/' ); $author_base = sanitize_title( $author_base ); $author_base = $author_base && trim( $front, '/' ) !== $author_base ? $author_base : 'author'; return $author_base; } add_action( 'init', 'secupress_author_base_init' ); /** * Set the actual author base on init. * * @since 2.2.6 * @author Grégory Viguier */ function secupress_author_base_init() { global $wp_rewrite; if ( ! $wp_rewrite || ! is_object( $wp_rewrite ) ) { return; } $wp_rewrite->author_base = secupress_get_author_base(); } add_action( 'show_user_profile', 'secupress_author_base_edit_user_options' ); add_action( 'edit_user_profile', 'secupress_author_base_edit_user_options' ); /** * Add the field. * * @since 2.2.6 * @author Grégory Viguier, Julio Potier */ function secupress_author_base_edit_user_options() { global $user_id, $wp_rewrite; $user_id = isset( $user_id ) ? (int) $user_id : 0; if ( ! ( $userdata = get_userdata( $user_id ) ) ) { return; } $def_user_nicename = sanitize_title( $userdata->display_name ); $blog_prefix = is_multisite() && ! is_subdomain_install() && is_main_site() ? '/blog/' : '/'; $author_base = $wp_rewrite->author_base; ?> <table class="form-table"> <tr> <th><label for="user_nicename"><?php _e( 'Profile URL Slug', 'secupress' ); ?></label></th> <td> <?php echo $blog_prefix . $author_base . '/'; ?> <input id="user_nicename" name="user_nicename" class="regular-text code" type="text" value="<?php echo esc_attr( sanitize_title( $userdata->user_nicename, $def_user_nicename ) ); ?>"/> <span class="description"><?php printf( __( 'Leave empty for default value: %s', 'secupress' ), secupress_tag_me( $def_user_nicename, 'strong' ) ); ?></span> </td> </tr> </table> <?php } add_action( 'personal_options_update', 'secupress_author_base_save_user_options' ); add_action( 'edit_user_profile_update', 'secupress_author_base_save_user_options' ); /** * Save the user nicename and display error notices. * * @since 2.2.6 * @author Grégory Viguier, Julio Potier */ function secupress_author_base_save_user_options() { if ( empty( $_POST['user_id'] ) || ! isset( $_POST['user_nicename'] ) ) { return; } $user_id = (int) $_POST['user_id']; check_admin_referer( 'update-user_' . $user_id ); if ( ! ( $userdata = get_userdata( $user_id ) ) ) { return; } $def_user_nicename = sanitize_title( $userdata->user_login ); $new_nicename = sanitize_title( $_POST['user_nicename'], $def_user_nicename ); if ( 0 === strcmp( $new_nicename, $userdata->user_nicename ) ) { return; } // "wp_admin_notice_markup" hook is 6.4 if ( secupress_get_user_by( $new_nicename ) ) { if ( secupress_wp_version_is( '6.4' ) ) { add_action( 'user_profile_update_errors', 'secupress_author_base_user_add_fake_error' ); } secupress_add_transient_notice( sprintf( __( 'Sorry, the slug %s is already in use!', 'secupress' ), secupress_tag_me( esc_html( $new_nicename ), 'strong' ) ), 'error', '', 'exist' ); } else { $updated = wp_update_user( array( 'ID' => $user_id, 'user_nicename' => $new_nicename, ) ); if ( ! $updated ) { if ( secupress_wp_version_is( '6.4' ) ) { add_action( 'user_profile_update_errors', 'secupress_author_base_user_add_fake_error' ); } secupress_add_transient_notice( __( 'Unexpected issue updating the author profile URL slug. Please try again.', 'secupress' ), 'error', '', 'exist' ); } } function secupress_author_base_user_add_fake_error( $errors ) { $errors->add( '###SECUPRESS-DO-NOT-SHOW-ME###', '###SECUPRESS-DO-NOT-SHOW-ME###' ); } add_filter( 'wp_admin_notice_markup', 'secupress_author_base_remove_fake_error', 10, 2 ); // WP 6.4 /** * Maybe remove the markup, the error is set just to not show the "Profile updated" msg * * @since 2.3.19 * @author Julio Potier * * @param (string) $markup * @param (string) $message * * @return (string) $markup **/ function secupress_author_base_remove_fake_error( $markup, $message ) { if ( strpos( $message, '###SECUPRESS-DO-NOT-SHOW-ME###' ) !== false ) { return ''; } return $markup; } } add_filter( 'template_include', 'secupress_blackhole_please_click_me', 1 ); /** * Use a custom template for our trap. * * @since 2.4.1 Moved here so we can use it for other cases * @since 2.2.6 Manage the ban from here with a nonce now * @author Julio Potier * * @since 1.0 * @author Grégory Viguier * * @param (string) $template Template path. * * @return (string) Template path. */ function secupress_blackhole_please_click_me( $template ) { if ( is_user_logged_in() ) { return $template; } $url = trailingslashit( secupress_get_current_url() ); $dirname = secupress_get_hashed_folder_name( basename( __FILE__, '.php' ) ); if ( isset( $_REQUEST['token'] ) && wp_verify_nonce( $_REQUEST['token'], 'ban_me_please-' . date( 'ymdhi' ) ) ) { $ip = secupress_get_ip( 'REMOTE_ADDR' ); $ban_ips = get_site_option( SECUPRESS_BAN_IP ); if ( ! is_array( $ban_ips ) ) { $ban_ips = array(); } $ban_ips[ $ip ] = time() + MONTH_IN_SECONDS; update_site_option( SECUPRESS_BAN_IP, $ban_ips ); /* This hook is documented in /inc/functions/admin.php */ do_action( 'secupress.ban.ip_banned', $ip, $ban_ips ); switch ( $_SERVER['REQUEST_METHOD'] ) { case 'GET': secupress_log_attack( 'bad_robots' ); break; case 'POST': secupress_log_attack( 'honeypot' ); break; } wp_die( 'Something went wrong.' ); // Do not use secupress_die() here. } if ( substr( $url, - strlen( $dirname ) ) === $dirname ) { add_filter( 'nonce_user_logged_out', 'secupress_modify_userid_for_nonces' ); return dirname( __FILE__ ) . '/inc/php/blackhole/warning-template.php'; } return $template; } free/modules/sensitive-data/callbacks.php 0000644 00000021470 15174670627 0014530 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** --------------------------------------------------------------------------------------------- */ /** ON MODULE SETTINGS SAVE ===================================================================== */ /** --------------------------------------------------------------------------------------------- */ /** * Callback to filter, sanitize, validate and de/activate submodules. * * @since 1.0 * * @param (array) $settings The module settings. * * @return (array) The sanitized and validated settings. */ function secupress_sensitive_data_settings_callback( $settings ) { $modulenow = 'sensitive-data'; $activate = secupress_get_submodule_activations( $modulenow ); $settings = $settings && is_array( $settings ) ? $settings : array(); if ( isset( $settings['sanitized'] ) ) { return $settings; } $settings['sanitized'] = 1; /* * Each submodule has its own sanitization function. * The `$settings` parameter is passed by reference. */ // Content Protection. secupress_content_protection_settings_callback( $modulenow, $settings, $activate ); // WordPress Endpoints. secupress_wp_endpoints_settings_callback( $modulenow, $settings, $activate ); /** * Filter the settings before saving. * * @since 1.4.9 * * @param (array) $settings The module settings. * @param (array\bool) $activate Contains the activation rules for the different modules */ $settings = apply_filters( "secupress_{$modulenow}_settings_callback", $settings, $activate ); return $settings; } /** * Content Protection plugins. * * @since 1.0 * * @param (string) $modulenow Current module. * @param (bool|array) $activate Used to (de)activate plugins. */ function secupress_content_protection_settings_callback( $modulenow, &$settings, $activate ) { if ( false === $activate ) { return; } // (De)Activation. secupress_manage_submodule( $modulenow, '404guess', ! empty( $activate['content-protect_404guess'] ) && secupress_is_pro() ); secupress_manage_submodule( $modulenow, 'hotlink', ! empty( $activate['content-protect_hotlink'] ) && secupress_is_pro() ); secupress_manage_submodule( $modulenow, 'blackhole', ! empty( $activate['content-protect_blackhole'] ) && secupress_blackhole_is_robots_txt_enabled() ); secupress_manage_submodule( $modulenow, 'directory-listing', ! empty( $activate['content-protect_directory-listing'] ) ); secupress_manage_submodule( $modulenow, 'php-easter-egg', ! empty( $activate['content-protect_php-disclosure'] ) ); secupress_manage_submodule( 'discloses', 'no-x-powered-by', ! empty( $activate['content-protect_php-version'] ) ); secupress_manage_submodule( 'discloses', 'wp-version', ! empty( $activate['content-protect_wp-version'] ) ); secupress_manage_submodule( 'discloses', 'readmes', ! empty( $activate['content-protect_readmes'] ) ); $settings['content-protect_bad-url-access'] = isset( $activate['content-protect_bad-url-access'][0] ) ? $activate['content-protect_bad-url-access'][0] : ''; $settings['content-protect_bad-url-access'] = 'allowed' === $settings['content-protect_bad-url-access'] || 'disallowed' === $settings['content-protect_bad-url-access'] ? $settings['content-protect_bad-url-access'] : ''; if ( ! empty( $settings['content-protect_bad-url-access_allowed-urls'] ) ) { $GLOBALS['contentprotectbadurlaccessallowedurls'] = isset( $settings['content-protect_bad-url-access_allowed-urls'] ) ? $settings['content-protect_bad-url-access_allowed-urls'] : false; $GLOBALS['contentprotectbadurlaccess'] = $settings['content-protect_bad-url-access']; $settings['content-protect_bad-url-access_allowed-urls'] = implode( "\n", array_filter( explode( "\n", $settings['content-protect_bad-url-access_allowed-urls'] ), '_secupress_bad_url_access_allowed_url_filter' ) ); $settings['content-protect_bad-url-access_allowed-urls'] = _secupress_bad_url_access_allowed_urls_sanitize( $settings['content-protect_bad-url-access_allowed-urls'] ); } secupress_manage_submodule( $modulenow, 'bad-url-access-pro', 'allowed' === $settings['content-protect_bad-url-access'] ); secupress_manage_submodule( $modulenow, 'bad-file-extensions', 'disallowed' === $settings['content-protect_bad-url-access'] ); secupress_manage_submodule( $modulenow, 'bad-url-access', 'disallowed' === $settings['content-protect_bad-url-access'] ); $plugin_disclose = ! empty( $activate['content-protect_plugin-version-discloses'] ) && is_array( $activate['content-protect_plugin-version-discloses'] ) ? array_flip( $activate['content-protect_plugin-version-discloses'] ) : array(); $wp_plugins = array( 'woocommerce', 'wpml' ); foreach ( $wp_plugins as $wp_plugin ) { secupress_manage_submodule( 'discloses', $wp_plugin . '-version', isset( $plugin_disclose[ $wp_plugin ] ) ); } } /** * WordPress Endpoints plugins. * * @since 1.0 * * @param (string) $modulenow Current module. * @param (array) $settings The module settings, passed by reference. * @param (bool|array) $activate Used to (de)activate plugins. */ function secupress_wp_endpoints_settings_callback( $modulenow, &$settings, $activate ) { global $wp_rewrite; // Settings. if ( ! empty( $settings['wp-endpoints_xmlrpc'] ) && is_array( $settings['wp-endpoints_xmlrpc'] ) ) { $xmlrpc = array( 'block-all', 'block-multi', ); $settings['wp-endpoints_xmlrpc'] = array_intersect( $xmlrpc, $settings['wp-endpoints_xmlrpc'] ); $settings['wp-endpoints_xmlrpc'] = array_slice( $settings['wp-endpoints_xmlrpc'], 0, 1 ); // Only one choice. } else { unset( $settings['wp-endpoints_xmlrpc'] ); } // (De)Activation. secupress_manage_submodule( $modulenow, 'xmlrpc', ! empty( $settings['wp-endpoints_xmlrpc'] ) ); // `$settings`, not `$activate`. if ( ! empty( $settings['wp-endpoints_author_base'] ) ) { $old_author_base = trim( secupress_get_module_option( 'wp-endpoints_author_base', 'author', 'sensitive-data' ), '/' ); $new_author_base = sanitize_title( $settings['wp-endpoints_author_base'] ); $message = ''; if ( $settings['wp-endpoints_author_base'] !== $old_author_base ) { if ( 'author' === $new_author_base || ! $new_author_base ) { // back to WP default, no need to check $settings['wp-endpoints_author_base'] = 'author'; secupress_set_author_base( 'author' ); secupress_add_module_notice( '', __( 'Author Page Base', 'secupress' ), 'deactivation' ); return $settings; } $is_first_blog = is_multisite() && ! is_subdomain_install() && is_main_site(); // Get all the available slugs $bases = array(); // slug => what // The "obvious" ones $bases['blog'] = 'blog'; $bases['date'] = 'date'; $bases[ $wp_rewrite->search_base ] = 'search_base'; $bases[ $wp_rewrite->comments_base ] = 'comments_base'; $bases[ $wp_rewrite->pagination_base ] = 'pagination_base'; $bases[ $wp_rewrite->feed_base ] = 'feed_base'; // RSS if ( $wp_rewrite->feeds ) { foreach ( $wp_rewrite->feeds as $item ) { $bases[ $item ] = $item; } } // Post types and taxos $post_types = get_post_types( array( 'public' => true ), 'objects' ); $taxos = get_taxonomies( array( 'public' => true ), 'objects' ); $whatever = array_merge( $taxos, $post_types ); if ( $whatever ) { foreach ( $whatever as $what ) { // Singular if ( ! empty( $what->rewrite['slug'] ) ) { $bases[ $what->rewrite['slug'] ] = $what->name; } else { $bases[ $what->name ] = $what->name; } // Archive if ( ! empty( $what->has_archive ) && true !== $what->has_archive ) { $bases[ $what->has_archive ] = $what->name; } } } if ( ! empty( $bases[ $new_author_base ] ) ) { $error = ''; if ( taxonomy_exists( $bases[ $new_author_base ] ) ) { $error = __( 'a taxonomy', 'secupress' ); } elseif ( post_type_exists( $bases[ $new_author_base ] ) ) { $error = __( 'a custom post type', 'secupress' ); } } elseif ( get_page_by_path( $new_author_base ) ) { $error = __( 'a page', 'secupress' ); } elseif ( trim( get_option( 'permalink_structure' ), '/' ) === trim( $wp_rewrite->front . '%postname%', '/' ) && get_page_by_path( $new_author_base, 'OBJECT', 'post' ) ) { $error = __( 'a post', 'secupress' ); } if ( $error ) { $settings['wp-endpoints_author_base'] = $old_author_base; $error = sprintf( __( 'This author page base is already used for %s. Please choose another one.', 'secupress' ), $message ); secupress_add_transient_notice( $error, 'error', '', 'exist' ); return $settings; } $settings['wp-endpoints_author_base'] = $new_author_base; secupress_set_author_base( $new_author_base ); secupress_add_module_notice( '', __( 'Author Page Base', 'secupress' ), 'activation' ); } } else { secupress_add_module_notice( '', __( 'Author Page Base', 'secupress' ), 'deactivation' ); } } free/modules/sensitive-data/settings/content-protect.php 0000644 00000053402 15174670627 0017601 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); global $is_apache, $is_nginx, $is_iis7; $this->set_current_section( 'content_protect' ); $this->add_section( __( 'Content Protection', 'secupress' ) ); $main_field_name = $this->get_field_name( 'hotlink' ); $is_plugin_active = (int) secupress_is_submodule_active( 'sensitive-data', 'hotlink' ); $this->add_field( array( 'title' => __( 'Anti Hotlink', 'secupress' ), 'description' => __( 'A hotlink is when someone embeds your media directly from your website, stealing your bandwidth.', 'secupress' ), 'label_for' => $main_field_name, 'plugin_activation' => true, 'type' => 'checkbox', 'value' => $is_plugin_active, 'label' => __( 'Yes, safeguard my media from being hotlinked', 'secupress' ), 'disabled' => ! secupress_is_site_ssl(), 'helpers' => array( array( 'type' => 'warning', 'description' => ! secupress_is_site_ssl() ? __( 'This feature is available only for sites with HTTPS.', 'secupress' ) : null, ), ), ) ); global $wp_version; $main_field_name = $this->get_field_name( '404guess' ); $disabled = version_compare( $wp_version, '5.5' ) < 0; $is_plugin_active = (int) secupress_is_submodule_active( 'sensitive-data', '404guess' ); $helpers = ! $disabled ? [] : array( array( 'type' => 'warning', 'description' => sprintf( __( '<strong>%1$s</strong> requires WordPress %2$s minimum, your website is actually running version %3$s.', 'secupress' ), __( 'Anti 404 Guessing', 'secupress' ), '<code>5.5</code>', '<code>' . $wp_version . '</code>' ), ), ); $this->add_field( array( 'title' => __( 'Anti 404 Guessing', 'secupress' ), 'description' => __( 'WordPress can redirect people on your public posts and pages even if they don’t know the URL just by guessing.', 'secupress' ), 'label_for' => $main_field_name, 'disabled' => $disabled, 'plugin_activation' => true, 'type' => 'checkbox', 'value' => $is_plugin_active, 'label' => __( 'Yes, prevent guessing the URL of my posts and pages', 'secupress' ), 'helpers' => $helpers, ) ); $robots_enabled = secupress_blackhole_is_robots_txt_enabled(); $this->add_field( array( 'title' => __( 'Blackhole', 'secupress' ), 'description' => sprintf( __( 'A blackhole is a prohibited directory listed in the %1$s file as %2$s. If a bot fails to adhere to this rule, its IP address will be banned.', 'secupress' ), '<code>robots.txt</code>', '<em>Disallowed</em>' ), 'label_for' => $this->get_field_name( 'blackhole' ), 'plugin_activation' => true, 'type' => 'checkbox', 'value' => (int) secupress_is_submodule_active( 'sensitive-data', 'blackhole' ), 'label' => sprintf( __( 'Yes, add a blackhole in my %s file', 'secupress' ), '<code>robots.txt</code>' ), 'disabled' => ! $robots_enabled, 'helpers' => array( array( 'type' => 'description', 'description' => $robots_enabled ? false : __( 'This feature is only available for sites installed at the domain root.', 'secupress' ), ), ), ) ); /** * If nginx or if `.htaccess`/`web.config` is not writable, display a textarea containing the rewrite rules for the Anti Hotlink. */ if ( $is_plugin_active && function_exists( 'secupress_hotlink_get_apache_rules' ) ) { $message = false; // Nginx. if ( $is_nginx ) { /** Translators: 1 is a file name, 2 is a tag name. */ $message = sprintf( __( 'You need to add the following code to your %1$s file, inside the %2$s block:', 'secupress' ), '<code>nginx.conf</code>', '<code>server</code>' ); $rules = secupress_hotlink_get_nginx_rules(); } // Apache. elseif ( $is_apache && ! secupress_root_file_is_writable( '.htaccess' ) ) { /** Translators: %s is a file name. */ $message = sprintf( __( 'Your %s file is not writable. Please add the following code to it:', 'secupress' ), '<code>.htaccess</code>' ); $rules = trim( secupress_hotlink_get_apache_rules() ); $rules = "# BEGIN SecuPress hotlink\n$rules\n# END SecuPress"; } // IIS7. elseif ( $is_iis7 && ! secupress_root_file_is_writable( 'web.config' ) ) { /** Translators: %s is a file name. */ $message = sprintf( __( 'Your %s file is not writable. Please add the following code to it:', 'secupress' ), '<code>web.config</code>' ); $rules = secupress_hotlink_get_iis7_rules(); } if ( $message ) { $this->add_field( array( 'title' => _x( 'Rules', 'rewrite rules', 'secupress' ), 'description' => $message, 'depends' => $main_field_name, 'label_for' => $this->get_field_name( 'hotlink_rules' ), 'type' => 'textarea', 'value' => $rules, 'attributes' => array( 'readonly' => 'readonly', 'rows' => substr_count( $rules, "\n" ) + 1, ), ) ); } } $main_field_name = $this->get_field_name( 'directory-listing' ); $is_plugin_active = (int) secupress_is_submodule_active( 'sensitive-data', 'directory-listing' ); $this->add_field( array( 'title' => __( 'Directory Listing', 'secupress' ), 'description' => __( 'Directory Listing is a feature that permits anyone to view the contents of a directory (including its files and sub-folders) by entering its URL in a web browser. This presents a significant security risk, and most hosts disable it by default. If this feature is enabled and you wish to disable it, you can do so here.', 'secupress' ), 'label_for' => $main_field_name, 'plugin_activation' => true, 'type' => 'checkbox', 'value' => $is_plugin_active, 'label' => __( 'Yes, disable Directory Listing', 'secupress' ), ) ); /** * If nginx or if `.htaccess`/`web.config` is not writable, display a textarea containing the rewrite rules for the Directory Listing. */ if ( $is_plugin_active && function_exists( 'secupress_directory_listing_apache_rules' ) ) { $message = false; // Nginx. if ( $is_nginx ) { /** Translators: %s is a file name. */ $message = sprintf( __( 'Please add the following code to your %s file:', 'secupress' ), '<code>nginx.conf</code>' ); $rules = secupress_directory_listing_nginx_rules(); } // Apache. elseif ( $is_apache && ! secupress_root_file_is_writable( '.htaccess' ) ) { /** Translators: %s is a file name. */ $message = sprintf( __( 'Your %s file is not writable. Please add the following code to it:', 'secupress' ), '<code>.htaccess</code>' ); $rules = trim( secupress_directory_listing_apache_rules() ); $rules = "# BEGIN SecuPress directory_listing\n$rules\n# END SecuPress"; } // IIS7. elseif ( $is_iis7 && ! secupress_root_file_is_writable( 'web.config' ) ) { /** Translators: %s is a file name. */ $message = sprintf( __( 'Your %s file is not writable. Please add the following code to it:', 'secupress' ), '<code>web.config</code>' ); $rules = secupress_directory_listing_iis7_rules(); } if ( $message ) { $this->add_field( array( 'title' => _x( 'Rules', 'rewrite rules', 'secupress' ), 'description' => $message, 'depends' => $main_field_name, 'label_for' => $this->get_field_name( 'directory_listing_rules' ), 'type' => 'textarea', 'value' => $rules, 'attributes' => array( 'readonly' => 'readonly', 'rows' => substr_count( $rules, "\n" ) + 1, ), ) ); } } $main_field_name = $this->get_field_name( 'php-disclosure' ); $is_plugin_active = (int) secupress_is_submodule_active( 'sensitive-data', 'php-easter-egg' ); $this->add_field( array( 'title' => __( 'PHP Disclosure', 'secupress' ), /** Translators: here we speak about PHP modules, as in http://de2.php.net/manual/en/function.phpinfo.php */ 'description' => __( 'PHP contains a flaw that discloses sensitive information about installed modules, this is also known as «PHP Easter Egg». This is highly insecure and most hosts disable it by default. If this is not the case you can disable it here.', 'secupress' ), 'label_for' => $main_field_name, 'plugin_activation' => true, 'type' => 'checkbox', 'value' => $is_plugin_active, /** Translators: here we speak about PHP modules, as in http://de2.php.net/manual/en/function.phpinfo.php */ 'label' => __( 'Yes, forbid access to this PHP modules disclosure', 'secupress' ), ) ); /** * If nginx or if `.htaccess`/`web.config` is not writable, display a textarea containing the rewrite rules for the PHP Disclosure. */ if ( $is_plugin_active && function_exists( 'secupress_php_disclosure_apache_rules' ) ) { $message = false; // Nginx. if ( $is_nginx ) { /** Translators: %s is a file name. */ $message = sprintf( __( 'You need to add the following code to your %s file:', 'secupress' ), '<code>nginx.conf</code>' ); $rules = secupress_php_disclosure_nginx_rules(); } // Apache. elseif ( $is_apache && ! secupress_root_file_is_writable( '.htaccess' ) ) { /** Translators: %s is a file name. */ $message = sprintf( __( 'Your %s file is not writable. Please add the following code to it:', 'secupress' ), '<code>.htaccess</code>' ); $rules = trim( secupress_php_disclosure_apache_rules() ); $rules = "# BEGIN SecuPress php_disclosure\n$rules\n# END SecuPress"; } // IIS7. elseif ( $is_iis7 && ! secupress_root_file_is_writable( 'web.config' ) ) { /** Translators: %s is a file name. */ $message = sprintf( __( 'Your %s file is not writable. Please add the following code to it:', 'secupress' ), '<code>web.config</code>' ); $rules = secupress_php_disclosure_iis7_rules(); } if ( $message ) { $this->add_field( array( 'title' => _x( 'Rules', 'rewrite rules', 'secupress' ), 'description' => $message, 'depends' => $main_field_name, 'label_for' => $this->get_field_name( 'php_disclosure_rules' ), 'type' => 'textarea', 'value' => $rules, 'attributes' => array( 'readonly' => 'readonly', 'rows' => substr_count( $rules, "\n" ) + 1, ), ) ); } } $main_field_name = $this->get_field_name( 'php-version' ); $is_plugin_active = (int) secupress_is_submodule_active( 'discloses', 'no-x-powered-by' ); $this->add_field( array( 'title' => __( 'PHP Version Disclosure', 'secupress' ), 'description' => sprintf( __( 'Some servers send a header called %s that contains the PHP version used on your site. It may be a useful information for attackers, and should be removed.', 'secupress' ), '<strong>X-Powered-By</strong>' ), 'label_for' => $main_field_name, 'plugin_activation' => true, 'type' => 'checkbox', 'value' => $is_plugin_active, 'label' => __( 'Yes, remove the PHP version', 'secupress' ), ) ); /** * If nginx or if `.htaccess`/`web.config` is not writable, display a textarea containing the rewrite rules for the PHP Version Disclosure. */ if ( $is_plugin_active && function_exists( 'secupress_no_x_powered_by_apache_rules' ) ) { $message = false; // Nginx. if ( $is_nginx ) { /** Translators: %s is a file name. */ $message = sprintf( __( 'You need to add the following code to your %s file:', 'secupress' ), '<code>nginx.conf</code>' ); $rules = secupress_no_x_powered_by_nginx_rules(); } // Apache. elseif ( $is_apache && ! secupress_root_file_is_writable( '.htaccess' ) ) { /** Translators: %s is a file name. */ $message = sprintf( __( 'Your %s file is not writable. Please add the following code to it:', 'secupress' ), '<code>.htaccess</code>' ); $rules = trim( secupress_no_x_powered_by_apache_rules() ); $rules = "# BEGIN SecuPress no_x_powered_by\n$rules\n# END SecuPress"; } // IIS7. elseif ( $is_iis7 && ! secupress_root_file_is_writable( 'web.config' ) ) { /** Translators: %s is a file name. */ $message = sprintf( __( 'Your %s file is not writable. Please add the following code to it:', 'secupress' ), '<code>web.config</code>' ); $rules = secupress_no_x_powered_by_iis7_rules(); } if ( $message ) { $this->add_field( array( 'title' => _x( 'Rules', 'rewrite rules', 'secupress' ), 'description' => $message, 'depends' => $main_field_name, 'label_for' => $this->get_field_name( 'no_x_powered_by_rules' ), 'type' => 'textarea', 'value' => $rules, 'attributes' => array( 'readonly' => 'readonly', 'rows' => substr_count( $rules, "\n" ) + 1, ), ) ); } } $main_field_name = $this->get_field_name( 'wp-version' ); $is_plugin_active = (int) secupress_is_submodule_active( 'discloses', 'wp-version' ); $this->add_field( array( 'title' => __( 'WordPress Version Disclosure', 'secupress' ), 'description' => __( 'Disclosing your WordPress version may be a useful information for attackers, it should be removed.', 'secupress' ), 'label_for' => $main_field_name, 'plugin_activation' => true, 'type' => 'checkbox', 'value' => $is_plugin_active, 'label' => __( 'Yes, remove the WordPress version', 'secupress' ), ) ); /** * If nginx or if `.htaccess`/`web.config` is not writable, display a textarea containing the rewrite rules for the WP Version Disclosure. */ if ( $is_plugin_active && function_exists( 'secupress_wp_version_apache_rules' ) ) { $message = false; // Nginx. if ( $is_nginx ) { /** Translators: %s is a file name. */ $message = sprintf( __( 'You need to add the following code to your %s file:', 'secupress' ), '<code>nginx.conf</code>' ); $rules = secupress_wp_version_nginx_rules(); } // Apache. elseif ( $is_apache && ! secupress_root_file_is_writable( '.htaccess' ) ) { /** Translators: %s is a file name. */ $message = sprintf( __( 'Your %s file is not writable. Please add the following code to it:', 'secupress' ), '<code>.htaccess</code>' ); $rules = trim( secupress_wp_version_apache_rules() ); $rules = "# BEGIN SecuPress wp_version\n$rules\n# END SecuPress"; } // IIS7. elseif ( $is_iis7 && ! secupress_root_file_is_writable( 'web.config' ) ) { /** Translators: %s is a file name. */ $message = sprintf( __( 'Your %s file is not writable. Please add the following code to it:', 'secupress' ), '<code>web.config</code>' ); $rules = secupress_wp_version_iis7_rules(); } if ( $message ) { $this->add_field( array( 'title' => _x( 'Rules', 'rewrite rules', 'secupress' ), 'description' => $message, 'depends' => $main_field_name, 'label_for' => $this->get_field_name( 'wp_version_rules' ), 'type' => 'textarea', 'value' => $rules, 'attributes' => array( 'readonly' => 'readonly', 'rows' => substr_count( $rules, "\n" ) + 1, ), ) ); } } $main_field_name = $this->get_field_name( 'readmes' ); $is_plugin_active = (int) secupress_is_submodule_active( 'discloses', 'readmes' ); $this->add_field( array( 'title' => __( 'Protect Readme Files', 'secupress' ), /** Translators: 1 and 2 are file names. */ 'description' => sprintf( __( 'Files like %1$s or %2$s are a good source of information for attackers, they should not be accessible.', 'secupress' ), '<code>readme.txt</code>', '<code>changelog.md</code>' ), 'label_for' => $main_field_name, 'plugin_activation' => true, 'type' => 'checkbox', 'value' => $is_plugin_active, 'label' => __( 'Yes, restrict access to these files', 'secupress' ), ) ); /** * If nginx or if `.htaccess`/`web.config` is not writable, display a textarea containing the rewrite rules for the Readmes. */ if ( $is_plugin_active && function_exists( 'secupress_protect_readmes_apache_rules' ) ) { $message = false; // Nginx. if ( $is_nginx ) { /** Translators: %s is a file name. */ $message = sprintf( __( 'You need to add the following code to your %s file:', 'secupress' ), '<code>nginx.conf</code>' ); $rules = secupress_protect_readmes_nginx_rules(); } // Apache. elseif ( $is_apache && ! secupress_root_file_is_writable( '.htaccess' ) ) { /** Translators: %s is a file name. */ $message = sprintf( __( 'Your %s file is not writable. Please add the following code to it:', 'secupress' ), '<code>.htaccess</code>' ); $rules = trim( secupress_protect_readmes_apache_rules() ); $rules = "# BEGIN SecuPress readme_discloses\n$rules\n# END SecuPress"; } // IIS7. elseif ( $is_iis7 && ! secupress_root_file_is_writable( 'web.config' ) ) { /** Translators: %s is a file name. */ $message = sprintf( __( 'Your %s file is not writable. Please add the following code to it:', 'secupress' ), '<code>web.config</code>' ); $rules = secupress_protect_readmes_iis7_rules(); } if ( $message ) { $this->add_field( array( 'title' => _x( 'Rules', 'rewrite rules', 'secupress' ), 'description' => $message, 'depends' => $main_field_name, 'label_for' => $this->get_field_name( 'readmes_rules' ), 'type' => 'textarea', 'value' => $rules, 'attributes' => array( 'readonly' => 'readonly', 'rows' => substr_count( $rules, "\n" ) + 1, ), ) ); } } $choices = array(); if ( class_exists( 'WooCommerce' ) ) { /** Translators: %s is a plugin name. */ $choices['woocommerce'] = sprintf( __( 'Do not display the %s version', 'secupress' ), '<strong>WooCommerce</strong>' ); } if ( class_exists( 'SitePress' ) ) { /** Translators: %s is a plugin name. */ $choices['wpml'] = sprintf( __( 'Do not display the %s version', 'secupress' ), '<strong>WPML</strong>' ); } if ( $choices ) { $values = array_keys( $choices ); $values = array_combine( $values, $values ); foreach ( $choices as $wp_plugin => $name ) { if ( ! secupress_is_submodule_active( 'discloses', $wp_plugin . '-version' ) ) { unset( $values[ $wp_plugin ] ); } } $this->add_field( array( 'title' => __( 'Plugin Version Disclosure', 'secupress' ), 'description' => __( 'Some popular big plugins print their version in your site’s source code. This information can be useful for attackers.', 'secupress' ), 'name' => $this->get_field_name( 'plugin-version-discloses' ), 'plugin_activation' => true, 'type' => 'checkboxes', 'options' => $choices, 'value' => $values, ) ); } $main_field_name = $this->get_field_name( 'bad-url-access' ); $value = (int) secupress_is_submodule_active( 'sensitive-data', 'bad-url-access' ) ? 'disallowed' : ''; $value = (int) secupress_is_submodule_active( 'sensitive-data', 'bad-url-access-pro' ) ? 'allowed' : $value; $options = [ 'disallowed' => __( 'Yes, block a list of <strong>disallowed</strong> URLs from WP core', 'secupress' ) ]; if ( secupress_is_expert_mode() ) { $options['allowed'] = __( 'Yes, only <strong>allow</strong> a list of URLs from WP core <em>(new)</em>', 'secupress' ); $options['disallowed'] .= ' <em>(' . __( 'legacy', 'secupress' ) . ')</em>'; } $this->add_field( array( 'title' => __( 'Bad URL Access and File Extensions', 'secupress' ), 'description' => __( 'Directly accessing certain URLs and files on your site could expose sensitive information helping attackers.', 'secupress' ), 'label_for' => $main_field_name, 'plugin_activation' => true, 'type' => 'radioboxes', 'value' => $value, 'options' => $options, 'helpers' => array( array( 'type' => 'force', 'description' => secupress_is_expert_mode() ? __( '• <strong>Disallowing</strong> does not require any configuration, but is less effective.<br>• <strong>Allowing</strong> may require a special configuration but is way more effective.<br>If you encounter any issue that you cannot resolve with the <strong>Custom allowed URLs</strong> field below, rollback to <strong>Disallowed</strong>.', 'secupress' ) : '', ), ), ) ); $this->add_field( array( 'title' => __( 'Custom allowed URLs and Folders', 'secupress' ), 'depends' => $main_field_name . '_allowed', 'name' => $main_field_name . '_allowed-urls', 'type' => 'textarea', 'helpers' => array( array( 'type' => 'description', 'description' => __( 'One URL per line.', 'secupress' ) . ' ' . __( 'They will be filtered to retain only those pointing to existing files and folders.', 'secupress' ), ), ), ) ); /** * If nginx or if `.htaccess`/`web.config` is not writable, display a textarea containing the rewrite rules for the Bad URL Access. */ if ( $is_plugin_active && function_exists( 'secupress_bad_url_access_apache_rules' ) ) { $message = false; // Nginx. if ( $is_nginx ) { /** Translators: %s is a file name. */ $message = sprintf( __( 'You need to add the following code to your %s file:', 'secupress' ), '<code>nginx.conf</code>' ); $rules = secupress_bad_url_access_nginx_rules(); } // Apache. elseif ( $is_apache && ! secupress_root_file_is_writable( '.htaccess' ) ) { /** Translators: %s is a file name. */ $message = sprintf( __( 'Your %s file is not writable. Please add the following code to it:', 'secupress' ), '<code>.htaccess</code>' ); $rules = trim( secupress_bad_url_access_apache_rules() ); $rules = "# BEGIN SecuPress bad_url_access\n$rules\n# END SecuPress"; } // IIS7. elseif ( $is_iis7 && ! secupress_root_file_is_writable( 'web.config' ) ) { /** Translators: %s is a file name. */ $message = sprintf( __( 'Your %s file is not writable. Please add the following code to it:', 'secupress' ), '<code>web.config</code>' ); $rules = secupress_bad_url_access_iis7_rules(); } if ( $message ) { $this->add_field( array( 'title' => _x( 'Rules', 'rewrite rules', 'secupress' ), 'description' => $message, 'depends' => $main_field_name, 'label_for' => $this->get_field_name( 'bad_url_access_rules' ), 'type' => 'textarea', 'value' => $rules, 'attributes' => array( 'readonly' => 'readonly', 'rows' => substr_count( $rules, "\n" ) + 1, ), ) ); } } free/modules/sensitive-data/settings/wp-endpoints.php 0000644 00000003066 15174670627 0017101 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); $this->set_current_section( 'wp_endpoints' ); $this->add_section( __( 'WordPress Endpoints', 'secupress' ) ); $this->add_field( array( 'title' => __( 'XML-RPC', 'secupress' ), 'description' => __( 'If you don’t use XML-RPC, you can disable it and avoid becoming a target if a vulnerability is discovered.', 'secupress' ), 'name' => $this->get_field_name( 'xmlrpc' ), 'type' => 'radioboxes', 'value' => ( secupress_is_submodule_active( 'sensitive-data', 'xmlrpc' ) ? null : array() ), 'options' => array( 'block-all' => __( '<strong>Disable all</strong> the features of XML-RPC', 'secupress' ), 'block-multi' => __( '<strong>Only disable</strong> for multiple authentication attempts', 'secupress' ), ), 'helpers' => array( array( 'type' => 'warning', 'description' => __( 'If you have a mobile application, or any service linked to your website, you should not disable all the features of XML-RPC.', 'secupress' ), ), ), ) ); $this->add_field( array( 'title' => __( 'Author Page Base', 'secupress' ), 'description' => __( 'If your site allows, it may display author pages; here, you can change the base page. Users with access to their WordPress profile page can choose their own slug.', 'secupress' ), 'name' => $this->get_field_name( 'author_base' ), 'type' => 'text', 'label_before' => home_url( ! is_multisite() ? '/' : '/blog/' ), 'label_after' => '/…', 'value' => secupress_get_author_base() ) ); free/modules/sensitive-data/settings.php 0000644 00000000240 15174670627 0014441 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); $this->load_plugin_settings( 'wp-endpoints' ); $this->load_plugin_settings( 'content-protect' ); free/modules/schedules/callbacks.php 0000644 00000001631 15174670627 0013564 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); /** --------------------------------------------------------------------------------------------- */ /** ON FORM SUBMIT ============================================================================== */ /** --------------------------------------------------------------------------------------------- */ /** * Callback to filter, sanitize, validate and de/activate submodules. * * @since 1.0 * * @return (array) The sanitized and validated settings. */ function secupress_schedules_settings_callback( $settings ) { /** * Filter the settings before saving. * * @since 1.4.9 * * @param (array) $settings The module settings. * @param (array\bool) $activate Contains the activation rules for the different modules */ $settings = apply_filters( "secupress_{$modulenow}_settings_callback", $settings, $activate ); return $settings; } free/modules/schedules/settings/schedules-backups.php 0000644 00000003413 15174670627 0017112 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); $this->set_current_section( 'backups' ); $this->add_section( __( 'Backups', 'secupress' ) . ' ' . sprintf( '<span class="button button-small alignright secupress-button-small"><a href="%s">%s</a></span>', secupress_admin_url( 'modules', 'backups' ), __( 'Backup Module', 'secupress' ) ) ); $this->add_field( array( 'name' => $this->get_field_name( 'scheduled' ), 'type' => 'scheduled_backups', 'row_class' => 'secupress-schedule-message-field', ) ); $this->add_field( array( 'title' => __( 'Backup Type', 'secupress' ), 'name' => $this->get_field_name( 'type' ), 'type' => 'checkboxes', 'options' => array( 'db' => __( 'Database', 'secupress' ), 'files' => __( 'Files', 'secupress' ) ), ) ); /** Translators: use %d, nothing else. */ $label_before = __( 'Every %d days', 'secupress' ); $label_before = explode( '%d', $label_before ); $label_after = $label_before[1]; $label_before = $label_before[0]; $this->add_field( array( 'title' => __( 'Frequency', 'secupress' ), 'label_for' => $this->get_field_name( 'periodicity' ), 'type' => 'number', 'label_before' => $label_before, 'label_after' => $label_after, 'attributes' => array( 'min' => 0, ), ) ); $this->add_field( array( 'title' => __( 'Notification of the result', 'secupress' ), 'description' => __( 'Once completed, a notification will be sent to the provided email address (optional).', 'secupress' ), 'label' => __( 'Email' ), 'label_for' => $this->get_field_name( 'email' ), 'type' => 'email', 'default' => secupress_get_module_option( $this->get_field_name( 'periodicity' ), '', $this->modulenow ) ? '' : wp_get_current_user()->user_email, ) ); free/modules/schedules/settings/schedules-file-monitoring.php 0000644 00000003257 15174670627 0020572 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); $this->set_current_section( 'files-monitoring' ); $this->add_section( __( 'File Monitoring', 'secupress' ) . ' ' . sprintf( '<span class="button button-small alignright secupress-button-small"><a href="%s">%s</a></span>', secupress_admin_url( 'modules', 'file-system' ), __( 'Malware Scan Module', 'secupress' ) ) ); $this->add_field( array( 'name' => $this->get_field_name( 'scheduled' ), 'type' => 'scheduled_monitoring', 'row_class' => 'secupress-schedule-message-field', ) ); /** Translators: use %d, nothing else. */ $label_before = __( 'Every %d days', 'secupress' ); $label_before = explode( '%d', $label_before ); $label_after = $label_before[1]; $label_before = $label_before[0]; $this->add_field( array( 'title' => __( 'Frequency', 'secupress' ), 'label_for' => $this->get_field_name( 'periodicity' ), 'type' => 'number', 'label_before' => $label_before, 'label_after' => $label_after, 'attributes' => array( 'min' => 0, 'max' => 7, ), 'helpers' => array( array( 'type' => 'help', 'description' => sprintf( __( 'Maximum %d days.', 'secupress' ), 7 ), ), ), ) ); $this->add_field( array( 'title' => __( 'Notification of result', 'secupress' ), 'description' => __( 'Once completed, a notification will be sent to the provided email address (optional).', 'secupress' ), 'label' => __( 'Email' ), 'label_for' => $this->get_field_name( 'email' ), 'type' => 'email', 'default' => secupress_get_module_option( $this->get_field_name( 'periodicity' ), '', $this->modulenow ) ? '' : wp_get_current_user()->user_email, ) ); free/modules/schedules/settings/schedules-scan.php 0000644 00000003207 15174670627 0016407 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); $this->set_current_section( 'scanners' ); $this->add_section( __( 'Scanners', 'secupress' ) . ' ' . sprintf( '<span class="button button-small alignright secupress-button-small"><a href="%s">%s</a></span>', secupress_admin_url( 'scanners' ), __( 'Scanner Module', 'secupress' ) ) ); $this->add_field( array( 'name' => $this->get_field_name( 'scheduled' ), 'type' => 'scheduled_scan', 'row_class' => 'secupress-schedule-message-field', ) ); /** Translators: use %d, nothing else. */ $label_before = __( 'Every %d days', 'secupress' ); $label_before = explode( '%d', $label_before ); $label_after = $label_before[1]; $label_before = $label_before[0]; $this->add_field( array( 'title' => __( 'Frequency', 'secupress' ), 'label_for' => $this->get_field_name( 'periodicity' ), 'type' => 'number', 'label_before' => $label_before, 'label_after' => $label_after, 'attributes' => array( 'min' => 0, 'max' => 7, ), 'helpers' => array( array( 'type' => 'help', 'description' => sprintf( __( 'Maximum %d days.', 'secupress' ), 7 ), ), ), ) ); $this->add_field( array( 'title' => __( 'Notification of result', 'secupress' ), 'description' => __( 'Once completed, a notification will be sent to the provided email address (optional).', 'secupress' ), 'label' => __( 'Email' ), 'label_for' => $this->get_field_name( 'email' ), 'type' => 'email', 'default' => secupress_get_module_option( $this->get_field_name( 'periodicity' ), '', $this->modulenow ) ? '' : wp_get_current_user()->user_email, ) ); free/modules/schedules/settings.php 0000644 00000000340 15174670627 0013501 0 ustar 00 <?php defined( 'ABSPATH' ) or die( 'Something went wrong.' ); $this->load_plugin_settings( 'schedules-backups' ); $this->load_plugin_settings( 'schedules-scan' ); $this->load_plugin_settings( 'schedules-file-monitoring' ); free/modules/users-login/plugins/admin-email.php 0000644 00000005350 15174670627 0015775 0 ustar 00 <?php /** * Module Name: Lock Admin Email * Description: Prevent future modification of the admin email * Main Module: users_login * Author: SecuPress * Version: 2.0 */ defined( 'SECUPRESS_VERSION' ) or die( 'Something went wrong.' ); /** --------------------------------------------------------------------------------------------- */ /** ACTIVATION / DEACTIVATION =================================================================== */ /** --------------------------------------------------------------------------------------------- */ add_action( 'secupress.modules.activate_submodule_' . basename( __FILE__, '.php' ), 'secupress_wpconfig_adminemail_activation' ); add_action( 'secupress.plugins.activation', 'secupress_wpconfig_adminemail_activation' ); /** * On module activation, remove the define. * * @since 2.0 * @author Julio Potier */ function secupress_wpconfig_adminemail_activation() { secupress_wpconfig_modules_activation( 'adminemail' ); } add_action( 'secupress.modules.deactivate_submodule_' . basename( __FILE__, '.php' ), 'secupress_wpconfig_adminemail_deactivation' ); add_action( 'secupress.plugins.deactivation', 'secupress_wpconfig_adminemail_deactivation' ); /** * On module deactivation, maybe put the constant back. * * @since 2.0 * @author Julio Potier */ function secupress_wpconfig_adminemail_deactivation() { secupress_wpconfig_modules_deactivation( 'adminemail' ); } add_filter( 'pre_option_admin_email', 'secupress_lock_admin_email_option' ); add_filter( 'pre_option_new_admin_email', 'secupress_lock_admin_email_option' ); /** * Lock the admin email from our constant set on module activation only * * @since 2.0 * @author Julio Potier * * @return (string) The slug of the default role **/ function secupress_lock_admin_email_option( $email ) { $email = defined( 'SECUPRESS_LOCKED_ADMIN_EMAIL' ) ? SECUPRESS_LOCKED_ADMIN_EMAIL : $email; $email = apply_filters( 'secupress.plugin.admin_email', $email ); if ( has_filter( 'secupress.plugin.admin_email' ) ) { _deprecated_hook( 'secupress.plugin.admin_email', '2.2.6', 'secupress.plugins.admin_email' ); } /** * Filter the admin email * @param (string) $email */ return apply_filters( 'secupress.plugins.admin_email', $email ); } add_action( 'admin_head-options-general.php', 'secupress_disable_admin_email_input' ); /** * Add JS/CSS to prevent UI manipulation * * @since 2.0 * @author Julio Potier * **/ function secupress_disable_admin_email_input() { $i18n = esc_js( __( 'Disabled for security reasons.', 'secupress' ) ); ?> <script> jQuery( document ).ready( function($){ $('#new_admin_email').attr('disabled','disabled').addClass('disabled').after( ' <span class="secupress_disable_select"><?php echo $i18n; ?></span> ' ); } ); </script> <?php } free/modules/users-login/plugins/inc/img/backup-codes-icon_2X.png 0000644 00000015754 15174670627 0021012 0 ustar 00 �PNG IHDR � � �F� �IDATx��wDz�]�?�=�<@ ���9����l� �hr���Id����;��;;���W���:hg��ڞWW���RKIAI�� RR ))���@J %��HII�� RR ))���@J %��HI)�.�z���DK�,=�Ԥ��K�#��Kp#��ۖ��R �a��kKwuP�J$0�5 �����B��.�@(��.��:J-��_E��RK�HI�� RR ))���@J %��HII�� RR ))���@J ��? �ܥ�)��2�*L��JS^Ym��v/ؾ��kN�����]ߕ���;�{��WUݭ�畻�U:j��J���(������hS ���a�Y�t�ٲe�ٰq��=g��ֽ����%�=�2l��q��e��f� f�in�QQ�������.^b6۾ݸ�̝��t��;��B��0�,[��=oï͌���w�>�L���/0�6o1�m�l�.6����xI���x��������~t����:}���ik���}͂E�f��o�o����ܽ���Ѧ TV��L�6�ܻ�h�W}}�8h� �2 ��e�W��ϟ���9x��ѫ�4f� ��۷�r�:l���`~��a^gϞ5�,����{��蓾�<��5��RxF�,�`x��I�x?~���?�5z��~�F^����kS b F�c^�x����O�߯^�� vИy��Ͳ/����}��߿��<�f)}:,g`�>}j^�|�?ߺu�I�B�>x����ѣ����纺:S��/���S.@��Y��/�TH�q��y�S�N��>2o߾��O�<�_���J����-��|G���j� �?`�ݦ����7/���W�r�:�~����g�q5,��3a�d��ͮݻ�����oX�v��Y����q�x/���3���߷������#F�k��,��]ח\<�����o���r�S�����~Z��J�K��� ��cF�k�ܹ��O�9�|c�ۼu��w��1ӯ� ���6��kS bF4�Ͱ�$y��a^��}�;w��d�O�8�|���;��I����Nf��\۶��D��������@�G�82��_�v�]����v�)�n9>���ߙ�o�Y��Xff̚�y9z��/_v��g�B˳�����7���<�G��MC�����u��5����I��7o��/^���tX��W��e� l����1v|�_���Y�����;,�;?~��v�R?((�X^�[�@^虳������g �� �"�v4C�ہ듰�d���ڗ �Ѷ��-D������F�H��炧�#G��^|�~Z��?k����۶{� #J�,��8�D�QB]���\�꾣 ���/c_@(ϋ�u�4 2�F��b�E��Xq"�X��/X��9}3`gϝ�/ � J5,f� �eF���2E���^Y�x��}�}_�f��SV��4<�������5?����E�)�P )�x�������̞;�|���}>|�o�Y:+ͱc�}_X��m@��� t�B��#���Ͷ)�G�y ����p����<u�h�� �N������ܚ�!�ʃ�x��{|_�j�$��iyo�>x*��w�z��(���Б�.��� �ؗЪW�(�� �X@H�8�d�Cԇ ڽgO��8�,�֮[��d�)� h��,��;@{� �- �� @��\�J;�q �"� :d �<