PageRenderTime 57ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 1ms

/wp-content/plugins/sucuri-scanner/sucuri.php

https://github.com/CaffeinatedJim/catsinmyyard
PHP | 11683 lines | 7249 code | 1669 blank | 2765 comment | 1250 complexity | 3f9a5bf111a2bd57ffe47be637e3db9a MD5 | raw file
Possible License(s): GPL-2.0, LGPL-3.0, GPL-3.0, BSD-3-Clause, Apache-2.0, MIT, AGPL-1.0, LGPL-2.1

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. /*
  3. Plugin Name: Sucuri Security - Auditing, Malware Scanner and Hardening
  4. Plugin URI: http://wordpress.sucuri.net/
  5. Description: The <a href="http://sucuri.net/" target="_blank">Sucuri</a> plugin provides the website owner the best Activity Auditing, SiteCheck Remote Malware Scanning, Effective Security Hardening and Post-Hack features. SiteCheck will check for malware, spam, blacklisting and other security issues like .htaccess redirects, hidden eval code, etc. The best thing about it is it's completely free.
  6. Author: Sucuri, INC
  7. Version: 1.7.7
  8. Author URI: http://sucuri.net
  9. */
  10. /**
  11. * Main file to control the plugin.
  12. *
  13. * @package Sucuri Security
  14. * @author Yorman Arias <yorman.arias@sucuri.net>
  15. * @author Daniel Cid <dcid@sucuri.net>
  16. * @copyright Since 2010-2015 Sucuri Inc.
  17. * @license Released under the GPL - see LICENSE file for details.
  18. * @link https://wordpress.sucuri.net/
  19. * @since File available since Release 0.1
  20. */
  21. /**
  22. * Plugin dependencies.
  23. *
  24. * List of required functions for the execution of this plugin, we are assuming
  25. * that this site was built on top of the WordPress project, and that it is
  26. * being loaded through a pluggable system, these functions most be defined
  27. * before to continue.
  28. *
  29. * @var array
  30. */
  31. $sucuriscan_dependencies = array(
  32. 'wp',
  33. 'wp_die',
  34. 'add_action',
  35. 'remove_action',
  36. 'wp_remote_get',
  37. 'wp_remote_post',
  38. );
  39. // Terminate execution if any of the functions mentioned above is not defined.
  40. foreach ( $sucuriscan_dependencies as $dependency ) {
  41. if ( ! function_exists( $dependency ) ) {
  42. exit(0);
  43. }
  44. }
  45. /**
  46. * Plugin's constants.
  47. *
  48. * These constants will hold the basic information of the plugin, file/folder
  49. * paths, version numbers, read-only variables that will affect the functioning
  50. * of the rest of the code. The conditional will act as a container helping in
  51. * the readability of the code considering the total number of lines that this
  52. * file will have.
  53. */
  54. /**
  55. * Unique name of the plugin through out all the code.
  56. */
  57. define( 'SUCURISCAN', 'sucuriscan' );
  58. /**
  59. * Current version of the plugin's code.
  60. */
  61. define( 'SUCURISCAN_VERSION', '1.7.7' );
  62. /**
  63. * The name of the Sucuri plugin main file.
  64. */
  65. define( 'SUCURISCAN_PLUGIN_FILE', 'sucuri.php' );
  66. /**
  67. * The name of the folder where the plugin's files will be located.
  68. */
  69. define( 'SUCURISCAN_PLUGIN_FOLDER', 'sucuri-scanner' );
  70. /**
  71. * The fullpath where the plugin's files will be located.
  72. */
  73. define( 'SUCURISCAN_PLUGIN_PATH', WP_PLUGIN_DIR.'/'.SUCURISCAN_PLUGIN_FOLDER );
  74. /**
  75. * The fullpath of the main plugin file.
  76. */
  77. define( 'SUCURISCAN_PLUGIN_FILEPATH', SUCURISCAN_PLUGIN_PATH.'/'.SUCURISCAN_PLUGIN_FILE );
  78. /**
  79. * The local URL where the plugin's files and assets are served.
  80. */
  81. define( 'SUCURISCAN_URL', rtrim( plugin_dir_url( SUCURISCAN_PLUGIN_FILEPATH ), '/' ) );
  82. /**
  83. * Checksum of this file to check the integrity of the plugin.
  84. */
  85. define( 'SUCURISCAN_PLUGIN_CHECKSUM', @md5_file( SUCURISCAN_PLUGIN_FILEPATH ) );
  86. /**
  87. * Remote URL where the public Sucuri API service is running.
  88. */
  89. define( 'SUCURISCAN_API', 'https://wordpress.sucuri.net/api/' );
  90. /**
  91. * Latest version of the public Sucuri API.
  92. */
  93. define( 'SUCURISCAN_API_VERSION', 'v1' );
  94. /**
  95. * Remote URL where the CloudProxy API service is running.
  96. */
  97. define( 'SUCURISCAN_CLOUDPROXY_API', 'https://waf.sucuri.net/api' );
  98. /**
  99. * Latest version of the CloudProxy API.
  100. */
  101. define( 'SUCURISCAN_CLOUDPROXY_API_VERSION', 'v2' );
  102. /**
  103. * The maximum quantity of entries that will be displayed in the last login page.
  104. */
  105. define( 'SUCURISCAN_LASTLOGINS_USERSLIMIT', 25 );
  106. /**
  107. * The maximum quantity of entries that will be displayed in the audit logs page.
  108. */
  109. define( 'SUCURISCAN_AUDITLOGS_PER_PAGE', 50 );
  110. /**
  111. * The maximum quantity of buttons in the paginations.
  112. */
  113. define( 'SUCURISCAN_MAX_PAGINATION_BUTTONS', 20 );
  114. /**
  115. * The minimum quantity of seconds to wait before each filesystem scan.
  116. */
  117. define( 'SUCURISCAN_MINIMUM_RUNTIME', 10800 );
  118. /**
  119. * The life time of the cache for the results of the SiteCheck scans.
  120. */
  121. define( 'SUCURISCAN_SITECHECK_LIFETIME', 1200 );
  122. /**
  123. * The life time of the cache for the results of the get_plugins function.
  124. */
  125. define( 'SUCURISCAN_GET_PLUGINS_LIFETIME', 1800 );
  126. /**
  127. * Plugin's global variables.
  128. *
  129. * These variables will be defined globally to allow the inclusion in multiple
  130. * functions and classes defined in the libraries loaded by this plugin. The
  131. * conditional will act as a container helping in the readability of the code
  132. * considering the total number of lines that this file will have.
  133. */
  134. if ( defined( 'SUCURISCAN' ) ){
  135. /**
  136. * List an associative array with the sub-pages of this plugin.
  137. *
  138. * @return array
  139. */
  140. $sucuriscan_pages = array(
  141. 'sucuriscan' => 'Dashboard',
  142. 'sucuriscan_scanner' => 'Malware Scan',
  143. 'sucuriscan_monitoring' => 'Firewall (WAF)',
  144. 'sucuriscan_hardening' => 'Hardening',
  145. 'sucuriscan_posthack' => 'Post-Hack',
  146. 'sucuriscan_lastlogins' => 'Last Logins',
  147. 'sucuriscan_settings' => 'Settings',
  148. 'sucuriscan_infosys' => 'Site Info',
  149. );
  150. /**
  151. * Settings options.
  152. *
  153. * The following global variables are mostly associative arrays where the key is
  154. * linked to an option that will be stored in the database, and their
  155. * correspondent values are the description of the option. These variables will
  156. * be used in the settings page to offer the user a way to configure the
  157. * behaviour of the plugin.
  158. *
  159. * @var array
  160. */
  161. $sucuriscan_notify_options = array(
  162. 'sucuriscan_notify_plugin_change' => 'Receive email alerts for <strong>Sucuri</strong> plugin changes',
  163. 'sucuriscan_prettify_mails' => 'Receive email alerts in HTML <em>(there may be issues with some mail services)</em>',
  164. 'sucuriscan_lastlogin_redirection' => 'Allow redirection after login to report the last-login information',
  165. 'sucuriscan_notify_user_registration' => 'user:Receive email alerts for new user registration',
  166. 'sucuriscan_notify_success_login' => 'user:Receive email alerts for successful login attempts',
  167. 'sucuriscan_notify_failed_login' => 'user:Receive email alerts for failed login attempts',
  168. 'sucuriscan_notify_bruteforce_attack' => 'user:Receive email alerts for password guessing brute force attacks',
  169. 'sucuriscan_notify_post_publication' => 'Receive email alerts for new content <em>(posts, attachments, forms, etc)</em>',
  170. 'sucuriscan_notify_website_updated' => 'Receive email alerts when the WordPress version is updated',
  171. 'sucuriscan_notify_settings_updated' => 'Receive email alerts when your website settings are updated',
  172. 'sucuriscan_notify_theme_editor' => 'Receive email alerts when a file is modified with theme/plugin editor',
  173. 'sucuriscan_notify_plugin_installed' => 'plugin:Receive email alerts when a plugin is installed',
  174. 'sucuriscan_notify_plugin_activated' => 'plugin:Receive email alerts when a plugin is activated',
  175. 'sucuriscan_notify_plugin_deactivated' => 'plugin:Receive email alerts when a plugin is deactivated',
  176. 'sucuriscan_notify_plugin_updated' => 'plugin:Receive email alerts when a plugin is updated',
  177. 'sucuriscan_notify_plugin_deleted' => 'plugin:Receive email alerts when a plugin is deleted',
  178. 'sucuriscan_notify_widget_added' => 'widget:Receive email alerts when a widget is added to a sidebar',
  179. 'sucuriscan_notify_widget_deleted' => 'widget:Receive email alerts when a widget is deleted from a sidebar',
  180. 'sucuriscan_notify_theme_installed' => 'theme:Receive email alerts when a theme is installed',
  181. 'sucuriscan_notify_theme_activated' => 'theme:Receive email alerts when a theme is activated',
  182. 'sucuriscan_notify_theme_updated' => 'theme:Receive email alerts when a theme is updated',
  183. 'sucuriscan_notify_theme_deleted' => 'theme:Receive email alerts when a theme is deleted',
  184. );
  185. $sucuriscan_schedule_allowed = array(
  186. 'hourly' => 'Every three hours (3 hours)',
  187. 'twicedaily' => 'Twice daily (12 hours)',
  188. 'daily' => 'Once daily (24 hours)',
  189. '_oneoff' => 'Never',
  190. );
  191. $sucuriscan_interface_allowed = array(
  192. 'spl' => 'SPL (high performance)',
  193. 'opendir' => 'OpenDir (medium)',
  194. 'glob' => 'Glob (low)',
  195. );
  196. $sucuriscan_emails_per_hour = array(
  197. '5' => 'Maximum 5 per hour',
  198. '10' => 'Maximum 10 per hour',
  199. '20' => 'Maximum 20 per hour',
  200. '40' => 'Maximum 40 per hour',
  201. '80' => 'Maximum 80 per hour',
  202. '160' => 'Maximum 160 per hour',
  203. 'unlimited' => 'Unlimited',
  204. );
  205. $sucuriscan_maximum_failed_logins = array(
  206. '30' => '30 failed logins per hour',
  207. '60' => '60 failed logins per hour',
  208. '120' => '120 failed logins per hour',
  209. '240' => '240 failed logins per hour',
  210. '480' => '480 failed logins per hour',
  211. );
  212. $sucuriscan_verify_ssl_cert = array(
  213. 'true' => 'Verify peer\'s cert',
  214. 'false' => 'Stop peer\'s cert verification',
  215. );
  216. $sucuriscan_no_notices_in = array(
  217. /* Value of the page parameter to ignore. */
  218. );
  219. $sucuriscan_email_subjects = array(
  220. 'Sucuri Alert, :domain, :event',
  221. 'Sucuri Alert, :domain, :event, :remoteaddr',
  222. 'Sucuri Alert, :event, :remoteaddr',
  223. 'Sucuri Alert, :event',
  224. );
  225. /**
  226. * Remove the WordPress generator meta-tag from the source code.
  227. */
  228. remove_action( 'wp_head', 'wp_generator' );
  229. /**
  230. * Run a specific function defined in the plugin's code to locate every
  231. * directory and file, collect their checksum and file size, and send this
  232. * information to the Sucuri API service where a security and integrity scan
  233. * will be performed against the hashes provided and the official versions.
  234. */
  235. add_action( 'sucuriscan_scheduled_scan', 'SucuriScanEvent::filesystem_scan' );
  236. /**
  237. * Initialize the execute of the main plugin's functions.
  238. *
  239. * This will load the menu options in the WordPress administrator panel, and
  240. * execute the bootstrap function of the plugin.
  241. */
  242. add_action( 'init', 'SucuriScanInterface::initialize', 1 );
  243. add_action( 'admin_init', 'SucuriScanInterface::create_datastore_folder' );
  244. add_action( 'admin_init', 'SucuriScanInterface::handle_old_plugins' );
  245. add_action( 'admin_enqueue_scripts', 'SucuriScanInterface::enqueue_scripts', 1 );
  246. add_action( 'admin_menu', 'SucuriScanInterface::add_interface_menu' );
  247. /**
  248. * Function call interceptors.
  249. *
  250. * Define the names for the hooks that will intercept specific function calls in
  251. * the admin interface and parts of the external site, an event report will be
  252. * sent to the API service and an email notification to the administrator of the
  253. * site.
  254. *
  255. * @see Class SucuriScanHook
  256. */
  257. if ( class_exists( 'SucuriScanHook' ) ){
  258. $sucuriscan_hooks = array(
  259. // Passes.
  260. 'add_attachment',
  261. 'add_link',
  262. 'create_category',
  263. 'delete_post',
  264. 'delete_user',
  265. 'login_form_resetpass',
  266. 'private_to_published',
  267. 'publish_page',
  268. 'publish_post',
  269. 'publish_phone',
  270. 'xmlrpc_publish_post',
  271. 'retrieve_password',
  272. 'switch_theme',
  273. 'user_register',
  274. 'wp_login',
  275. 'wp_login_failed',
  276. 'wp_trash_post',
  277. );
  278. foreach ( $sucuriscan_hooks as $hook_name ){
  279. $hook_func = 'SucuriScanHook::hook_' . $hook_name;
  280. add_action( $hook_name, $hook_func, 50 );
  281. }
  282. add_action( 'admin_init', 'SucuriScanHook::hook_undefined_actions' );
  283. add_action( 'login_form', 'SucuriScanHook::hook_undefined_actions' );
  284. } else {
  285. SucuriScanInterface::error( 'Function call interceptors are not working properly.' );
  286. }
  287. /**
  288. * Display a message if the plugin is not activated.
  289. *
  290. * Display a message at the top of the administration panel with a button that
  291. * once clicked will send the site's email and domain name to the Sucuri API
  292. * service where an API key will be generated for the site, this key will allow
  293. * the plugin to execute the filesystem scans, the project integrity, and the
  294. * email notifications.
  295. */
  296. add_action( 'admin_notices', 'SucuriScanInterface::setup_notice' );
  297. /**
  298. * Heartbeat API
  299. *
  300. * Update the settings of the Heartbeat API according to the values set by an
  301. * administrator. This tool may cause an increase in the CPU usage, a bad
  302. * configuration may cause low account to run out of resources, but in better
  303. * cases it may improve the performance of the site by reducing the quantity of
  304. * requests sent to the server per session.
  305. */
  306. add_filter( 'init', 'SucuriScanHeartbeat::register_script', 1 );
  307. add_filter( 'heartbeat_settings', 'SucuriScanHeartbeat::update_settings' );
  308. add_filter( 'heartbeat_send', 'SucuriScanHeartbeat::respond_to_send', 10, 3 );
  309. add_filter( 'heartbeat_received', 'SucuriScanHeartbeat::respond_to_received', 10, 3 );
  310. add_filter( 'heartbeat_nopriv_send', 'SucuriScanHeartbeat::respond_to_send', 10, 3 );
  311. add_filter( 'heartbeat_nopriv_received', 'SucuriScanHeartbeat::respond_to_received', 10, 3 );
  312. }
  313. /**
  314. * Miscellaneous library.
  315. *
  316. * Multiple and generic functions that will be used through out the code of
  317. * other libraries extending from this and functions defined in other files, be
  318. * aware of the hierarchy and check the other libraries for duplicated methods.
  319. */
  320. class SucuriScan {
  321. /**
  322. * Class constructor.
  323. */
  324. public function __construct(){
  325. }
  326. /**
  327. * Return name of a variable with the plugin's prefix (if needed).
  328. *
  329. * To facilitate the development, you can prefix the name of the key in the
  330. * request (when accessing it) with a single colon, this function will
  331. * automatically replace that character with the unique identifier of the
  332. * plugin.
  333. *
  334. * @param string $var_name Name of a variable with an optional colon at the beginning.
  335. * @return string Full name of the variable with the extra characters (if needed).
  336. */
  337. public static function variable_prefix( $var_name = '' ){
  338. if ( preg_match( '/^:(.*)/', $var_name, $match ) ){
  339. $var_name = sprintf( '%s_%s', SUCURISCAN, $match[1] );
  340. }
  341. return $var_name;
  342. }
  343. /**
  344. * Gets the value of a configuration option.
  345. *
  346. * @param string $property The configuration option name.
  347. * @return string Value of the configuration option as a string on success.
  348. */
  349. public static function ini_get( $property = '' ){
  350. $ini_value = ini_get( $property );
  351. if ( empty($ini_value) || is_null( $ini_value ) ){
  352. switch ( $property ){
  353. case 'error_log': $ini_value = 'error_log'; break;
  354. case 'safe_mode': $ini_value = 'Off'; break;
  355. case 'allow_url_fopen': $ini_value = '1'; break;
  356. case 'memory_limit': $ini_value = '128M'; break;
  357. case 'upload_max_filesize': $ini_value = '2M'; break;
  358. case 'post_max_size': $ini_value = '8M'; break;
  359. case 'max_execution_time': $ini_value = '30'; break;
  360. case 'max_input_time': $ini_value = '-1'; break;
  361. }
  362. }
  363. if ( $property == 'error_log' ) {
  364. $ini_value = basename( $ini_value );
  365. }
  366. return $ini_value;
  367. }
  368. /**
  369. * Encodes the less-than, greater-than, ampersand, double quote and single quote
  370. * characters, will never double encode entities.
  371. *
  372. * @param string $text The text which is to be encoded.
  373. * @return string The encoded text with HTML entities.
  374. */
  375. public static function escape( $text = '' ){
  376. // Escape the value of the variable using a built-in function if possible.
  377. if ( function_exists( 'esc_attr' ) ){
  378. $text = esc_attr( $text );
  379. } else {
  380. $text = htmlspecialchars( $text );
  381. }
  382. return $text;
  383. }
  384. /**
  385. * Generates a lowercase random string with an specific length.
  386. *
  387. * @param integer $length Length of the string that will be generated.
  388. * @return string The random string generated.
  389. */
  390. public static function random_char( $length = 4 ){
  391. $string = '';
  392. $chars = range( 'a','z' );
  393. for ( $i = 0; $i < $length; $i++ ){
  394. $string .= $chars[ rand( 0, count( $chars ) -1 ) ];
  395. }
  396. return $string;
  397. }
  398. /**
  399. * Translate a given number in bytes to a human readable file size using the
  400. * a approximate value in Kylo, Mega, Giga, etc.
  401. *
  402. * @link http://www.php.net/manual/en/function.filesize.php#106569
  403. * @param integer $bytes An integer representing a file size in bytes.
  404. * @param integer $decimals How many decimals should be returned after the translation.
  405. * @return string Human readable representation of the given number in Kylo, Mega, Giga, etc.
  406. */
  407. public static function human_filesize( $bytes = 0, $decimals = 2 ){
  408. $sz = 'BKMGTP';
  409. $factor = floor( (strlen( $bytes ) - 1) / 3 );
  410. return sprintf( "%.{$decimals}f", $bytes / pow( 1024, $factor ) ) . @$sz[ $factor ];
  411. }
  412. /**
  413. * Returns the system filepath to the relevant user uploads directory for this
  414. * site. This is a multisite capable function.
  415. *
  416. * @param string $path The relative path that needs to be completed to get the absolute path.
  417. * @return string The full filesystem path including the directory specified.
  418. */
  419. public static function datastore_folder_path( $path = '' ){
  420. $datastore_path = SucuriScanOption::get_option( ':datastore_path' );
  421. $datastore_dirname = 'sucuri';
  422. // Use the uploads folder by default.
  423. if ( empty($datastore_path) ) {
  424. $uploads_path = false;
  425. // Multisite installations may have different paths.
  426. if ( function_exists( 'wp_upload_dir' ) ) {
  427. $upload_dir = wp_upload_dir();
  428. if ( isset($upload_dir['basedir']) ) {
  429. $uploads_path = rtrim( $upload_dir['basedir'], '/' );
  430. }
  431. }
  432. if ( $uploads_path === false ) {
  433. if ( defined( 'WP_CONTENT_DIR' ) ) {
  434. $uploads_path = rtrim( WP_CONTENT_DIR, '/' ) . '/uploads';
  435. } else {
  436. $uploads_path = rtrim( ABSPATH, '/' ) . '/wp-content/uploads';
  437. }
  438. }
  439. $datastore_path = $uploads_path . '/' . $datastore_dirname;
  440. SucuriScanOption::update_option( ':datastore_path', $datastore_path );
  441. }
  442. $wp_filepath = rtrim( $datastore_path, '/' ) . '/' . $path;
  443. return $wp_filepath;
  444. }
  445. /**
  446. * Check whether the current site is working as a multi-site instance.
  447. *
  448. * @return boolean Either TRUE or FALSE in case WordPress is being used as a multi-site instance.
  449. */
  450. public static function is_multisite(){
  451. if (
  452. function_exists( 'is_multisite' )
  453. && is_multisite()
  454. ){
  455. return true;
  456. }
  457. return false;
  458. }
  459. /**
  460. * Find and retrieve the current version of Wordpress installed.
  461. *
  462. * @return string The version number of Wordpress installed.
  463. */
  464. public static function site_version(){
  465. global $wp_version;
  466. if ( $wp_version === null ) {
  467. $wp_version_path = ABSPATH . WPINC . '/version.php';
  468. if ( file_exists( $wp_version_path ) ) {
  469. include($wp_version_path);
  470. $wp_version = isset($wp_version) ? $wp_version : '0.0';
  471. }
  472. else {
  473. $option_version = get_option( 'version' );
  474. $wp_version = $option_version ? $option_version : '0.0';
  475. }
  476. }
  477. $wp_version = self::escape( $wp_version );
  478. return $wp_version;
  479. }
  480. /**
  481. * Find and retrieve the absolute path of the WordPress configuration file.
  482. *
  483. * @return string Absolute path of the WordPress configuration file.
  484. */
  485. public static function get_wpconfig_path(){
  486. if ( defined( 'ABSPATH' ) ){
  487. $file_path = ABSPATH . '/wp-config.php';
  488. // if wp-config.php doesn't exist, or is not readable check one directory up.
  489. if ( ! file_exists( $file_path ) ){
  490. $file_path = ABSPATH . '/../wp-config.php';
  491. }
  492. // Remove duplicated double slashes.
  493. $file_path = @realpath( $file_path );
  494. if ( $file_path ){
  495. return $file_path;
  496. }
  497. }
  498. return false;
  499. }
  500. /**
  501. * Find and retrieve the absolute path of the main WordPress htaccess file.
  502. *
  503. * @return string Absolute path of the main WordPress htaccess file.
  504. */
  505. public static function get_htaccess_path(){
  506. if ( defined( 'ABSPATH' ) ){
  507. $base_dirs = array(
  508. rtrim( ABSPATH, '/' ),
  509. dirname( ABSPATH ),
  510. dirname( dirname( ABSPATH ) ),
  511. );
  512. foreach ( $base_dirs as $base_dir ){
  513. $htaccess_path = sprintf( '%s/.htaccess', $base_dir );
  514. if ( file_exists( $htaccess_path ) ){
  515. return $htaccess_path;
  516. }
  517. }
  518. }
  519. return false;
  520. }
  521. /**
  522. * Get the pattern of the definition related with a WordPress secret key.
  523. *
  524. * @return string Secret key definition pattern.
  525. */
  526. public static function secret_key_pattern(){
  527. return '/define\((\s+)?\'([A-Z_]+)\',(\s+)?\'(.*)\'(\s+)?\);/';
  528. }
  529. /**
  530. * Retrieve the real ip address of the user in the current request.
  531. *
  532. * @param boolean $return_header Whether the header name where the address was found must be returned.
  533. * @return string The real ip address of the user in the current request.
  534. */
  535. public static function get_remote_addr( $return_header = false ){
  536. $remote_addr = '';
  537. $header_used = 'unknown';
  538. if (
  539. self::support_reverse_proxy()
  540. || self::is_behind_cloudproxy()
  541. ) {
  542. $alternatives = array(
  543. 'HTTP_X_SUCURI_CLIENTIP',
  544. 'HTTP_X_REAL_IP',
  545. 'HTTP_CLIENT_IP',
  546. 'HTTP_X_FORWARDED_FOR',
  547. 'HTTP_X_FORWARDED',
  548. 'HTTP_FORWARDED_FOR',
  549. 'HTTP_FORWARDED',
  550. 'SUCURI_RIP',
  551. 'REMOTE_ADDR',
  552. );
  553. foreach ( $alternatives as $alternative ){
  554. if (
  555. isset($_SERVER[ $alternative ])
  556. && self::is_valid_ip( $_SERVER[ $alternative ] )
  557. ){
  558. $remote_addr = $_SERVER[ $alternative ];
  559. $header_used = $alternative;
  560. break;
  561. }
  562. }
  563. }
  564. elseif ( isset($_SERVER['REMOTE_ADDR']) ) {
  565. $remote_addr = $_SERVER['REMOTE_ADDR'];
  566. $header_used = 'REMOTE_ADDR';
  567. }
  568. if ( $remote_addr == '::1' ){
  569. $remote_addr = '127.0.0.1';
  570. }
  571. if ( $return_header ){
  572. return $header_used;
  573. }
  574. return $remote_addr;
  575. }
  576. /**
  577. * Return the HTTP header used to retrieve the remote address.
  578. *
  579. * @return string The HTTP header used to retrieve the remote address.
  580. */
  581. public static function get_remote_addr_header(){
  582. return self::get_remote_addr( true );
  583. }
  584. /**
  585. * Retrieve the user-agent from the current request.
  586. *
  587. * @return string The user-agent from the current request.
  588. */
  589. public static function get_user_agent(){
  590. if ( isset($_SERVER['HTTP_USER_AGENT']) ){
  591. return self::escape( $_SERVER['HTTP_USER_AGENT'] );
  592. }
  593. return false;
  594. }
  595. /**
  596. * Get the clean version of the current domain.
  597. *
  598. * @return string The domain of the current site.
  599. */
  600. public static function get_domain( $return_tld = false ){
  601. if ( function_exists( 'get_site_url' ) ) {
  602. $site_url = get_site_url();
  603. $pattern = '/([fhtps]+:\/\/)?([^:\/]+)(:[0-9:]+)?(\/.*)?/';
  604. $replacement = ( $return_tld === true ) ? '$2' : '$2$3$4';
  605. $domain_name = @preg_replace( $pattern, $replacement, $site_url );
  606. return $domain_name;
  607. }
  608. return false;
  609. }
  610. /**
  611. * Get top-level domain (TLD) of the website.
  612. *
  613. * @return string Top-level domain (TLD) of the website.
  614. */
  615. public static function get_top_level_domain(){
  616. return self::get_domain( true );
  617. }
  618. /**
  619. * Check whether reverse proxy servers must be supported.
  620. *
  621. * @return boolean TRUE if reverse proxies must be supported, FALSE otherwise.
  622. */
  623. public static function support_reverse_proxy(){
  624. return (bool) ( SucuriScanOption::get_option( ':revproxy' ) === 'enabled' );
  625. }
  626. /**
  627. * Check whether the site is behing the Sucuri CloudProxy network.
  628. *
  629. * @param boolean $verbose Return an array with the hostname, address, and status, or not.
  630. * @return boolean Either TRUE or FALSE if the site is behind CloudProxy.
  631. */
  632. public static function is_behind_cloudproxy( $verbose = false ){
  633. $http_host = self::get_top_level_domain();
  634. $host_by_addr = @gethostbyname( $http_host );
  635. $host_by_name = @gethostbyaddr( $host_by_addr );
  636. $status = (bool) preg_match( '/^cloudproxy[0-9]+\.sucuri\.net$/', $host_by_name );
  637. /*
  638. * If the DNS reversion failed but the CloudProxy API key is set, then consider
  639. * the site as protected by a firewall. A fake key can be used to bypass the DNS
  640. * checking, but that is not something that will affect us, only the client.
  641. */
  642. if (
  643. $status === false
  644. && SucuriScanAPI::get_cloudproxy_key()
  645. ) {
  646. $status = true;
  647. }
  648. if ( $verbose ){
  649. return array(
  650. 'http_host' => $http_host,
  651. 'host_name' => $host_by_name,
  652. 'host_addr' => $host_by_addr,
  653. 'status' => $status,
  654. );
  655. }
  656. return $status;
  657. }
  658. /**
  659. * Get the email address set by the administrator to receive the notifications
  660. * sent by the plugin, if the email is missing the WordPress email address is
  661. * chosen by default.
  662. *
  663. * @return string The administrator email address.
  664. */
  665. public static function get_site_email(){
  666. $email = get_option( 'admin_email' );
  667. if ( self::is_valid_email( $email ) ){
  668. return $email;
  669. }
  670. return false;
  671. }
  672. /**
  673. * Returns the current time measured in the number of seconds since the Unix Epoch.
  674. *
  675. * @return integer Return current Unix timestamp.
  676. */
  677. public static function local_time(){
  678. if ( function_exists( 'current_time' ) ){
  679. return current_time( 'timestamp' );
  680. } else {
  681. return time();
  682. }
  683. }
  684. /**
  685. * Retrieve the date in localized format, based on timestamp.
  686. *
  687. * If the locale specifies the locale month and weekday, then the locale will
  688. * take over the format for the date. If it isn't, then the date format string
  689. * will be used instead.
  690. *
  691. * @param integer $timestamp Unix timestamp.
  692. * @return string The date, translated if locale specifies it.
  693. */
  694. public static function datetime( $timestamp = 0 ){
  695. if ( is_numeric( $timestamp ) && $timestamp > 0 ){
  696. $date_format = get_option( 'date_format' );
  697. $time_format = get_option( 'time_format' );
  698. $timezone_format = sprintf( '%s %s', $date_format, $time_format );
  699. return date_i18n( $timezone_format, $timestamp );
  700. }
  701. return null;
  702. }
  703. /**
  704. * Retrieve the date in localized format based on the current time.
  705. *
  706. * @return string The date, translated if locale specifies it.
  707. */
  708. public static function current_datetime(){
  709. $local_time = self::local_time();
  710. return self::datetime( $local_time );
  711. }
  712. /**
  713. * Return the time passed since the specified timestamp until now.
  714. *
  715. * @param integer $timestamp The Unix time number of the date/time before now.
  716. * @return string The time passed since the timestamp specified.
  717. */
  718. public static function time_ago( $timestamp = 0 ){
  719. if ( ! is_numeric( $timestamp ) ){
  720. $timestamp = strtotime( $timestamp );
  721. }
  722. $local_time = self::local_time();
  723. $diff = abs( $local_time - intval( $timestamp ) );
  724. if ( $diff == 0 ){ return 'just now'; }
  725. $intervals = array(
  726. 1 => array( 'year', 31556926, ),
  727. $diff < 31556926 => array( 'month', 2592000, ),
  728. $diff < 2592000 => array( 'week', 604800, ),
  729. $diff < 604800 => array( 'day', 86400, ),
  730. $diff < 86400 => array( 'hour', 3600, ),
  731. $diff < 3600 => array( 'minute', 60, ),
  732. $diff < 60 => array( 'second', 1, ),
  733. );
  734. $value = floor( $diff / $intervals[1][1] );
  735. $time_ago = sprintf(
  736. '%s %s%s ago',
  737. $value,
  738. $intervals[1][0],
  739. ( $value > 1 ? 's' : '' )
  740. );
  741. return $time_ago;
  742. }
  743. /**
  744. * Convert an string of characters into a valid variable name.
  745. *
  746. * @see http://www.php.net/manual/en/language.variables.basics.php
  747. *
  748. * @param string $text A text containing alpha-numeric and special characters.
  749. * @return string A valid variable name.
  750. */
  751. public static function human2var( $text = '' ){
  752. $text = strtolower( $text );
  753. $pattern = '/[^a-z0-9_]/';
  754. $var_name = preg_replace( $pattern, '_', $text );
  755. return $var_name;
  756. }
  757. /**
  758. * Check whether a variable contains a serialized data or not.
  759. *
  760. * @param string $data The data that will be checked.
  761. * @return boolean TRUE if the data was serialized, FALSE otherwise.
  762. */
  763. public static function is_serialized( $data = '' ){
  764. return ( is_string( $data ) && preg_match( '/^(a|O):[0-9]+:.+/', $data ) );
  765. }
  766. /**
  767. * Check whether an IP address has a valid format or not.
  768. *
  769. * @param string $remote_addr The host IP address.
  770. * @return boolean Whether the IP address specified is valid or not.
  771. */
  772. public static function is_valid_ip( $remote_addr = '' ){
  773. // Check for IPv4 and IPv6.
  774. if ( function_exists( 'filter_var' ) ){
  775. return (bool) filter_var( $remote_addr, FILTER_VALIDATE_IP );
  776. }
  777. // Assuming older version of PHP and server, so only will check for IPv4.
  778. elseif ( strlen( $remote_addr ) >= 7 ) {
  779. $pattern = '/^([0-9]{1,3}\.){3}[0-9]{1,3}$/';
  780. if ( preg_match( $pattern, $remote_addr, $match ) ){
  781. for ( $i = 0; $i < 4; $i++ ){
  782. if ( $match[ $i ] > 255 ){ return false; }
  783. }
  784. return true;
  785. }
  786. }
  787. return false;
  788. }
  789. /**
  790. * Check whether an IP address is formatted as CIDR or not.
  791. *
  792. * @param string $remote_addr The supposed ip address that will be checked.
  793. * @return boolean Either TRUE or FALSE if the ip address specified is valid or not.
  794. */
  795. public static function is_valid_cidr( $remote_addr = '' ){
  796. if ( preg_match( '/^([0-9\.]{7,15})\/(8|16|24)$/', $remote_addr, $match ) ) {
  797. if ( self::is_valid_ip( $match[1] ) ) {
  798. return true;
  799. }
  800. }
  801. return false;
  802. }
  803. /**
  804. * Separate the parts of an IP address.
  805. *
  806. * @param string $remote_addr The supposed ip address that will be formatted.
  807. * @return array Clean address, CIDR range, and CIDR format; FALSE otherwise.
  808. */
  809. public static function get_ip_info( $remote_addr = '' ){
  810. if ( $remote_addr ) {
  811. $ip_parts = explode( '/', $remote_addr );
  812. if (
  813. array_key_exists( 0, $ip_parts )
  814. && self::is_valid_ip( $ip_parts[0] )
  815. ) {
  816. $addr_info = array();
  817. $addr_info['remote_addr'] = $ip_parts[0];
  818. $addr_info['cidr_range'] = isset($ip_parts[1]) ? $ip_parts[1] : '32';
  819. $addr_info['cidr_format'] = $addr_info['remote_addr'] . '/' . $addr_info['cidr_range'];
  820. return $addr_info;
  821. }
  822. }
  823. return false;
  824. }
  825. /**
  826. * Validate email address.
  827. *
  828. * This use the native PHP function filter_var which is available in PHP >=
  829. * 5.2.0 if it is not found in the interpreter this function will sue regular
  830. * expressions to check whether the email address passed is valid or not.
  831. *
  832. * @see http://www.php.net/manual/en/function.filter-var.php
  833. *
  834. * @param string $email The string that will be validated as an email address.
  835. * @return boolean TRUE if the email address passed to the function is valid, FALSE if not.
  836. */
  837. public static function is_valid_email( $email = '' ){
  838. if ( function_exists( 'filter_var' ) ){
  839. return (bool) filter_var( $email, FILTER_VALIDATE_EMAIL );
  840. } else {
  841. $pattern = '/^([a-z0-9\+_\-]+)(\.[a-z0-9\+_\-]+)*@([a-z0-9\-]+\.)+[a-z]{2,6}$/ix';
  842. return (bool) preg_match( $pattern, $email );
  843. }
  844. }
  845. /**
  846. * Return a string with all the valid email addresses.
  847. *
  848. * @param string $email The string that will be validated as an email address.
  849. * @param boolean $as_array TRUE to return the list of valid email addresses as an array.
  850. * @return string All the valid email addresses separated by a comma.
  851. */
  852. public static function get_valid_email( $email = '', $as_array = false ){
  853. $valid_emails = array();
  854. $is_valid_string = (bool) ( is_string( $email ) && ! empty($email) );
  855. if (
  856. $is_valid_string === true
  857. && strpos( $email, ',' ) !== false
  858. ) {
  859. $addresses = explode( ',', $email );
  860. foreach ( $addresses as $address ){
  861. $address = trim( $address );
  862. if ( self::is_valid_email( $address ) ){
  863. $valid_emails[] = $address;
  864. }
  865. }
  866. }
  867. elseif (
  868. $is_valid_string === true
  869. && self::is_valid_email( $email )
  870. ) {
  871. $valid_emails[] = $email;
  872. }
  873. if ( ! empty($valid_emails) ) {
  874. $valid_emails = array_unique( $valid_emails );
  875. if ( $as_array === true ) {
  876. return $valid_emails;
  877. }
  878. return self::implode( ', ', $valid_emails );
  879. }
  880. return false;
  881. }
  882. /**
  883. * Cut a long text to the length specified, and append suspensive points at the end.
  884. *
  885. * @param string $text String of characters that will be cut.
  886. * @param integer $length Maximum length of the returned string, default is 10.
  887. * @return string Short version of the text specified.
  888. */
  889. public static function excerpt( $text = '', $length = 10 ){
  890. $text_length = strlen( $text );
  891. if ( $text_length > $length ){
  892. return substr( $text, 0, $length ) . '...';
  893. }
  894. return $text;
  895. }
  896. /**
  897. * Same as the excerpt method but with the string reversed.
  898. *
  899. * @param string $text String of characters that will be cut.
  900. * @param integer $length Maximum length of the returned string, default is 10.
  901. * @return string Short version of the text specified.
  902. */
  903. public static function excerpt_rev( $text = '', $length = 10 ){
  904. $str_reversed = strrev( $text );
  905. $str_excerpt = self::excerpt( $str_reversed, $length );
  906. $text_transformed = strrev( $str_excerpt );
  907. return $text_transformed;
  908. }
  909. /**
  910. * Check whether an list is a multidimensional array or not.
  911. *
  912. * @param array $list An array or multidimensional array of different values.
  913. * @return boolean TRUE if the list is multidimensional, FALSE otherwise.
  914. */
  915. public static function is_multi_list( $list = array() ){
  916. if ( ! empty($list) ){
  917. foreach ( (array) $list as $item ) {
  918. if ( is_array( $item ) ) {
  919. return true;
  920. }
  921. }
  922. }
  923. return false;
  924. }
  925. /**
  926. * Join array elements with a string no matter if it is multidimensional.
  927. *
  928. * @param string $separator Character that will act as a separator, default to an empty string.
  929. * @param array $list The array of strings to implode.
  930. * @return string String of all the items in the list, with the separator between them.
  931. */
  932. public static function implode( $separator = '', $list = array() ){
  933. if ( self::is_multi_list( $list ) ){
  934. $pieces = array();
  935. foreach ( $list as $items ){
  936. $pieces[] = @implode( $separator, $items );
  937. }
  938. $joined_pieces = '(' . implode( '), (', $pieces ) . ')';
  939. return $joined_pieces;
  940. } else {
  941. return implode( $separator, $list );
  942. }
  943. }
  944. /**
  945. * Determine if the plugin notices can be displayed in the current page.
  946. *
  947. * @param string $current_page Identifier of the current page.
  948. * @return boolean TRUE if the current page must not have noticies.
  949. */
  950. public static function no_notices_here( $current_page = false ){
  951. global $sucuriscan_no_notices_in;
  952. if ( $current_page === false ) {
  953. $current_page = SucuriScanRequest::get( 'page' );
  954. }
  955. if (
  956. isset($sucuriscan_no_notices_in)
  957. && is_array( $sucuriscan_no_notices_in )
  958. && ! empty($sucuriscan_no_notices_in)
  959. ) {
  960. return (bool) in_array( $current_page, $sucuriscan_no_notices_in );
  961. }
  962. return false;
  963. }
  964. /**
  965. * Check whether the site is running over the Nginx web server.
  966. *
  967. * @return boolean TRUE if the site is running over Nginx, FALSE otherwise.
  968. */
  969. public static function is_nginx_server(){
  970. return (bool) preg_match( '/^nginx(\/[0-9\.]+)?$/', @$_SERVER['SERVER_SOFTWARE'] );
  971. }
  972. /**
  973. * Check whether the site is running over the Nginx web server.
  974. *
  975. * @return boolean TRUE if the site is running over Nginx, FALSE otherwise.
  976. */
  977. public static function is_iis_server(){
  978. return (bool) preg_match( '/Microsoft-IIS/i', @$_SERVER['SERVER_SOFTWARE'] );
  979. }
  980. }
  981. /**
  982. * HTTP request handler.
  983. *
  984. * Function definitions to retrieve, validate, and clean the parameters during a
  985. * HTTP request, generally after a form submission or while loading a URL. Use
  986. * these methods at most instead of accessing an index in the global PHP
  987. * variables _POST, _GET, _REQUEST since they may come with insecure data.
  988. */
  989. class SucuriScanRequest extends SucuriScan {
  990. /**
  991. * Returns the value stored in a specific index in the global _GET, _POST or
  992. * _REQUEST variables, you can specify a pattern as the second argument to
  993. * match allowed values.
  994. *
  995. * @param array $list The array where the specified key will be searched.
  996. * @param string $key Name of the index where the requested variable is supposed to be.
  997. * @param string $pattern Optional pattern to match allowed values in the requested key.
  998. * @return string The value stored in the specified key inside the global _GET variable.
  999. */
  1000. public static function request( $list = array(), $key = '', $pattern = '' ){
  1001. $key = self::variable_prefix( $key );
  1002. if (
  1003. is_array( $list )
  1004. && is_string( $key )
  1005. && isset($list[ $key ])
  1006. ){
  1007. // Select the key from the list and escape its content.
  1008. $key_value = $list[ $key ];
  1009. // Define regular expressions for specific value types.
  1010. if ( $pattern === '' ){
  1011. $pattern = '/.*/';
  1012. } else {
  1013. switch ( $pattern ){
  1014. case '_nonce': $pattern = '/^[a-z0-9]{10}$/'; break;
  1015. case '_page': $pattern = '/^[a-z_]+$/'; break;
  1016. case '_array': $pattern = '_array'; break;
  1017. case '_yyyymmdd': $pattern = '/^[0-9]{4}(\-[0-9]{2}){2}$/'; break;
  1018. default: $pattern = '/^'.$pattern.'$/'; break;
  1019. }
  1020. }
  1021. // If the request data is an array, then only cast the value.
  1022. if ( $pattern == '_array' && is_array( $key_value ) ){
  1023. return (array) $key_value;
  1024. }
  1025. // Check the format of the request data with a regex defined above.
  1026. if ( @preg_match( $pattern, $key_value ) ){
  1027. return self::escape( $key_value );
  1028. }
  1029. }
  1030. return false;
  1031. }
  1032. /**
  1033. * Returns the value stored in a specific index in the global _GET variable,
  1034. * you can specify a pattern as the second argument to match allowed values.
  1035. *
  1036. * @param string $key Name of the index where the requested variable is supposed to be.
  1037. * @param string $pattern Optional pattern to match allowed values in the requested key.
  1038. * @return string The value stored in the specified key inside the global _GET variable.
  1039. */
  1040. public static function get( $key = '', $pattern = '' ){
  1041. return self::request( $_GET, $key, $pattern );
  1042. }
  1043. /**
  1044. * Returns the value stored in a specific index in the global _POST variable,
  1045. * you can specify a pattern as the second argument to match allowed values.
  1046. *
  1047. * @param string $key Name of the index where the requested variable is supposed to be.
  1048. * @param string $pattern Optional pattern to match allowed values in the requested key.
  1049. * @return string The value stored in the specified key inside the global _POST variable.
  1050. */
  1051. public static function post( $key = '', $pattern = '' ){
  1052. return self::request( $_POST, $key, $pattern );
  1053. }
  1054. /**
  1055. * Returns the value stored in a specific index in the global _REQUEST variable,
  1056. * you can specify a pattern as the second argument to match allowed values.
  1057. *
  1058. * @param string $key Name of the index where the requested variable is supposed to be.
  1059. * @param string $pattern Optional pattern to match allowed values in the requested key.
  1060. * @return string The value stored in the specified key inside the global _POST variable.
  1061. */
  1062. public static function get_or_post( $key = '', $pattern = '' ){
  1063. return self::request( $_REQUEST, $key, $pattern );
  1064. }
  1065. }
  1066. /**
  1067. * Class to process files and folders.
  1068. *
  1069. * Here are implemented the functions needed to open, scan, read, create files
  1070. * and folders using the built-in PHP class SplFileInfo. The SplFileInfo class
  1071. * offers a high-level object oriented interface to information for an individual
  1072. * file.
  1073. */
  1074. class SucuriScanFileInfo extends SucuriScan {
  1075. /**
  1076. * Define the interface that will be used to execute the file system scans, the
  1077. * available options are SPL, OpenDir, and Glob (all in lowercase). This can be
  1078. * configured from the settings page.
  1079. *
  1080. * @var string
  1081. */
  1082. public $scan_interface = 'spl';
  1083. /**
  1084. * Whether the list of files that can be ignored from the filesystem scan will
  1085. * be used to return the directory tree, this should be disabled when scanning a
  1086. * directory without the need to filter the items in the list.
  1087. *
  1088. * @var boolean
  1089. */
  1090. public $ignore_files = true;
  1091. /**
  1092. * Whether the list of folders that can be ignored from the filesystem scan will
  1093. * be used to return the directory tree, this should be disabled when scanning a
  1094. * path without the need to filter the items in the list.
  1095. *
  1096. * @var boolean
  1097. */
  1098. public $ignore_directories = true;
  1099. /**
  1100. * A list of ignored directory paths, these folders will be skipped during the
  1101. * execution of the file system scans, and any sub-directory or files inside
  1102. * these paths will be ignored too.
  1103. *
  1104. * @see SucuriScanFSScanner.get_ignored_directories()
  1105. * @var array
  1106. */
  1107. private $ignored_directories = array();
  1108. /**
  1109. * Whether the filesystem scanner should run recursively or not.
  1110. *
  1111. * @var boolean
  1112. */
  1113. public $run_recursively = true;
  1114. /**
  1115. * Class constructor.
  1116. */
  1117. public function __construct(){
  1118. }
  1119. /**
  1120. * Retrieve a long text string with signatures of all the files contained
  1121. * in the main and subdirectories of the folder specified, also the filesize
  1122. * and md5sum of that file. Some folders and files will be ignored depending
  1123. * on some rules defined by the developer.
  1124. *
  1125. * @param string $directory Parent directory where the filesystem scan will start.
  1126. * @param boolean $as_array Whether the result of the operation will be returned as an array or string.
  1127. * @return array List of files in the main and subdirectories of the folder specified.
  1128. */
  1129. public function get_directory_tree_md5( $directory = '', $as_array = false ){
  1130. $project_signatures = '';
  1131. $abs_path = rtrim( ABSPATH, DIRECTORY_SEPARATOR );
  1132. $files = $this->get_directory_tree( $directory );
  1133. if ( $as_array ){
  1134. $project_signatures = array();
  1135. }
  1136. if ( $files ){
  1137. sort( $files );
  1138. foreach ( $files as $filepath ){
  1139. $file_checksum = @md5_file( $filepath );
  1140. $filesize = @filesize( $filepath );
  1141. if ( $as_array ){
  1142. $basename = str_replace( $abs_path . DIRECTORY_SEPARATOR, '', $filepath );
  1143. $project_signatures[ $basename ] = array(
  1144. 'filepath' => $filepath,
  1145. 'checksum' => $file_checksum,
  1146. 'filesize' => $filesize,
  1147. 'created_at' => @filectime( $filepath ),
  1148. 'modified_at' => @filemtime( $filepath ),
  1149. );
  1150. } else {
  1151. $filepath = str_replace( $abs_path, $abs_path . DIRECTORY_SEPARATOR, $filepath );
  1152. $project_signatures .= sprintf(
  1153. "%s%s%s%s\n",
  1154. $file_checksum,
  1155. $filesize,
  1156. chr( 32 ),
  1157. $filepath
  1158. );
  1159. }
  1160. }
  1161. }
  1162. return $project_signatures;
  1163. }
  1164. /**
  1165. * Retrieve a list with all the files contained in the main and subdirectories
  1166. * of the folder specified. Some folders and files will be ignored depending
  1167. * on some rules defined by the developer.
  1168. *
  1169. * @param string $directory Parent directory where the filesystem scan will start.
  1170. * @return array List of files in the main and subdirectories of the folder specified.
  1171. */
  1172. public function get_directory_tree( $directory = '' ){
  1173. if ( file_exists( $directory ) && is_dir( $directory ) ){
  1174. $tree = array();
  1175. // Check whether the ignore scanning feature is enabled or not.
  1176. if ( SucuriScanFSScanner::will_ignore_scanning() ){
  1177. $this->ignored_directories = SucuriScanFSScanner::get_ignored_directories();
  1178. }
  1179. switch ( $this->scan_interface ){
  1180. case 'spl':
  1181. if ( $this->is_spl_available() ){
  1182. $tree = $this->get_directory_tree_with_spl( $directory );
  1183. } else {
  1184. $this->scan_interface = 'opendir';
  1185. $tree = $this->get_directory_tree( $directory );
  1186. }
  1187. break;
  1188. case 'glob':
  1189. $tree = $this->get_directory_tree_with_glob( $directory );
  1190. break;
  1191. case 'opendir':
  1192. $tree = $this->get_directory_tree_with_opendir( $directory );
  1193. break;
  1194. default:
  1195. $this->scan_interface = 'spl';
  1196. $tree = $this->get_directory_tree( $directory );
  1197. break;
  1198. }
  1199. return $tree;
  1200. }
  1201. return false;
  1202. }
  1203. /**
  1204. * Find a file under the directory tree specified.
  1205. *
  1206. * @param string $filename Name of the folder or file being scanned at the moment.
  1207. * @param string $directory Directory where the scanner is located at the moment.
  1208. * @return array List of file paths where the file was found.
  1209. */
  1210. public function find_file( $filename = '', $directory = null ){
  1211. $file_paths = array();

Large files files are truncated, but you can click here to view the full file