From 0ce4afa786d0c2bf13607faf409bab3ff4021d0c Mon Sep 17 00:00:00 2001
From: Joaquim Homrighausen
Date: Wed, 10 Mar 2021 12:12:36 +0100
Subject: [PATCH] 1.1.0, with verified WordPress 5.7 compatibility
---
README.md | 30 +-
fail2wp/fail2wp.conf | 21 +-
fail2wp/fail2wp.php | 1675 ++++++++++++++++++++++---
fail2wp/includes/fail2wp_misc.inc.php | 105 ++
fail2wp/languages/fail2wp-sv_SE.mo | Bin 5835 -> 14600 bytes
fail2wp/languages/fail2wp-sv_SE.po | 462 ++++++-
fail2wp/languages/fail2wp.pot | 423 ++++++-
fail2wp/uninstall.php | 42 +-
8 files changed, 2426 insertions(+), 332 deletions(-)
create mode 100644 fail2wp/includes/fail2wp_misc.inc.php
diff --git a/README.md b/README.md
index 297b03f..08f6325 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
-[![Software License](https://img.shields.io/badge/License-GPL%20v2-green.svg?style=flat-square)](LICENSE) [![PHP 7.2\+](https://img.shields.io/badge/PHP-7.2-blue?style=flat-square)](https://php.net) [![WordPress 5](https://img.shields.io/badge/WordPress-5.6-orange?style=flat-square)](https://wordpress.org)
+[![Software License](https://img.shields.io/badge/License-GPL%20v2-green.svg?style=flat-square)](LICENSE) [![PHP 7.2\+](https://img.shields.io/badge/PHP-7.2-blue?style=flat-square)](https://php.net) [![WordPress 5](https://img.shields.io/badge/WordPress-5.7-orange?style=flat-square)](https://wordpress.org)
# Fail2WP
-Security plugin for WordPress with support for Fail2ban and Cloudflare. Tested with WordPress 5.6+.
+Security plugin for WordPress with support for Fail2ban and Cloudflare. Tested with WordPress 5.5+.
## Description
@@ -17,14 +17,21 @@ Basic security functionality includes:
* Disabling login with username (require e-mail address)
* Preventing user enumeration (?author=nnn)
* Less detailed error messages on login failures
+* Minimum username length
+* Blocking specific usernames from being used to register new users
+* Requiring e-mail address matching for new user registrations
+* Warning about new user role setting
+* Blocking of portions or all of WordPress REST API
+* Disabling of RSS and Atom feeds
+* Removal of "Generator" information from HTML and feeds
* Detection of Cloudflare IP addresses for logging of actual IP addresses
The plugin also plays nicely with Fail2ban, which is an advanced way of blocking IP addresses dynamically upon suspicious behavior.
### Other notes
-* This plugin may work with earlier versions of WordPress
-* This plugin has been tested with `WordPress 5.5.3`, `5.6`, and `5.6.1` at the time of this writing
+* This plugin `may` work with earlier versions of WordPress
+* This plugin has been tested with `WordPress 5.5+` at the time of this writing
* This plugin optionally makes use of `mb_` PHP functions
* This plugin may create entries in your PHP error log (if active)
* This plugin contains no Javascript
@@ -59,11 +66,24 @@ This is a hard question to answer. There are no known incompatibilities.
## Changelog
+### 1.1.0
+* Added minimum username length
+* Added blocking of specific usernames (user registration)
+* Added requiring e-mail address matching setting
+* Added warning about new user role setting
+* Added blocking of portions or all of WordPress REST API
+* Added setting to disable RSS and Atom feeds
+* Added setting to remove "Generator" information from HTML and feeds
+* Minor corrections and general improvements
+
### 1.0.0
* Initial release
## Upgrade Notice
+### 1.1.0
+* Install the new version and walk through the settings.
+
### 1.0.0
* Initial release
@@ -101,6 +121,8 @@ If there is something you feel to be missing from this plugin, or if you have fo
This plugin can also be downloaded from [code.webbplatsen.net](https://code.webbplatsen.net/wordpress/fail2wp/) and [WordPress.org](https://wordpress.org/plugins/fail2wp/)
+More detailed documentation is available at [code.webbplatsen.net/documentation/fail2wp/](https://code.webbplatsen.net/documentation/fail2wp/)
+
Kudos to [Vincent Le Moign and Webalys](https://webalys.com) and [Thomas Lutz](https://github.com/tholu)
### External references
diff --git a/fail2wp/fail2wp.conf b/fail2wp/fail2wp.conf
index f7cf3a8..1edf6bb 100644
--- a/fail2wp/fail2wp.conf
+++ b/fail2wp/fail2wp.conf
@@ -2,23 +2,37 @@
before = common.conf
after = fail2wp.local
-# This filter is intended to be used with fail2ban and the Fail2WP plugin.
+# This filter is intended to be used with Fail2ban and the Fail2WP plugin.
#
# This file should be placed in /etc/fail2ban/filter.d as fail2wp.conf
#
# I'm by no means a fail2ban filter expert, so I'm sure this could do with some
-# improvements. It has been tested with fail2ban 0.11.1 on Ubuntu 20.04.LTS.
+# improvements. It has been tested with Fail2ban 0.11.1 on Ubuntu 20.04.LTS.
+#
+# ADVANCED: You may, of course, split these into several different jails and
+# triggers and give them different treatment in Fail2ban so that some
+# of the log messages trigger one behavior in Fail2ban, and others
+# are ignored or behave differently.
#
# Joaquim Homrighausen
#
# The intended log messages to trigger fail2ban on are:
#
+# @since 1.0.0
+#
# Authentication failure for validuser from n.n.n.n port 443
# Invalid email invalidemail from n.n.n.n port 443
# Invalid user invaliduser from n.n.n.n port 443
# User enumeration request from n.n.n.n port 443
# Invalid credentials invalidlogin from n.n.n.n port 443
#
+# @since 1.1.0
+#
+# Blocked REST API request from n.n.n.n port 443
+# Unauthenticated REST API request from n.n.n.n port 443
+#
+# Other messages:
+#
# The Fail2WP plugin can further emit these messages (no action taken):
#
# Unknown error "nnn" during login from n.n.n.n port nnn
@@ -31,7 +45,8 @@ failregex = fail2wp(.*): Authentication failure for .* from port .*$
fail2wp(.*): Invalid user .* from port .*$
fail2wp(.*): Invalid email .* from port .*$
fail2wp(.*): Invalid credentials .* from port .*$
- fail2wp(.*): User enumeration request from port .*$
+ fail2wp(.*): Blocked REST API request from from port .*$
+ fail2wp(.*): Unauthenticated REST API request from port .*$
# Your entry for Fail2WP in jail.local should look like this:
#
diff --git a/fail2wp/fail2wp.php b/fail2wp/fail2wp.php
index 49ec040..d4fdef9 100644
--- a/fail2wp/fail2wp.php
+++ b/fail2wp/fail2wp.php
@@ -11,12 +11,12 @@
* Plugin Name: Fail2WP
* Plugin URI: https://code.webbplatsen.net/wordpress/fail2wp/
* Description: Security plugin for WordPress with support for fail2ban
- * Version: 1.0.0
+ * Version: 1.1.0
* Author: WebbPlatsen, Joaquim Homrighausen
* Author URI: https://webbplatsen.se/
* License: GPL-2.0+
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt
- * Text Domain: playground
+ * Text Domain: fail2wp
* Domain Path: /languages
*
* fail2wp.php
@@ -42,7 +42,6 @@
*/
namespace fail2wp;
-
// If this file is called directly, abort.
if ( ! defined( 'WPINC' ) ) {
die;
@@ -51,16 +50,28 @@
die( '-1' );
}
-define( 'FAIL2WP_VERSION', '1.0.0' );
-define( 'FAIL2WP_REV', 1 );
-define( 'FAIL2WP_PLUGINNAME_HUMAN', 'Fail2WP' );
-define( 'FAIL2WP_PLUGINNAME_SLUG', 'fail2wp' );
-define( 'FAIL2WP_DEFAULT_PREFIX', 'fail2wp' );
-define( 'FAIL2WP_ALERT_SUCCESS', 1 );
-define( 'FAIL2WP_ALERT_FAILURE', 2 );
-define( 'FAIL2WP_ALERT_USER_ENUM', 3 );
-define( 'FAIL2WP_DEFAULT_HTTP_PORT', 80 );
-define( 'FAIL2WP_DEFAULT_HTTPS_PORT', 443 );
+define( 'FAIL2WP_WORDPRESS_PLUGIN', true );
+define( 'FAIL2WP_VERSION', '1.1.0' );
+define( 'FAIL2WP_REV', 1 );
+define( 'FAIL2WP_PLUGINNAME_HUMAN', 'Fail2WP' );
+define( 'FAIL2WP_PLUGINNAME_SLUG', 'fail2wp' );
+define( 'FAIL2WP_DEFAULT_PREFIX', 'fail2wp' );
+define( 'FAIL2WP_ALERT_SUCCESS', 1 );
+define( 'FAIL2WP_ALERT_FAILURE', 2 );
+define( 'FAIL2WP_ALERT_USER_ENUM', 3 );
+define( 'FAIL2WP_ALERT_REST_NOTAUTH', 4 );
+define( 'FAIL2WP_ALERT_REST_BLOCKED', 5 );
+define( 'FAIL2WP_DEFAULT_HTTP_PORT', 80 );
+define( 'FAIL2WP_DEFAULT_HTTPS_PORT', 443 );
+define( 'FAIL2WP_DB_VERSION', 2 );
+define( 'FAIL2WP_EXPORT_HEADER', 'fail2wp_export.begin.' );
+define( 'FAIL2WP_EXPORT_FOOTER', '.fail2wp_export.end' );
+
+// define( 'FAIL2WP_DEBUG', true );
+if ( defined( 'FAIL2WP_DEBUG' ) ) {
+ define( 'FAIL2WP_REST_DEBUG', true );
+ define( 'FAIL2WP_DUMP_SETTINGS', true );
+}
require_once plugin_dir_path( __FILE__ ) . 'includes/class-fail2wp-syslog.php';
@@ -73,7 +84,10 @@
class Fail2WP {
public static $instance = null;
protected $plugin_name;
- protected $version;
+ protected $fail2wp_plugin_version;
+ protected $fail2wp_mail_headers; // @since 1.1.0
+ protected $fail2wp_have_mbstring; // @since 1.1.0
+
protected $fail2wp_wp_roles = null;
protected $fail2wp_wp_roles_enus = null;
protected $fail2wp_settings_tab = '';
@@ -81,7 +95,10 @@ class Fail2WP {
protected $fail2wp_roles_notify;
protected $fail2wp_roles_warn;
protected $fail2wp_unknown_warn;
+ protected $fail2wp_settings_dbversion; // @since 1.1.0
protected $fail2wp_settings_remove;
+ protected $fail2wp_settings_remove_generator; // @since 1.1.0
+ protected $fail2wp_settings_remove_feeds; // @since 1.1.0
protected $fail2wp_also_log_php;
protected $fail2wp_block_user_enum;
protected $fail2wp_block_username_login;
@@ -92,10 +109,29 @@ class Fail2WP {
protected $fail2wp_cloudflare_ipv4;
protected $fail2wp_cloudflare_ipv6;
+ protected $fail2wp_reguser_warn; // @since 1.1.0
+ protected $fail2wp_reguser_warn_role; // @since 1.1.0
+ protected $fail2wp_reguser_force; // @since 1.1.0
+ protected $fail2wp_reguser_force_role; // @since 1.1.0
+ protected $fail2wp_reguser_username_length; // @since 1.1.0
+ protected $fail2wp_reguser_username_ban; // @since 1.1.0
+ protected $fail2wp_reguser_useremail_require; // @since 1.1.0
+
+ protected $fail2wp_rest = null; // @since 1.1.0
+ protected $fail2wp_rest_filter_log_blocked; // @since 1.1.0
+ protected $fail2wp_rest_filter_block_all; // @since 1.1.0
+ protected $fail2wp_rest_filter_block_index; // @since 1.1.0
+ protected $fail2wp_rest_filter_block_ns; // @since 1.1.0
+ protected $fail2wp_rest_filter_block_routes; // @since 1.1.0
+ protected $fail2wp_rest_filter_route_list; // @since 1.1.0
+ protected $fail2wp_rest_filter_require_authenticated; // @since 1.1.0
+ protected $fail2wp_rest_filter_ipv4_bypass; // @since 1.1.0
+ protected $fail2wp_rest_filter_ipv6_bypass; // @since 1.1.0
+
public static function getInstance( string $version = '', string $slug = '' )
{
null === self::$instance AND self::$instance = new self( $version, $slug );
- return self::$instance;
+ return( self::$instance );
}
/**
* Start me up ...
@@ -103,53 +139,807 @@ public static function getInstance( string $version = '', string $slug = '' )
public function __construct( string $version = '', string $slug = '' ) {
if ( empty( $version ) ) {
if ( defined( 'FAIL2WP_VERSION' ) ) {
- $this->version = FAIL2WP_VERSION;
+ $this->fail2wp_plugin_version = FAIL2WP_VERSION;
} else {
- $this->version = '0.0.1';
+ $this->fail2wp_plugin_version = '1.0.0';
}
} else {
- $this->version = $version;
+ $this->fail2wp_plugin_version = $version;
}
if ( empty( $slug ) ) {
$this->plugin_name = FAIL2WP_PLUGINNAME_SLUG;
} else {
$this->plugin_name = $slug;
}
+ // Setup default mail headers
+ $this->fail2wp_mail_headers = array( 'Auto-Submitted: auto-replied',
+ 'X-Auto-Response-Suppress: All',
+ 'Precedence: auto_reply' );
+ // We only need to query this once really
+ $this->fail2wp_have_mbstring = extension_loaded( 'mbstring ');
+ // Things we may be interested in for the REST API
+ $this->fail2wp_rest_filter_route_list = array(
+ 'categories',
+ 'comments',
+ 'media',
+ 'pages',
+ 'posts',
+ 'search',
+ 'statuses',
+ 'tags',
+ 'taxonomies',
+ 'types',
+ 'users',
+ );
+
+ // Dump all of our settings, for development
+ if ( defined( 'FAIL2WP_DUMP_SETTINGS' ) && FAIL2WP_DUMP_SETTINGS ) {
+ global $wpdb;
+ $settings = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->options} WHERE option_name LIKE 'fail2wp%'" ), ARRAY_A );
+ if ( is_array( $settings ) ) {
+ error_log( var_export( $settings, true ) );
+ } else {
+ error_log ( basename( __FILE__ ) . ': Unable to fetch settings' );
+ }
+ }
// Fetch options and setup defaults
- $this->fail2wp_site_label = $this->fail2wp_get_option( 'fail2wp-site-label', true );
- $this->fail2wp_roles_notify = $this->fail2wp_get_option( 'fail2wp-roles-notify', true );
- $this->fail2wp_roles_warn = $this->fail2wp_get_option( 'fail2wp-roles-warn', true );
- $this->fail2wp_unknown_warn = $this->fail2wp_get_option( 'fail2wp-unknown-warn', true );
-
- $this->fail2wp_also_log_php = $this->fail2wp_get_option( 'fail2wp-also-log-php', false );
- $this->fail2wp_block_user_enum = $this->fail2wp_get_option( 'fail2wp-block-user-enum', false );
- $this->fail2wp_block_username_login = $this->fail2wp_get_option( 'fail2wp-block-username-login', false );
- $this->fail2wp_log_user_enum = $this->fail2wp_get_option( 'fail2wp-log-user-enum', false );
- $this->fail2wp_secure_login_message = $this->fail2wp_get_option( 'fail2wp-secure-login-message', false );
- $this->fail2wp_cloudflare_check = $this->fail2wp_get_option( 'fail2wp-cloudflare-check', false );
- $this->fail2wp_cloudflare_ipv4 = @ json_decode( $this->fail2wp_get_option( 'fail2wp-cloudflare-ipv4', '' ), true, 2 );
+ // ..General
+ $this->fail2wp_settings_dbversion = get_option( 'fail2wp-settings-dbversion', null );
+ if ( $this->fail2wp_settings_dbversion === null ) {
+ $this->fail2wp_settings_dbversion = FAIL2WP_DB_VERSION;
+ update_option( 'fail2wp-settings-dbversion', $this->fail2wp_settings_dbversion );
+ }
+ $this->fail2wp_site_label = $this->fail2wp_get_site_label( false );
+ $this->fail2wp_roles_notify = @ json_decode( get_option( 'fail2wp-roles-notify', null ), true, 2 );
+ if ( ! is_array( $this->fail2wp_roles_notify ) ) {
+ $this->fail2wp_roles_notify = array();
+ update_option( 'fail2wp-roles-notify', json_encode( $this->fail2wp_roles_notify ) );
+ }
+ $this->fail2wp_roles_warn = @ json_decode( get_option( 'fail2wp-roles-warn', null ), true, 2 );
+ if ( ! is_array( $this->fail2wp_roles_warn ) ) {
+ $this->fail2wp_roles_warn = array( 'administrator' );
+ update_option( 'fail2wp-roles-warn', json_encode( $this->fail2wp_roles_warn ) );
+ }
+ $this->fail2wp_unknown_warn = get_option( 'fail2wp-unknown-warn', null );
+ if ( $this->fail2wp_unknown_warn || $this->fail2wp_unknown_warn === null ) {
+ $this->fail2wp_unknown_warn = true;
+ } else {
+ $this->fail2wp_unknown_warn = false;
+ }
+ // ..New users @since 1.1.0
+ $default_role = get_option( 'default_role', null );// Get WordPress' default role
+ $this->fail2wp_reguser_warn = get_option( 'fail2wp-reguser-warn', null );
+ if ( $this->fail2wp_reguser_warn === null ) {
+ $this->fail2wp_reguser_warn = true;
+ }
+ $this->fail2wp_reguser_warn_role = get_option( 'fail2wp-reguser-warn-role', null );
+ if ( $this->fail2wp_reguser_warn_role === null ) {
+ $this->fail2wp_reguser_warn_role = ( ! empty( $default_role ) ? $default_role:'subscriber' );
+ }
+ $this->fail2wp_reguser_force = get_option( 'fail2wp-reguser-force', null );
+ if ( $this->fail2wp_reguser_force === null ) {
+ $this->fail2wp_reguser_force = true;
+ }
+ $this->fail2wp_reguser_force_role = get_option( 'fail2wp-reguser-force-role', null );
+ if ( $this->fail2wp_reguser_force_role === null ) {
+ $this->fail2wp_reguser_force_role = $this->fail2wp_reguser_warn_role;
+ }
+ if ( $this->fail2wp_reguser_force && $default_role != $this->fail2wp_reguser_force_role ) {
+ // Set WordPress' default role
+ update_option( 'default_role', $this->fail2wp_reguser_force_role );
+ // Tell admin we've done this
+ add_action( 'admin_notices', [$this, 'fail2wp_admin_alert_new_user_role_forced'] );
+ add_action( 'plugins_loaded', [$this, 'fail2wp_admin_alert_new_user_role_forced_email'] );
+ $default_role = $this->fail2wp_reguser_force_role;
+ }
+ $users_can_register = get_option( 'users_can_register', null );
+ if ( $this->fail2wp_reguser_warn ) {
+ if ( $users_can_register ) {
+ // Only trigger alarms if users_can_register is true
+ if ( $default_role != $this->fail2wp_reguser_warn_role ) {
+ add_action( 'admin_notices', [$this, 'fail2wp_admin_alert_new_users_mismatch'] );
+ } elseif ( $default_role == 'administrator' ) {
+ add_action( 'admin_notices', [$this, 'fail2wp_admin_alert_new_users_admin'] );
+ } elseif ( $default_role == null ) {
+ add_action( 'admin_notices', [$this, 'fail2wp_admin_alert_new_users_null'] );
+ }
+ }
+ }
+ $this->fail2wp_reguser_username_length = get_option( 'fail2wp-reguser-username-length', null );
+ if ( $this->fail2wp_reguser_username_length === null || $this->fail2wp_reguser_username_length > 200 ) {
+ $this->fail2wp_reguser_username_length = 0;
+ update_option( 'fail2wp-reguser-username-length', (int)$this->fail2wp_reguser_username_length );
+ }
+ $this->fail2wp_reguser_username_ban = @ json_decode( get_option ( 'fail2wp-reguser-username-ban', null ), true, 2 );
+ if ( ! is_array( $this->fail2wp_reguser_username_ban ) ) {
+ $this->fail2wp_reguser_username_ban = array( 'administrator', 'admin', 'sysadmin', 'siteadmin' );
+ update_option( 'fail2wp-reguser-username-ban', json_encode( $this->fail2wp_reguser_username_ban ) );
+ }
+ $this->fail2wp_reguser_useremail_require = @ json_decode( get_option ( 'fail2wp-reguser-useremail-require', null ), true, 2 );
+ if ( ! is_array( $this->fail2wp_reguser_useremail_require ) ) {
+ $this->fail2wp_reguser_useremail_require = array();
+ update_option( 'fail2wp-reguser-useremail-require', json_encode( $this->fail2wp_reguser_useremail_require ) );
+ }
+ // Possibly add new user registration details validation if new user
+ // registrations are active and we have something to validate against.
+ if ( $users_can_register &&
+ ( ! empty( $this->fail2wp_reguser_username_ban )
+ || ! empty( $this->fail2wp_reguser_useremail_require )
+ || $this->fail2wp_reguser_username_length > 1 ) ) {
+ add_filter( 'registration_errors', [$this, 'fail2wp_admin_check_new_user'], 10, 3 );
+ }
+ // .. REST API @since 1.1.0
+ $this->fail2wp_rest_filter_require_authenticated = get_option( 'fail2wp-rest-filter-require-authenticated', null );
+ if ( ! $this->fail2wp_rest_filter_require_authenticated || $this->fail2wp_rest_filter_require_authenticated === null ) {
+ $this->fail2wp_rest_filter_require_authenticated = false;
+ } else {
+ $this->fail2wp_rest_filter_require_authenticated = true;
+ }
+ if ( $this->fail2wp_rest_filter_require_authenticated ) {
+ add_filter( 'rest_authentication_errors', [$this, 'fail2wp_rest_filter_authenticate'], 10, 1 );
+ }
+ $this->fail2wp_rest_filter_log_blocked = get_option( 'fail2wp-rest-filter-log-blocked', null );
+ if ( ! $this->fail2wp_rest_filter_log_blocked || $this->fail2wp_rest_filter_log_blocked === null ) {
+ $this->fail2wp_rest_filter_log_blocked = false;
+ } else {
+ $this->fail2wp_rest_filter_log_blocked = true;
+ }
+ $this->fail2wp_rest_filter_block_index = get_option( 'fail2wp-rest-filter-block-index', null );
+ if ( ! $this->fail2wp_rest_filter_block_index || $this->fail2wp_rest_filter_block_index === null ) {
+ $this->fail2wp_rest_filter_block_index = false;
+ } else {
+ $this->fail2wp_rest_filter_block_index = true;
+ }
+ $this->fail2wp_rest_filter_block_all = get_option( 'fail2wp-rest-filter-block-all', null );
+ if ( ! $this->fail2wp_rest_filter_block_all || $this->fail2wp_rest_filter_block_all === null ) {
+ $this->fail2wp_rest_filter_block_all = false;
+ } else {
+ $this->fail2wp_rest_filter_block_all = true;
+ }
+ $this->fail2wp_rest_filter_block_ns = @ json_decode( get_option( 'fail2wp-rest-filter-block-ns', null ), true, 2 );
+ if ( ! is_array( $this->fail2wp_rest_filter_block_ns ) ) {
+ $this->fail2wp_rest_filter_block_ns = array();
+ update_option( 'fail2wp-rest-filter-block-ns', json_encode( $this->fail2wp_rest_filter_block_ns ) );
+ }
+ $this->fail2wp_rest_filter_block_routes = @ json_decode( get_option( 'fail2wp-rest-filter-block-routes', null ), true, 2 );
+ if ( ! is_array( $this->fail2wp_rest_filter_block_routes ) ) {
+ $this->fail2wp_rest_filter_block_routes = array();
+ update_option( 'fail2wp-rest-filter-block-routes', json_encode( $this->fail2wp_rest_filter_block_routes ) );
+ }
+ $this->fail2wp_rest_filter_ipv4_bypass = @ json_decode( get_option ( 'fail2wp-rest-filter-ipv4-bypass', null ), true, 2 );
+ if ( ! is_array( $this->fail2wp_rest_filter_ipv4_bypass ) ) {
+ $this->fail2wp_rest_filter_ipv4_bypass = array();
+ update_option( 'fail2wp-rest-filter-ipv4-bypass', json_encode( $this->fail2wp_rest_filter_ipv4_bypass ) );
+ }
+ $this->fail2wp_rest_filter_ipv6_bypass = @ json_decode( get_option ( 'fail2wp-rest-filter-ipv6-bypass', null ), true, 2 );
+ if ( ! is_array( $this->fail2wp_rest_filter_ipv6_bypass ) ) {
+ $this->fail2wp_rest_filter_ipv6_bypass = array();
+ update_option( 'fail2wp-rest-filter-ipv6-bypass', json_encode( $this->fail2wp_rest_filter_ipv6_bypass ) );
+ }
+ // ..Logging
+ $this->fail2wp_also_log_php = get_option( 'fail2wp-also-log-php', null );
+ if ( ! $this->fail2wp_also_log_php || $this->fail2wp_also_log_php === null ) {
+ $this->fail2wp_also_log_php = false;
+ } else {
+ $this->fail2wp_also_log_php = true;
+ }
+ $this->fail2wp_block_user_enum = get_option( 'fail2wp-block-user-enum', null );
+ if ( $this->fail2wp_block_user_enum || $this->fail2wp_block_user_enum === null ) {
+ $this->fail2wp_block_user_enum = true;
+ } else {
+ $this->fail2wp_block_user_enum = false;
+ }
+ $this->fail2wp_block_username_login = get_option( 'fail2wp-block-username-login', null );
+ if ( $this->fail2wp_block_username_login === null || ! $this->fail2wp_block_username_login ) {
+ $this->fail2wp_block_username_login = false;
+ } else {
+ $this->fail2wp_block_username_login = true;
+ }
+ $this->fail2wp_log_user_enum = get_option( 'fail2wp-log-user-enum', null );
+ if ( $this->fail2wp_log_user_enum || $this->fail2wp_log_user_enum === null ) {
+ $this->fail2wp_log_user_enum = true;
+ } else {
+ $this->fail2wp_log_user_enum = false;
+ }
+ // ..Failed login message
+ $this->fail2wp_secure_login_message = get_option( 'fail2wp-secure-login-message', null );
+ if ( $this->fail2wp_secure_login_message || $this->fail2wp_secure_login_message === null ) {
+ $this->fail2wp_secure_login_message = true;
+ } else {
+ $this->fail2wp_secure_login_message = false;
+ }
+ // ..Cloudflare
+ $this->fail2wp_cloudflare_check = get_option( 'fail2wp-cloudflare-check', null );
+ if ( $this->fail2wp_cloudflare_check === null || ! $this->fail2wp_cloudflare_check ) {
+ $this->fail2wp_cloudflare_check = false;
+ } else {
+ $this->fail2wp_cloudflare_check = true;
+ }
+ $this->fail2wp_cloudflare_ipv4 = @ json_decode( get_option ( 'fail2wp-cloudflare-ipv4', null ), true, 2 );
if ( ! is_array( $this->fail2wp_cloudflare_ipv4 ) ) {
$this->fail2wp_cloudflare_ipv4 = array();
+ update_option( 'fail2wp-cloudflare-ipv4', json_encode( $this->fail2wp_cloudflare_ipv4 ) );
}
- $this->fail2wp_cloudflare_ipv6 = @ json_decode( $this->fail2wp_get_option( 'fail2wp-cloudflare-ipv6', '' ), true, 2 );
+ $this->fail2wp_cloudflare_ipv6 = @ json_decode( get_option ( 'fail2wp-cloudflare-ipv6', null ), true, 2 );
if ( ! is_array( $this->fail2wp_cloudflare_ipv6 ) ) {
- $this->fail2wp_cloudflare_ipv4 = array();
+ $this->fail2wp_cloudflare_ipv6 = array();
+ update_option( 'fail2wp-cloudflare-ipv6', json_encode( $this->fail2wp_cloudflare_ipv6 ) );
+ }
+ // ..Other options
+ $this->fail2wp_settings_remove_generator = get_option( 'fail2wp-settings-remove-generator', null );
+ if ( $this->fail2wp_settings_remove_generator === null || ! $this->fail2wp_settings_remove_generator ) {
+ $this->fail2wp_settings_remove_generator = false;
+ } else {
+ $this->fail2wp_settings_remove_generator = true;
}
- $this->fail2wp_settings_remove = $this->fail2wp_get_option( 'fail2wp-settings-remove', false );
+ $this->fail2wp_settings_remove_feeds = get_option( 'fail2wp-settings-remove-feeds', null );
+ if ( $this->fail2wp_settings_remove_feeds === null || ! $this->fail2wp_settings_remove_feeds ) {
+ $this->fail2wp_settings_remove_feeds = false;
+ } else {
+ $this->fail2wp_settings_remove_feeds = true;
+ }
+ $this->fail2wp_settings_remove = get_option( 'fail2wp-settings-remove', null );
+ if ( $this->fail2wp_settings_remove === null || ! $this->fail2wp_settings_remove ) {
+ $this->fail2wp_settings_remove = false;
+ } else {
+ $this->fail2wp_settings_remove = true;
+ }
+ // .. REST filtering
+ if ( ! $this->fail2wp_rest_filter_require_authenticated ) {
+ if ( $this->fail2wp_rest_filter_block_all ) {
+ remove_action( 'wp_head', 'rest_output_link_wp_head' );
+ }
+ if ( $this->fail2wp_rest_filter_block_all
+ || ! empty( $this->fail2wp_rest_filter_block_index )
+ || ! empty( $this->fail2wp_rest_filter_block_ns )
+ || ! empty( $this->fail2wp_rest_filer_block_routes ) ) {
+ add_action( 'rest_api_init', [$this, 'fail2wp_rest_init'] );
+ }
+ }
+ // .. Generator filtering
+ if ( $this->fail2wp_settings_remove_generator ) {
+ remove_action( 'wp_head', 'wp_generator' );
+ add_filter( 'the_generator', [$this, 'fail2wp_the_generator'], 10, 2 );
+ }
+ // .. Feed filtering
+ if ( $this->fail2wp_settings_remove_feeds ) {
+ add_filter( 'feed_links_show_posts_feed', [$this, 'fail2wp_noshow_feeds'], 10, 1 );
+ add_filter( 'feed_links_show_comments_feed', [$this, 'fail2wp_noshow_feeds'], 10, 1 );
+ remove_action( 'wp_head', 'rsd_link' );
+ remove_action( 'wp_head', 'feed_links', 2 );
+ remove_action( 'wp_head', 'index_rel_link' );
+ remove_action( 'wp_head', 'wlwmanifest_link' );
+ remove_action( 'wp_head', 'feed_links_extra', 3 );
+ remove_action( 'wp_head', 'start_post_rel_link', 10, 0 );
+ remove_action( 'wp_head', 'parent_post_rel_link', 10, 0 );
+ remove_action( 'wp_head', 'adjacent_posts_rel_link', 10, 0 );
+ remove_action( 'wp_head', 'wp_shortlink_wp_head', 10, 0 );
+ remove_action( 'wp_head', 'adjacent_posts_rel_link_wp_head', 10, 0 );
+ remove_action( 'wp_head', 'wp_oembed_add_discovery_links' );
+ add_action( 'do_feed', [$this, 'fail2wp_remove_feeds'] );
+ add_action( 'do_feed_rdf', [$this, 'fail2wp_remove_feeds'] );
+ add_action( 'do_feed_rss', [$this, 'fail2wp_remove_feeds'] );
+ add_action( 'do_feed_rss2', [$this, 'fail2wp_remove_feeds'] );
+ add_action( 'do_feed_atom', [$this, 'fail2wp_remove_feeds'] );
+ add_action( 'do_feed_rss2_comments', [$this, 'fail2wp_remove_feeds'] );
+ add_action( 'do_feed_atom_comments', [$this, 'fail2wp_remove_feeds'] );
+ }
+ // .. Various settings
$this->fail2wp_default_http_port = FAIL2WP_DEFAULT_HTTP_PORT;
$this->fail2wp_default_https_port = FAIL2WP_DEFAULT_HTTPS_PORT;
-
+ $this->fail2wp_prefix = get_option( 'fail2wp-prefix', null );
+ if ( $this->fail2wp_prefix === null ) {
+ $this->fail2wp_prefix = '';
+ }
+ // Validate selected configuration tab
$this->fail2wp_settings_tab = ( ! empty( $_GET['tab'] ) ? $_GET['tab'] : '' );
- if ( ! in_array( $this->fail2wp_settings_tab, ['logging', 'advanced', 'cloudflare', 'about'] ) ) {
+ if ( ! in_array( $this->fail2wp_settings_tab, ['newuser', 'logging', 'advanced', 'restapi', 'cloudflare', 'importexport', 'about'] ) ) {
$this->fail2wp_settings_tab = '';
}
+ // Add 'Settings' link in plugin list
+ add_filter( 'plugin_action_links_' . plugin_basename( __FILE__ ), [$this, 'fail2wp_settings_link']);
}
/**
- * Fetch filemtime() of filen and return it.
+ * Add link to Fail2WP settings in plugin list.
+ *
+ * @since 1.1.0
+ */
+ public function fail2wp_settings_link( array $links ) {
+ $our_link = '' .
+ esc_html__( 'Settings ', $this->plugin_name ) . '';
+ array_unshift( $links, $our_link );
+ return ( $links );
+ }
+
+ /**
+ * Remove "generator" output.
+ *
+ * @since 1.1.0
+ */
+ public function fail2wp_the_generator( string $generator_type, string $type ) {
+ return( '' );
+ }
+
+ /**
+ * Remove feeds.
+ *
+ * Instead of a 404, we issue a WordPress redirect to the main page.
+ *
+ * @since 1.1.0
+ */
+ public function fail2wp_remove_feeds( $feed ) {
+ wp_redirect( home_url(), 302 );
+ exit();
+ // One could have something like this too of course, but WordPress
+ // doesn't seem to like this for some reason and will still display
+ // the feed content.
+ /*
+ wp_die( __( 'This feed has been disabled, please visit', $this->plugin_name ) .
+ ' ' .
+ home_url() .
+ '',
+ __( 'Feed disabled', $this->plugin_name ),
+ array( 'link_url' => home_url(),
+ 'link_text' => home_url(),
+ 'code' => 'feed_disabled',
+ 'response' => 404 ) );
+ */
+
+ }
+ public function fail2wp_noshow_feeds( $noshow ) {
+ return( false );
+ }
+
+ /**
+ * Setup various REST handlers.
+ *
+ * We only do this on 'rest_api_init' so that we don't clutter chain.
+ *
+ * @since 1.1.0
+ */
+ public function fail2wp_rest_init( \WP_REST_Server $wp_rest ) {
+ if ( $this->fail2wp_rest === null ){
+ $this->fail2wp_rest = $wp_rest;
+ }
+ if ( $this->fail2wp_rest_filter_block_all ) {
+ // Block everything
+ $add_pre_dispatch_filter = true;
+ } else {
+ $add_pre_dispatch_filter = false;
+ // Block selectively
+ if ( $this->fail2wp_rest_filter_block_index ) {
+ // Block REST API index calls
+ add_filter( 'rest_index', [$this, 'fail2wp_rest_index'] );
+ $add_pre_dispatch_filter = true;
+ } elseif ( ! empty( $this->fail2wp_rest_filter_block_ns ) ) {
+ // Check namespaces on requests
+ $add_pre_dispatch_filter = true;
+ }
+ }
+ if ( $add_pre_dispatch_filter ) {
+ add_filter( 'rest_pre_dispatch', [$this, 'fail2wp_rest_pre_dispatch'], 10, 4 );
+ }
+ }
+
+ /**
+ * Require authentication for REST API calls.
+ *
+ * This effectively bypasses all other "block" settings.
+ *
+ * @since 1.1.0
+ */
+ public function fail2wp_rest_filter_authenticate( $result ) {
+ // Pass along previous success or failure
+ if ( $result || is_wp_error( $result ) ) {
+ return( $result );
+ }
+ if ( ! is_user_logged_in() ) {
+ if ( $this->fail2wp_rest_filter_log_blocked ) {
+ // Possibly log this for Fail2ban
+ $alert_message = $this->fail2wp_make_alert_message( '', null, FAIL2WP_ALERT_REST_NOTAUTH );
+ if ( ! empty( $alert_message ) ) {
+ $this->fail2wp_alert_send( $alert_message );
+ }
+ }
+ // This text is taken verbatim from WordPress and will thus be
+ // translated in the same way.
+ return new \WP_Error( 'rest_not_logged_in',
+ __( 'You are not currently logged in.' ),
+ array( 'status' => 401 )
+ );
+ }
+ return( $result );
+ }
+
+ /**
+ * Block REST API index.
+ *
+ * This simply makes the REST API index request return an empty body.
+ *
+ * @since 1.1.0
+ */
+ public function fail2wp_rest_index( $response ) {
+ if ( defined( 'FAIL2WP_REST_DEBUG' ) ) {
+ error_log( basename(__FILE__) . ' (' . __FUNCTION__ . '): We are blocking REST API requests for index' );
+ }
+ if ( $response instanceof \WP_REST_Response ) {
+ $response->data = array();
+ }
+ return( $response );
+
+ }
+
+ /**
+ * Handle majority of REST API filtering.
+ *
+ * @since 1.1.0
+ */
+ public function fail2wp_rest_pre_dispatch( $result, \WP_REST_Server $rest_server, \WP_REST_Request $request ) {
+ // Figure out who we're talking to
+ $remote_ip = $_SERVER['REMOTE_ADDR'];
+ $remote_ip_cf = $this->fail2wp_do_cloudflare_lookup( $remote_ip );
+ if ( defined( 'FAIL2WP_REST_DEBUG' ) ) {
+ error_log( basename(__FILE__) . ' (' . __FUNCTION__ . '): ' .
+ 'Remote address="' . $remote_ip_cf . '" ' . ( $remote_ip !== $remote_ip_cf ? '(' . $remote_ip . ') ' : '' ) .
+ 'REST URL="' . rest_url() . '" ' .
+ 'Route="' . $request->get_route() . '"' );
+ }
+ // Check bypass lists
+ if ( ! empty( $this->fail2wp_rest_filter_ipv4_bypass ) || ! empty( $this->fail2wp_rest_filter_ipv6_bypass ) ) {
+ // Setup CIDRmatch
+ $cidrm = new \CIDRmatch\CIDRmatch();
+ $allowed_to_bypass = false;
+ if ( ! empty( $this->fail2wp_rest_filter_ipv4_bypass ) && is_array( $this->fail2wp_rest_filter_ipv4_bypass ) ) {
+ foreach( $this->fail2wp_rest_filter_ipv4_bypass as $bypass ) {
+ if ( ! empty( $bypass ) && $cidrm->match( $remote_ip_cf, $bypass ) ) {
+ $allowed_to_bypass = true;
+ break;
+ }
+ }
+ }
+ if ( ! $allowed_to_bypass && ! empty( $this->fail2wp_rest_filter_ipv6_bypass ) && is_array( $this->fail2wp_rest_filter_ipv6_bypass ) ) {
+ foreach( $this->fail2wp_rest_filter_ipv6_bypass as $bypass ) {
+ if ( ! empty( $bypass ) && $cidrm->match( $remote_ip_cf, $bypass ) ) {
+ $allowed_to_bypass = true;
+ break;
+ }
+ }
+ }
+ if ( $allowed_to_bypass ) {
+ if ( defined( 'FAIL2WP_REST_DEBUG' ) ) {
+ error_log( basename(__FILE__) . ' (' . __FUNCTION__ . '): IP address is allowed to bypass REST API blocks' );
+ }
+ return( $result );
+ }
+ }
+ // Should we block all REST requests?
+ if ( $this->fail2wp_rest_filter_block_all ) {
+ if ( defined( 'FAIL2WP_REST_DEBUG' ) ) {
+ error_log( basename(__FILE__) . ' (' . __FUNCTION__ . '): We are blocking all REST API requests' );
+ }
+ $alert_message = $this->fail2wp_make_alert_message( $remote_ip_cf, null, FAIL2WP_ALERT_REST_BLOCKED, true );
+ if ( ! empty( $alert_message ) ) {
+ $this->fail2wp_alert_send( $alert_message );
+ }
+ return new \WP_Error(
+ 'rest_no_route',
+ // This text is taken verbatim from WordPress and will thus be
+ // translated in the same way.
+ __( 'No route was found matching the URL and request method.' ),
+ array( 'status' => '404' )
+ );
+ }
+ // Figure out what has been requested and where we live
+ $namespaces = $this->fail2wp_get_rest_ns( $rest_server );
+ $route = $request->get_route();
+ $wp_route = '';
+ $is_wp_route = false;
+ if ( ! empty( $route ) ) {
+ // "Normalize" route, remove leading /
+ if ( $this->fail2wp_have_mbstring ) {
+ $route = mb_substr( $route, 1 );
+ $is_wp_route = ( mb_stripos( $route, 'wp/v2/' ) === 0 );
+ if ( $is_wp_route ) {
+ $wp_route = mb_substr( $route, 6 );
+ }
+ } else {
+ $route = substr( $route, 1 );
+ $is_wp_route = ( stripos( $route, 'wp/v2/' ) === 0 );
+ if ( $is_wp_route ) {
+ $wp_route = substr( $route, 6 );
+ }
+ }
+ }
+ // Figure out if we're blocking index requests and if this is one
+ if ( in_array( $route, $namespaces ) ) {
+ if ( defined( 'FAIL2WP_REST_DEBUG' ) ) {
+ error_log( basename(__FILE__) . ' (' . __FUNCTION__ . '): We are blocking REST API requests for index' );
+ }
+ $alert_message = $this->fail2wp_make_alert_message( $remote_ip_cf, null, FAIL2WP_ALERT_REST_BLOCKED, true );
+ if ( ! empty( $alert_message ) ) {
+ $this->fail2wp_alert_send( $alert_message );
+ }
+ return new \WP_Error(
+ 'rest_no_route',
+ // This text is taken verbatim from WordPress and will thus be
+ // translated in the same way.
+ __( 'No route was found matching the URL and request method.' ),
+ array( 'status' => '404' )
+ );
+ }
+ // Figure out if we're blocking this namespace. Request will be /...
+ // Namespaces to block are stored without the leading slash.
+ if ( ! empty( $this->fail2wp_rest_filter_block_ns ) ) {
+ if ( in_array( $route, $this->fail2wp_rest_filter_block_ns ) ) {
+ if ( defined( 'FAIL2WP_REST_DEBUG' ) ) {
+ error_log( basename(__FILE__) . ' (' . __FUNCTION__ . '): We are blocking REST API requests for NS "' . $route . '"' );
+ }
+ $alert_message = $this->fail2wp_make_alert_message( $remote_ip_cf, null, FAIL2WP_ALERT_REST_BLOCKED, true );
+ if ( ! empty( $alert_message ) ) {
+ $this->fail2wp_alert_send( $alert_message );
+ }
+ return new \WP_Error(
+ 'rest_no_route',
+ // This text is taken verbatim from WordPress and will thus be
+ // translated in the same way.
+ __( 'No route was found matching the URL and request method.' ),
+ array( 'status' => '404' )
+ );
+ }
+ }
+ // Figure out if we're blocking this WP REST API route. Request will be
+ // /... Routes to block are stored without the leading slash.
+ if ( $is_wp_route && ! empty( $this->fail2wp_rest_filter_block_routes ) ) {
+ if ( in_array( $wp_route, $this->fail2wp_rest_filter_block_routes ) ) {
+ if ( defined( 'FAIL2WP_REST_DEBUG' ) ) {
+ error_log( basename(__FILE__) . ' (' . __FUNCTION__ . '): We are blocking REST API requests for route "' . $route . '"' );
+ }
+ $alert_message = $this->fail2wp_make_alert_message( $remote_ip_cf, null, FAIL2WP_ALERT_REST_BLOCKED, true );
+ if ( ! empty( $alert_message ) ) {
+ $this->fail2wp_alert_send( $alert_message );
+ }
+ return new \WP_Error(
+ 'rest_no_route',
+ // This text is taken verbatim from WordPress and will thus be
+ // translated in the same way.
+ __( 'No route was found matching the URL and request method.' ),
+ array( 'status' => '404' )
+ );
+ }
+ }
+ // Carry on ...
+ return( $result );
+ }
+
+ /**
+ * Checks username and e-mail address prior to registration.
+ *
+ * Hooks 'registration_errors' and validates username and e-mail address
+ * according to our settings.
+ *
+ * @since 1.1.0
+ * @param WP_Error $errors.
+ * @param string $sanitized_user_login.
+ * @param string $user_email.
+ * @return WP_Error With or without errors
+ */
+ public function fail2wp_admin_check_new_user( \WP_Error $errors, string $user_login, string $user_email ) {
+ // apply_filters( 'registration_errors', $errors, $sanitized_user_login, $user_email );
+ if ( ! is_object( $errors ) || ! is_a( $errors, 'WP_Error' ) ) {
+ //Make sure we have what we need, otherwise just return it
+ if ( is_object( $errors ) ) {
+ error_log( basename(__FILE__) . ' (' . __FUNCTION__ . '): Unknown context "' . get_class( $errors ) . '"' );
+ } else {
+ error_log( basename(__FILE__) . ' (' . __FUNCTION__ . '): Unknown context "' . $errors . '"' );
+ }
+ return( $errors );
+ }
+ error_log(print_r($errors, true));
+ // Check for existing error message and possibly make it less revealing
+ if ( $this->fail2wp_secure_login_message ) {
+ if ( ! empty( $errors->errors['username_exists'] ) ) {
+ $errors->remove( 'username_exists' );
+ $errors->add( 'username_exists', esc_html__( 'Invalid username, please try again.', $this->plugin_name ) );
+ return( $errors );
+ } elseif ( ! empty( $errors->errors['email_exists'] ) ) {
+ $errors->remove( 'email_exists' );
+ $errors->add( 'email_exists', esc_html__( 'Invalid e-mail address, please try again.', $this->plugin_name ) );
+ return( $errors );
+ }
+ }
+ // Check usernames
+ $have_error = false;
+ if ( empty( $user_login ) || in_array( $user_login, $this->fail2wp_reguser_username_ban ) ) {
+ $errors->add( 'fail2wp_username_ban', esc_html__( 'Invalid username, please try again.', $this->plugin_name ) );
+ $have_error = true;
+ } elseif ( $this->fail2wp_reguser_username_length > 1 ) {
+ if ( $this->fail2wp_have_mbstring ) {
+ if ( mb_strlen( $user_login ) < $this->fail2wp_reguser_username_length ) {
+ $have_error = true;
+ }
+ } elseif ( strlen( $user_login ) < $this->fail2wp_reguser_username_length ) {
+ $have_error = true;
+ }
+ if ( $have_error ) {
+ $errors->add( 'fail2wp_username_ban', esc_html__( 'Invalid username, please try again.', $this->plugin_name ) );
+ }
+ }
+ if ( ! $have_error ) {
+ $invalid_email = true;
+ if ( ! empty ( $user_email ) ) {
+ $invalid_email = true;
+ if ( $this->fail2wp_have_mbstring ) {
+ // mb_stripos
+ foreach( $this->fail2wp_reguser_useremail_require as $email_required ) {
+ if ( mb_stripos( $user_email, $email_required ) !== false ) {
+ $invalid_email = false;
+ break;
+ }
+ }// foreach
+ } else {
+ // stripos
+ foreach( $this->fail2wp_reguser_useremail_require as $email_required ) {
+ if ( stripos( $user_email, $email_required ) !== false ) {
+ $invalid_email = false;
+ break;
+ }
+ }// foreach
+ }
+ }// !empty email
+ if ( $invalid_email ) {
+ error_log( FAIL2WP_PLUGINNAME_HUMAN . ': ' .
+ '"' . $user_email . '" is not an acceptable e-mail address' );
+ $errors->add( 'fail2wp_username_ban', esc_html__( 'Invalid e-mail address, please try again.', $this->plugin_name ) );
+ }
+ }// e-mail
+
+ return( $errors );
+ }
+
+ /*
+ * Display admin alert about setting default user role.
+ *
+ * This will display an admin alert if we have overridden WordPress'
+ * default new user role.
+ *
+ * @since 1.1.0
+ */
+ public function fail2wp_admin_alert_new_user_role_forced() {
+ echo '
'.
+ ' ' .
+ esc_html__( 'New user role default has been reset according to your', $this->plugin_name ) .
+ ' ' .
+ FAIL2WP_PLUGINNAME_HUMAN .
+ ' ' .
+ esc_html__( 'settings', $this->plugin_name ) .
+ '!' .
+ '
';
+ echo '
';
+ }
+ // Send the same alert via e-mail
+ public function fail2wp_admin_alert_new_user_role_forced_email() {
+ $admin_email = get_option( 'admin_email', null );
+ if ( empty( $admin_email ) || $admin_email === null ) {
+ error_log( FAIL2WP_PLUGINNAME_HUMAN . ': ' .
+ 'No admin e-mail address from WordPress!' );
+ return;
+ }
+ if ( ! empty($this->fail2wp_site_label ) ) {
+ $from_site = $this->fail2wp_site_label;
+ } else {
+ $from_site = $this->fail2wp_get_site_label( true );
+ }
+ wp_mail( $admin_email,
+ __( 'Notification about new user role from', $this->plugin_name ) . ' ' . FAIL2WP_PLUGINNAME_HUMAN,
+ "\n" .
+ __( 'This is a notification from', $this->plugin_name ) . ' ' . FAIL2WP_PLUGINNAME_HUMAN .
+ "\n\n" .
+ __( 'The role for newly registered users has been reset to your configured setting.', $this->plugin_name ) .
+ "\n\n" .
+ __( 'This notification was sent from the site', $this->plugin_name ) .
+ ' ' . $from_site . "\n\n" .
+ __( 'You may access the admin interface to the site here', $this->plugin_name ) .
+ ': ' . admin_url() .
+ "\n"
+ ,
+ $this->fail2wp_mail_headers );
+ }
+ /**
+ * Display admin alert about default user role.
+ *
+ * This will display an admin alert if the default new user role does not
+ * match that configured in Fail2WP.
+ *
+ * @since 1.1.0
+ */
+ public function fail2wp_admin_alert_new_users_mismatch() {
+ echo '
'.
+ ' ' .
+ esc_html__( 'User registration is enabled, but the new user role does not match', $this->plugin_name ) .
+ ' ' .
+ FAIL2WP_PLUGINNAME_HUMAN .
+ '!' .
+ '
';
+ if ( is_admin( ) && is_user_logged_in() && current_user_can( 'administrator' ) ) {
+ // Include link to General settings, if we're not on that page already
+ if ( empty( $_SERVER['REQUEST_URI'] ) || basename( $_SERVER['REQUEST_URI'] ) != 'options-general.php' ) {
+ $action = admin_url( 'options-general.php' );
+ echo esc_html__( 'Go to', $this->plugin_name ) .
+ ' ' .
+ esc_html__( 'General settings', $this->plugin_name ) .
+ ' ' .
+ esc_html__( 'to check your membership and new user settings.', $this->plugin_name ).
+ '
';
+ }
+ }
+ echo '
';
+ }
+ /*
+ * Display admin alert about default user role.
+ *
+ * This will display an admin alert if the default new user role is set to
+ * 'administrator'.
+ *
+ * @since 1.1.0
+ */
+ public function fail2wp_admin_alert_new_users_admin() {
+ echo '
'.
+ ' ' .
+ esc_html__( 'User registration is enabled, and new users will be administrators', $this->plugin_name ) .
+ ' ' .
+ FAIL2WP_PLUGINNAME_HUMAN .
+ '!' .
+ '
';
+ if ( is_admin( ) && is_user_logged_in() && current_user_can( 'administrator' ) ) {
+ // Include link to General settings, if we're not on that page already
+ if ( empty( $_SERVER['REQUEST_URI'] ) || basename( $_SERVER['REQUEST_URI'] ) != 'options-general.php' ) {
+ $action = admin_url( 'options-general.php' );
+ echo esc_html__( 'Go to', $this->plugin_name ) .
+ ' ' .
+ esc_html__( 'General settings', $this->plugin_name ) .
+ ' ' .
+ esc_html__( 'to check your membership and new user settings.', $this->plugin_name ).
+ '
';
+ }
+ }
+ echo '
';
+ }
+ /*
+ * Display admin alert about default user role.
+ *
+ * This will display an admin alert if the default new user role is not set
+ *
+ * @since 1.1.0
+ */
+ public function fail2wp_admin_alert_new_users_null() {
+ echo '
'.
+ ' ' .
+ esc_html__( 'User registration is enabled, but no role has been configured', $this->plugin_name ) .
+ ' ' .
+ FAIL2WP_PLUGINNAME_HUMAN .
+ '!' .
+ '
';
+ if ( is_admin( ) && is_user_logged_in() && current_user_can( 'administrator' ) ) {
+ // Include link to General settings, if we're not on that page already
+ if ( empty( $_SERVER['REQUEST_URI'] ) || basename( $_SERVER['REQUEST_URI'] ) != 'options-general.php' ) {
+ $action = admin_url( 'options-general.php' );
+ echo esc_html__( 'Go to', $this->plugin_name ) .
+ ' ' .
+ esc_html__( 'General settings', $this->plugin_name ) .
+ ' ' .
+ esc_html__( 'to check your membership and new user settings.', $this->plugin_name ).
+ '
' . esc_html__( 'Provides authentication related logging and security functions for WordPress, suitable for use with Fail2ban', $this->plugin_name ) . '
';
- $html .= '
';
- $html .= ''; // tab-content
$html .= ''; // wrap
//
- echo $html;
+ echo $tab_header . $html;
}
/**
* Display about/support.
@@ -363,10 +1349,15 @@ public function fail2wp_about_page() {
' WebbPlatsen i Sverige AB '.
esc_html__('in Stockholm, Sweden. We speak Swedish and English', $this->plugin_name ) . ' :-)' .
'
' .
- esc_html__( 'The plugin is written by Joaquim Homrighausen and sponsored by WebbPlatsen i Sverige AB.', $this->plugin_name ) . '
' .
- '
' . esc_html__( 'If you find this plugin useful, the author is happy to receive a donation, good review, or just a kind word.', $this->plugin_name ) . '
' .
- '
' . esc_html__( 'If there is something you feel to be missing from this plugin, or if you have found a problem with the code or a feature, please do not hesitate to reach out to', $this->plugin_name ) .
- ' support@webbplatsen.se' . '
';
+ esc_html__( 'The plugin is written by Joaquim Homrighausen and sponsored by WebbPlatsen i Sverige AB.', $this->plugin_name ) .
+ '' .
+ '
' . esc_html__( 'If you find this plugin useful, the author is happy to receive a donation, good review, or just a kind word.', $this->plugin_name ) . ' ' .
+ esc_html__( 'If there is something you feel to be missing from this plugin, or if you have found a problem with the code or a feature, please do not hesitate to reach out to', $this->plugin_name ) .
+ ' support@webbplatsen.se' . ' '.
+ esc_html__( 'There is more documentation available at', $this->plugin_name ) . ' ' .
+ ''.
+ 'code.webbplatsen.net/documentation/fail2wp/' .
+ '
';
+ }
+ public function fail2wp_settings_reguser_username_length() {
+ echo '';
+ echo '
' . esc_html__( 'Minimum length of usernames, 2-200 characters, 0 ignores the setting', $this->plugin_name ) . '
';
+ }
+ public function fail2wp_settings_reguser_username_ban() {
+ echo '';
+ echo '
' . esc_html__( 'These usernames will be blocked for new user registrations', $this->plugin_name ) . '
';
+ }
+ public function fail2wp_settings_reguser_useremail_require() {
+ echo '';
+ echo '
' . esc_html__( 'E-mail address must match at least one of these for new users', $this->plugin_name ) . '
';
+ }
+ public function fail2wp_setting_remove_generator() {
+ echo '
';
+ echo ' ';
+ echo '
';
+ }
+ public function fail2wp_setting_remove_feeds() {
+ echo '
';
+ echo ' ';
+ echo '
';
+ }
+ // @since 1.0.0
public function fail2wp_setting_remove() {
- $option_val = $this->fail2wp_get_option( 'fail2wp-settings-remove', false );
echo '
';
echo ' ';
echo '
';
}
public function fail2wp_setting_block_enums() {
- $option_val = $this->fail2wp_get_option( 'fail2wp-block-user-enum', false );
echo '
';
echo ' ';
echo '
';
}
public function fail2wp_setting_block_username_login() {
- $option_val = $this->fail2wp_get_option( 'fail2wp-block-username-login', false );
echo '
';
echo ' ';
echo '
';
}
public function fail2wp_setting_secure_login_messages() {
- $option_val = $this->fail2wp_get_option( 'fail2wp-secure-login-message', false );
echo '
';
echo ' ';
echo '
';
}
+ public function fail2wp_settings_restapi_callback() {
+ if ( ! is_admin( ) || ! is_user_logged_in() || ! current_user_can( 'administrator' ) ) {
+ return;
+ }
+ echo '
' .
+ esc_html__( 'Please make sure you understand how these settings can impact the operation of WordPress and other plugins before making changes to them.', $this->plugin_name ) .
+ '
';
+ $rest_url = get_rest_url();
+ echo '
' .
+ esc_html__( 'The REST API URL of this site is', $this->plugin_name ) .
+ ': ' . esc_html( $rest_url ) . '' .
+ '
';
+ // Possibly output notice about blocking settings
+ if ( $this->fail2wp_rest_filter_require_authenticated ) {
+ echo '
' . esc_html__( 'NOTE', $this->plugin_name ) . ': ' .
+ esc_html__( 'If "Require authentication" is enabled, no REST API calls will be blocked for logged in users and/or authenticated requests!', $this->plugin_name ) .
+ '
';
+ }
+ }
public function fail2wp_settings_advanced_callback() {
if ( ! is_admin( ) || ! is_user_logged_in() || ! current_user_can( 'administrator' ) ) {
return;
}
- echo '
'.
- esc_html__( 'Please make sure you understand how these settings can impact the operation of the plugin before making changes to them.', $this->plugin_name ).
+ echo '
' .
+ esc_html__( 'Please make sure you understand how these settings can impact the operation of the plugin before making changes to them.', $this->plugin_name ) .
'
';
}
public function fail2wp_settings_cloudflare_callback() {
@@ -595,46 +1774,100 @@ public function fail2wp_settings_cloudflare_callback() {
''.
'';
}
+ // @since 1.1.0
+ public function fail2wp_setting_rest_filter_require_authenticated() {
+ echo '
';
+ echo ' ';
+ echo '
';
+ }
+ public function fail2wp_setting_rest_filter_block_index() {
+ echo '
';
+ echo ' ';
+ echo '
';
+ }
+ public function fail2wp_setting_rest_filter_log_blocked() {
+ echo '
';
+ echo ' ';
+ echo '
';
+ }
+ public function fail2wp_setting_rest_filter_block_all() {
+ echo '
';
+ echo ' ';
+ echo '
';
+ }
+ public function fail2wp_setting_rest_filter_block_ns() {
+ $rest_ns = $this->fail2wp_get_rest_ns();
+ if ( ! is_array( $rest_ns ) || empty( $rest_ns ) ) {
+ echo '
';
+ echo esc_html__( 'WordPress did not return any available namespaces', $this->plugin_name );
+ echo '