wp-config.php are disabled via a filter.', 'better-wp-security' ) ); } return self::remove( $file_path, $patterns ); } /** * Write the supplied modification to the wp-config.php file. * * @since 1.15.0 * @access protected * * @param string $modification The modification to add to the wp-config.php file. * @param bool $clear_existing_modifications Optional. Whether or not existing modifications should be removed * first. Defaults to true. * @return bool|WP_Error Boolean true on success or a WP_Error object otherwise. */ protected static function write_wp_config( $modification, $clear_existing_modifications = true ) { $file_path = self::get_wp_config_file_path(); if ( empty( $file_path ) ) { return new WP_Error( 'itsec-lib-config-file-wp-config-file-updates-disabled', __( 'Updates to wp-config.php are disabled via a filter.', 'better-wp-security' ) ); } return self::update( $file_path, 'wp-config', $modification, $clear_existing_modifications ); } /** * Returns the contents of the file. * * @since 1.15.0 * @access protected * * @param string $file Config file to read. * @return string|WP_Error The contents of the file, an empty string if the file does not exist, or a WP_Error object on error. */ protected static function get_file_contents( $file ) { if ( ! ITSEC_Lib_File::exists( $file ) ) { return ''; } $contents = ITSEC_Lib_File::read( $file ); if ( is_wp_error( $contents ) ) { return new WP_Error( 'itsec-lib-config-file-cannot-read-file', sprintf( __( 'Unable to read %1$s due to the following error: %2$s', 'better-wp-security' ), $file, $contents->get_error_message() ) ); } return $contents; } /** * Returns the contents of the file with the iThemes Security modifications removed. * * @since 1.15.0 * @access protected * * @param string $file Config file to read. * @param string $type The type of config file. Valid options are apache, nginx, and wp-config. * @return string|WP_Error The contents of the file with modifications removed, an empty string if the file does not exist, or a WP_Error object on error. */ protected static function get_file_contents_without_modification( $file, $type ) { $contents = self::get_file_contents( $file ); if ( is_wp_error( $contents ) ) { return $contents; } // Contents of just whitespace are treated as empty. if ( preg_match( '/^\s+$/', $contents ) ) { return ''; } $format_version = 0; // Attempt to retrieve config file details from the contents. if ( preg_match( '/iThemes\s+Security\s+Config\s+Details:\s+([^\s]+)/', $contents, $match ) ) { $details = explode( ':', $match[1] ); if ( isset( $details[0] ) && ( (string) intval( $details[0] ) === $details[0] ) ) { $format_version = intval( $details[0] ); } } $placeholder = self::get_placeholder(); // Ensure that the generated placeholder can be uniquely identified in the contents. while ( false !== strpos( $contents, $placeholder ) ) { $placeholder = self::get_placeholder(); } // Create a set of regex patterns to identify existing iThemes Security modifications. $comment_delimiter = self::get_comment_delimiter( $type ); $quoted_comment_delimiter = preg_quote( $comment_delimiter, '/' ); $line_ending = self::get_line_ending( $contents ); $patterns = array( array( 'begin' => "$quoted_comment_delimiter+\s*BEGIN\s+iThemes\s+Security", 'end' => "$quoted_comment_delimiter+\s*END\s+iThemes\s+Security", ), array( 'begin' => "$quoted_comment_delimiter+\s*BEGIN\s+Better\s+WP\s+Security", 'end' => "$quoted_comment_delimiter+\s*END\s+Better\s+WP\s+Security", ), ); // Remove matched content. foreach ( $patterns as $pattern ) { $contents = preg_replace( "/\s*{$pattern['begin']}.+?{$pattern['end']}[^\r\n]*\s*/is", "$line_ending$placeholder", $contents ); } if ( 'wp-config' === $type ) { // Special treatment for wp-config.php config data. // If the format is old or could not be detected, assume that cleanup of old modifications is required. if ( version_compare( $format_version, self::FORMAT_VERSION, '<' ) ) { // This code is clumsy, but it's the only way to remove the modifications given that the start and end // were not indicated in older versions. $contents = preg_replace( '/(<\?(?:php)?)?.+BWPS_FILECHECK.+/', "$1$line_ending$placeholder", $contents ); $contents = preg_replace( '/(<\?(?:php)?)?.+BWPS_AWAY_MODE.+/', "$1$line_ending$placeholder", $contents ); if ( preg_match( '|//\s*The entry below were created by iThemes Security to disable the file editor|', $contents ) ) { $contents = preg_replace( '%(<\?(?:php)?)?.*//\s*The entry below were created by iThemes Security to disable the file editor.*(?:\r\n|\r|\n)%', "$1$line_ending$placeholder", $contents ); $contents = preg_replace( '/(<\?(?:php)?)?.+DISALLOW_FILE_EDIT.+(?:\r\r\n|\r\n|\r|\n)/', "$1$line_ending$placeholder", $contents ); } if ( preg_match( '|//\s*The entries below were created by iThemes Security to enforce SSL|', $contents ) ) { $contents = preg_replace( '%(<\?(?:php)?)?.*//\s*The entries below were created by iThemes Security to enforce SSL.*(?:\r\n|\r|\n)%', "$1$line_ending$placeholder", $contents ); $contents = preg_replace( '/(<\?(?:php)?)?.+FORCE_SSL_LOGIN.+(?:\r\r\n|\r\n|\r|\n)/', "$1$line_ending$placeholder", $contents ); $contents = preg_replace( '/(<\?(?:php)?)?.+FORCE_SSL_ADMIN.+(?:\r\r\n|\r\n|\r|\n)/', "$1$line_ending$placeholder", $contents ); } } } // Remove adjacent placeholders. $contents = preg_replace( "/$placeholder(?:\s*$placeholder)+/", $placeholder, $contents ); // Remove whitespace from around the placeholders. $contents = preg_replace( "/\s*$placeholder\s*/", $placeholder, $contents ); // Placeholders at the beginning or end of the contents do not need to have newlines added. $contents = preg_replace( "/^$placeholder|$placeholder$/", '', $contents ); // Remaining placeholders are replaced with two newlines to leave a gap between sections of remaining contents. $contents = preg_replace( "/$placeholder/", "$line_ending$line_ending", $contents ); // Fix potentially damaged Windows-style newlines for W3 Total Cache modifications $translated_w3tc_comment = __( 'Added by W3 Total Cache', 'w3-total-cache' ); if ( preg_match_all( '/[^\r\n]+(?:W3 Total Cache|' . preg_quote( $translated_w3tc_comment, '/' ) . ').*?(?:\r\n|\r|\n)/', $contents, $matches ) ) { foreach ( $matches[0] as $match ) { $new_line = rtrim( $match ) . "\r\n"; if ( $new_line !== $match ) { $contents = str_replace( $match, $new_line, $contents ); } } } return $contents; } /** * Update modifications in the supplied configuration file. * * If a blank $contents argument is supplied, all modifications will be removed. * * @since 1.15.0 * @access protected * * @param string $file Config file to update. * @param string $type The type of config file. Valid options are apache, nginx, and * wp-config. * @param string $modification The contents to add or update the file with. If an empty string is * supplied, all iThemes Security modifications will be removed. * @param bool $clear_existing_modifications Optional. Whether or not existing modifications should be removed * first. Defaults to true. * @return bool|WP_Error Boolean true on success or a WP_Error object otherwise. */ protected static function update( $file, $type, $modification, $clear_existing_modifications = true ) { // Check to make sure that the settings give permission to write files. if ( ! ITSEC_Files::can_write_to_files() ) { $display_file = str_replace( '\\', '/', $file ); $abspath = str_replace( '\\', '/', ABSPATH ); $display_file = preg_replace( '/^' . preg_quote( $abspath, '/' ) . '/', '', $display_file ); $display_file = ltrim( $display_file, '/' ); return new WP_Error( 'itsec-config-file-update-writes-files-disabled', sprintf( __( 'The "Write to Files" setting is disabled. Manual configuration for the %s file can be found on the Security > Settings page in the Advanced section.', 'better-wp-security' ), $display_file ) ); } if ( $clear_existing_modifications ) { $contents = self::get_file_contents_without_modification( $file, $type ); } else { $contents = self::get_file_contents( $file ); } if ( is_wp_error( $contents ) ) { return $contents; } $modification = ltrim( $modification, "\x0B\r\n\0" ); $modification = rtrim( $modification, " \t\x0B\r\n\0" ); if ( empty( $modification ) ) { // If there isn't a new modification, write the content without any modification and return the result. if ( empty( $contents ) ) { $contents = PHP_EOL; } return ITSEC_Lib_File::write( $file, $contents ); } $placeholder = self::get_placeholder(); // Ensure that the generated placeholder can be uniquely identified in the contents. while ( false !== strpos( $contents, $placeholder ) ) { $placeholder = self::get_placeholder(); } if ( 'wp-config' === $type ) { // Put the placeholder at the beginning of the file, after the $contents"; } } else { // Apache and nginx server config files. $contents = "$placeholder$contents"; } // Pad away from existing sections when adding iThemes Security modifications. $line_ending = self::get_line_ending( $contents ); while ( ! preg_match( "/(?:^|(?:(? $replacement ) { $contents = preg_replace( $pattern, $replacement, $contents, -1, $count ); $total += $count; } // Write the new contents to the file and return the results. return ITSEC_Lib_File::write( $file, $contents ); } /** * Get the appropriate comment delimiter for a specific type of config. * * @since 1.15.0 * @access protected * * @param string $type The type of config that the comment will be used for. * @return string The comment delimiter. */ protected static function get_comment_delimiter( $type ) { if ( 'wp-config' === $type ) { $delimiter = '//'; } else { $delimiter = '#'; } $delimiter = apply_filters( 'itsec_filter_server_config_file_comment_delimiter', $delimiter, $type ); return $delimiter; } /** * Internal class function to get the primary line ending found in the contents. * * @since 1.15.0 * @access protected * * @param string $contents The contents to count line endings in. * @return string The line ending. */ protected static function get_line_ending( $contents ) { if ( empty( $contents ) ) { return PHP_EOL; } $count["\n"] = preg_match_all( "/(? '.htaccess', 'litespeed' => '.htaccess', 'nginx' => 'nginx.conf', ); if ( isset( $defaults[$server] ) ) { $name = $defaults[$server]; } else { $name = false; } return apply_filters( 'itsec_filter_default_server_config_file_name', $name, $server ); } /** * Get full file path to the site's wp-config.php file. * * Customize the returned value with the itsec_filter_wp_config_file_path filter. Filter the value to a blank string * ("") in order to disable modifications to this file. * * @since 1.15.0 * * @return string Full path to the wp-config.php file or a blank string if modifications for the file are disabled. */ public static function get_wp_config_file_path() { if ( file_exists( ABSPATH . 'wp-config.php') ) { $path = ABSPATH . 'wp-config.php'; } else if ( file_exists( dirname( ABSPATH ) . '/wp-config.php' ) && ! file_exists( dirname( ABSPATH ) . '/wp-settings.php' ) ) { $path = dirname( ABSPATH ) . '/wp-config.php'; } else { $path = ABSPATH . 'wp-config.php'; } return apply_filters( 'itsec_filter_wp_config_file_path', $path ); } } require_once( dirname( __FILE__ ) . '/class-itsec-lib-utility.php' ); require_once( dirname( __FILE__ ) . '/class-itsec-lib-file.php' );