This commit is contained in:
Mauro Torrez 2019-09-17 18:28:38 -03:00
commit 3a8e77323e
30 changed files with 3648 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
*~
\#*
.#*

66
admin/admin.php Normal file
View File

@ -0,0 +1,66 @@
<?php
/**
* Admin
*
* @package wp-fail2ban
* @since 4.0.0
*/
namespace org\lecklider\charles\wordpress\wp_fail2ban;
if ( !defined( 'ABSPATH' ) ) {
exit;
}
require __DIR__ . '/config.php';
require __DIR__ . '/lib/about.php';
/**
* Register admin menus
*
* @since 4.0.0
*/
function admin_menu()
{
global $submenu ;
add_menu_page(
'WP fail2ban',
'WP fail2ban',
'manage_options',
'wp-fail2ban',
__NAMESPACE__ . '\\about',
'dashicons-analytics'
);
add_submenu_page(
'wp-fail2ban',
'Settings',
'Settings',
'manage_options',
'wp-fail2ban-settings',
__NAMESPACE__ . '\\settings'
);
$submenu['wp-fail2ban'][0][0] = __( 'Welcome' );
}
add_action( 'admin_menu', __NAMESPACE__ . '\\admin_menu' );
/**
* Add Settings link on Plugins page
*
* @since 4.2.0
*
* @param array $links
* @param string $file
*/
function plugin_action_links( $links, $file )
{
if ( preg_match( "|{$file}\$|", WP_FAIL2BAN_FILE ) ) {
// Add Settings at the start
array_unshift( $links, '<a href="' . admin_url( 'admin.php' ) . '?page=wp-fail2ban-settings&tab=about">Settings</a>' );
}
return $links;
}
add_filter(
'plugin_action_links',
__NAMESPACE__ . '\\plugin_action_links',
10,
2
);

69
admin/config.php Normal file
View File

@ -0,0 +1,69 @@
<?php
/**
* Config
*
* @package wp-fail2ban
* @since 4.0.0
*/
namespace org\lecklider\charles\wordpress\wp_fail2ban;
if ( !defined( 'ABSPATH' ) ) {
exit;
}
require_once 'lib/tab.php';
foreach ( glob( __DIR__ . '/config/*.php' ) as $filename ) {
require_once $filename;
}
/**
* Render Settings
*
* @since 4.0.0
*/
function settings()
{
$tabs = [
'logging',
'syslog',
'block',
'remote-ips',
'plugins'
];
$title = 'WP fail2ban';
?>
<div class="wrap">
<h1><?php
echo $title ;
?></h1>
<hr class="wp-header-end">
<h2 class="nav-tab-wrapper wp-clearfix">
<?php
$active_tab = Tab::getActiveTab( 'logging' );
foreach ( $tabs as $slug ) {
$class = 'nav-tab';
if ( $active_tab->getSlug() == $slug ) {
$class .= ' nav-tab-active';
}
printf(
'<a class="%s" href="?page=wp-fail2ban-settings&tab=%s">%s</a>',
$class,
$slug,
Tab::getTabName( $slug )
);
}
?>
</h2>
<form action="options.php?tab=<?php
echo $active_tab->getSlug() ;
?>" method="post">
<?php
settings_fields( 'wp-fail2ban' );
$active_tab->render();
echo '<hr><p>' . __( '<strong>Note:</strong> The Free version of <em>WP fail2ban</em> is configured by defining constants in <tt>wp-config.php</tt>; these tabs display those values.<br>Upgrade to the Premium version to enable this interface.' ) . '</p>' ;
?>
</form>
</div>
<?php
}

120
admin/config/block.php Normal file
View File

@ -0,0 +1,120 @@
<?php
/**
* Settings - Block
*
* @package wp-fail2ban
* @since 4.0.0
*/
namespace org\lecklider\charles\wordpress\wp_fail2ban;
if ( !defined( 'ABSPATH' ) ) {
exit;
}
/**
* Tab: Block
*
* @since 4.0.0
*/
class TabBlock extends Tab
{
/**
* {@inheritDoc}
*
* @since 4.0.0
*/
public function __construct()
{
add_action( 'admin_init', [ $this, 'admin_init' ] );
parent::__construct( 'block', 'Users' );
}
/**
* {@inheritDoc}
*
* @since 4.0.0
*/
public function admin_init()
{
// phpcs:disable Generic.Functions.FunctionCallArgumentSpacing
add_settings_section(
'wp-fail2ban-block',
__( 'Block' ),
[ $this, 'section' ],
'wp-fail2ban-block'
);
add_settings_field(
'block-user-enumeration',
parent::doc_link( 'WP_FAIL2BAN_BLOCK_USER_ENUMERATION', __( 'User Enumeration' ) ),
[ $this, 'userEnumeration' ],
'wp-fail2ban-block',
'wp-fail2ban-block'
);
add_settings_field(
'block-users',
parent::doc_link( 'WP_FAIL2BAN_BLOCKED_USERS', __( 'Usernames' ) ),
[ $this, 'usernames' ],
'wp-fail2ban-block',
'wp-fail2ban-block'
);
// phpcs:enable
}
/**
* {@inheritDoc}
*
* @since 4.0.0
*
* @param array $settings
* @param array $input
*/
public function sanitize( array $settings, array $input = null )
{
return $settings;
}
/**
* {@inheritDoc}
*
* @since 4.0.0
*/
public function section()
{
echo '' ;
}
/**
* User Enumeration
*
* @since 4.0.0
*/
public function userEnumeration()
{
printf( '<input type="checkbox" disabled="disabled" %s>', checked( WP_FAIL2BAN_BLOCK_USER_ENUMERATION, true, false ) );
}
/**
* Blocked usernames
*
* @since 4.0.0
*/
public function usernames()
{
if ( defined( 'WP_FAIL2BAN_BLOCKED_USERS' ) ) {
if ( is_array( WP_FAIL2BAN_BLOCKED_USERS ) ) {
$value = join( ', ', WP_FAIL2BAN_BLOCKED_USERS );
} else {
$value = WP_FAIL2BAN_BLOCKED_USERS;
}
} else {
$value = '';
}
printf( '<input class="regular-text" type="text" disabled="disabled" value="%s">', esc_attr( $value ) );
}
}
new TabBlock();

255
admin/config/logging.php Normal file
View File

@ -0,0 +1,255 @@
<?php
/**
* Settings - Logging
*
* @package wp-fail2ban
* @since 4.0.0
*/
namespace org\lecklider\charles\wordpress\wp_fail2ban;
if ( !defined( 'ABSPATH' ) ) {
exit;
}
/**
* Tab: Logging
*
* @since 4.0.0
*/
class TabLogging extends Tab
{
/**
* {@inheritDoc}
*/
public function __construct()
{
add_action( 'admin_init', [ $this, 'admin_init' ], 100 );
parent::__construct( 'logging', 'Logging' );
}
/**
* {@inheritDoc}
*
* @since 4.0.0
*/
public function admin_init()
{
// phpcs:disable Generic.Functions.FunctionCallArgumentSpacing
add_settings_section(
'wp-fail2ban-logging',
__( 'What & Where' ),
[ $this, 'sectionWhatWhere' ],
'wp-fail2ban-logging'
);
add_settings_field(
'logging-log-authentication',
parent::doc_link( 'WP_FAIL2BAN_AUTH_LOG', __( 'Authentication' ) ),
[ $this, 'authentication' ],
'wp-fail2ban-logging',
'wp-fail2ban-logging'
);
add_settings_field(
'logging-log-comments',
parent::doc_link( 'WP_FAIL2BAN_LOG_COMMENTS', __( 'Comments' ) ),
[ $this, 'comments' ],
'wp-fail2ban-logging',
'wp-fail2ban-logging'
);
add_settings_field(
'logging-log-spam',
parent::doc_link( 'WP_FAIL2BAN_LOG_SPAM', __( 'Spam' ) ),
[ $this, 'spam' ],
'wp-fail2ban-logging',
'wp-fail2ban-logging'
);
add_settings_field(
'logging-log-password-request',
parent::doc_link( 'WP_FAIL2BAN_LOG_PASSWORD_REQUEST', __( 'Password Requests' ) ),
[ $this, 'passwordRequest' ],
'wp-fail2ban-logging',
'wp-fail2ban-logging'
);
add_settings_field(
'logging-log-pingbacks',
parent::doc_link( 'WP_FAIL2BAN_LOG_PINGBACKS', __( 'Pingbacks' ) ),
[ $this, 'pingbacks' ],
'wp-fail2ban-logging',
'wp-fail2ban-logging'
);
// phpcs:enable
}
/**
* {@inheritDoc}
*
* @since 4.0.0
*/
public function render()
{
parent::render();
}
/**
* {@inheritDoc}
*
* @since 4.0.0
*
* @param array $settings {@inheritDoc}
* @param array $input {@inheritDoc}
*
* @return array {@inheritDoc}
*/
public function sanitize( array $settings, array $input = null )
{
return $settings;
}
/**
* Section summary.
*
* @since 4.0.0
*/
public function sectionWhatWhere()
{
echo '' ;
}
/**
* Authentication.
*
* @since 4.0.0
*/
public function authentication()
{
printf( '<label>%s: %s</label>', __( 'Use facility' ), $this->getLogFacilities( 'WP_FAIL2BAN_AUTH_LOG', true ) );
}
/**
* Comments.
*
* @since 4.0.0
*/
public function comments()
{
add_filter(
'wp_fail2ban_log_WP_FAIL2BAN_LOG_COMMENTS',
[ $this, 'commentsExtra' ],
10,
3
);
$this->log(
'WP_FAIL2BAN_LOG_COMMENTS',
'WP_FAIL2BAN_COMMENT_LOG',
'',
[ 'comments-extra', 'logging-comments-extra-facility' ]
);
}
/**
* Comments extra helper - checked.
*
* @since 4.0.0
*
* @param int $value Value to check
*/
protected function commentExtraChecked( $value )
{
if ( !defined( 'WP_FAIL2BAN_LOG_COMMENTS_EXTRA' ) ) {
return '';
}
return checked( $value & WP_FAIL2BAN_LOG_COMMENTS_EXTRA, $value, false );
}
/**
* Comments extra helper - disabled.
*
* @since 4.0.0
*/
protected function commentExtraDisabled()
{
return 'disabled="disabled';
}
/**
* Comments extra.
*
* @since 4.0.0
*
* @param string $html HTML prefixed to output
* @param string $define_name Not used
* @param string $define_log Not used
*
* @return string
*/
public function commentsExtra( $html, $define_name, $define_log )
{
$fmt = <<<___HTML___
<table>
<tr>
<th>%s</th>
<td>
<fieldset id="comments-extra" disabled="disabled">
<label><input type="checkbox" %s> %s</label><br>
<label><input type="checkbox" %s> %s</label><br>
<label><input type="checkbox" %s> %s</label><br>
<label><input type="checkbox" %s> %s</label><br>
<label><input type="checkbox" %s> %s</label>
</fieldset>
</td>
</tr>
<tr>
<th>%s</th>
<td>%s</td>
</tr>
</table>
___HTML___;
return $html . sprintf(
$fmt,
parent::doc_link( 'WP_FAIL2BAN_LOG_COMMENTS_EXTRA', __( 'Also log:' ) ),
$this->commentExtraChecked( WPF2B_EVENT_COMMENT_NOT_FOUND ),
__( 'Post not found' ),
$this->commentExtraChecked( WPF2B_EVENT_COMMENT_CLOSED ),
__( 'Comments closed' ),
$this->commentExtraChecked( WPF2B_EVENT_COMMENT_TRASH ),
__( 'Trash post' ),
$this->commentExtraChecked( WPF2B_EVENT_COMMENT_DRAFT ),
__( 'Draft post' ),
$this->commentExtraChecked( WPF2B_EVENT_COMMENT_PASSWORD ),
__( 'Password-protected post' ),
parent::doc_link( 'WP_FAIL2BAN_COMMENT_EXTRA_LOG', __( 'Use facility:' ) ),
$this->getLogFacilities( 'WP_FAIL2BAN_COMMENT_EXTRA_LOG', false )
);
}
/**
* Password request
*
* @since 4.0.0
*/
public function passwordRequest()
{
$this->log( 'WP_FAIL2BAN_LOG_PASSWORD_REQUEST', 'WP_FAIL2BAN_PASSWORD_REQUEST_LOG' );
}
/**
* Pingbacks
*
* @since 4.0.0
*/
public function pingbacks()
{
$this->log( 'WP_FAIL2BAN_LOG_PINGBACKS', 'WP_FAIL2BAN_PINGBACK_LOG' );
}
/**
* Spam
*
* @since 4.0.0
*/
public function spam()
{
$this->log( 'WP_FAIL2BAN_LOG_SPAM', 'WP_FAIL2BAN_SPAM_LOG' );
}
}
new TabLogging();

185
admin/config/plugins.php Normal file
View File

@ -0,0 +1,185 @@
<?php
/**
* Settings - Plugins
*
* @package wp-fail2ban
* @since 4.2.0
*/
namespace org\lecklider\charles\wordpress\wp_fail2ban;
if ( !defined( 'ABSPATH' ) ) {
exit;
}
/**
* Tab: Plugins
*
* @since 4.2.0
*/
class TabPlugins extends Tab
{
/**
* {@inheritDoc}
*/
public function __construct()
{
add_action( 'admin_init', [ $this, 'admin_init' ], 100 );
parent::__construct( 'plugins', 'Plugins' );
}
/**
* {@inheritDoc}
*
* @since 4.0.0
*/
public function admin_init()
{
// phpcs:disable Generic.Functions.FunctionCallArgumentSpacing
add_settings_section(
'wp-fail2ban-plugins',
__( 'Event Class Facilities' ),
[ $this, 'sectionLoggingEventClasses' ],
'wp-fail2ban-plugins'
);
add_settings_field(
'plugins-log-auth',
parent::doc_link( 'WP_FAIL2BAN_PLUGIN_LOG_AUTH', __( 'Authentication' ) ),
[ $this, 'auth' ],
'wp-fail2ban-plugins',
'wp-fail2ban-plugins'
);
add_settings_field(
'plugins-log-comment',
parent::doc_link( 'WP_FAIL2BAN_PLUGIN_LOG_COMMENT', __( 'Comment' ) ),
[ $this, 'comment' ],
'wp-fail2ban-plugins',
'wp-fail2ban-plugins'
);
add_settings_field(
'plugins-log-password',
parent::doc_link( 'WP_FAIL2BAN_PLUGIN_LOG_PASSWORD', __( 'Password' ) ),
[ $this, 'password' ],
'wp-fail2ban-plugins',
'wp-fail2ban-plugins'
);
add_settings_field(
'plugins-log-rest',
parent::doc_link( 'WP_FAIL2BAN_PLUGIN_LOG_REST', __( 'REST' ) ),
[ $this, 'rest' ],
'wp-fail2ban-plugins',
'wp-fail2ban-plugins'
);
add_settings_field(
'plugins-log-spam',
parent::doc_link( 'WP_FAIL2BAN_PLUGIN_LOG_SPAM', __( 'Spam' ) ),
[ $this, 'spam' ],
'wp-fail2ban-plugins',
'wp-fail2ban-plugins'
);
add_settings_field(
'plugins-log-xmlrpc',
parent::doc_link( 'WP_FAIL2BAN_PLUGIN_LOG_XMLRPC', __( 'XML-RPC' ) ),
[ $this, 'xmlrpc' ],
'wp-fail2ban-plugins',
'wp-fail2ban-plugins'
);
// phpcs:enable
}
/**
* {@inheritDoc}
*
* @since 4.2.0
*/
public function render()
{
parent::render();
}
/**
* {@inheritDoc}
*
* @since 4.2.0
*
* @param array $settings {@inheritDoc}
* @param array $input {@inheritDoc}
*
* @return array {@inheritDoc}
*/
public function sanitize( array $settings, array $input = null )
{
return $settings;
}
/**
* Section summary.
*
* @since 4.2.0
*/
public function sectionLoggingEventClasses()
{
echo __( 'Facilities to use for plugin-generated messages. The defaults follow the Core defaults.' ) ;
}
/**
* Auth
*
* @since 4.2.0
*/
public function auth()
{
$this->log( 'WP_FAIL2BAN_PLUGIN_LOG_AUTH', 'WP_FAIL2BAN_PLUGIN_AUTH_LOG' );
}
/**
* Comment
*
* @since 4.2.0
*/
public function comment()
{
$this->log( 'WP_FAIL2BAN_PLUGIN_LOG_COMMENT', 'WP_FAIL2BAN_PLUGIN_COMMENT_LOG' );
}
/**
* Password
*
* @since 4.2.0
*/
public function password()
{
$this->log( 'WP_FAIL2BAN_PLUGIN_LOG_PASSWORD', 'WP_FAIL2BAN_PLUGIN_PASSWORD_LOG' );
}
/**
* REST
*
* @since 4.2.0
*/
public function rest()
{
$this->log( 'WP_FAIL2BAN_PLUGIN_LOG_REST', 'WP_FAIL2BAN_PLUGIN_REST_LOG' );
}
/**
* Spam
*
* @since 4.2.0
*/
public function spam()
{
$this->log( 'WP_FAIL2BAN_PLUGIN_LOG_SPAM', 'WP_FAIL2BAN_PLUGIN_SPAM_LOG' );
}
/**
* XML-RPC
*
* @since 4.2.0
*/
public function xmlrpc()
{
$this->log( 'WP_FAIL2BAN_PLUGIN_LOG_XMLRPC', 'WP_FAIL2BAN_PLUGIN_XMLRPC_LOG' );
}
}
new TabPlugins();

100
admin/config/remote-ips.php Normal file
View File

@ -0,0 +1,100 @@
<?php
/**
* Settings - Remote IPs
*
* @package wp-fail2ban
* @since 4.0.0
*/
namespace org\lecklider\charles\wordpress\wp_fail2ban;
if ( !defined( 'ABSPATH' ) ) {
exit;
}
/**
* Tab: Remote IPs
*
* @since 4.0.0
*/
class TabRemoteIPs extends Tab
{
/**
* {@inheritDoc}
*
* @since 4.0.0
*/
public function __construct()
{
add_action( 'admin_init', [ $this, 'admin_init' ] );
parent::__construct( 'remote-ips', 'Remote IPs' );
}
/**
* {@inheritDoc}
*
* @since 4.0.0
*/
public function admin_init()
{
// phpcs:disable Generic.Functions.FunctionCallArgumentSpacing
add_settings_section(
'wp-fail2ban-proxies',
__( 'Proxies' ),
[ $this, 'section' ],
'wp-fail2ban-remote-ips'
);
add_settings_field(
'remote-ips-proxies',
parent::doc_link( 'WP_FAIL2BAN_PROXIES', __( 'IP list' ) ),
[ $this, 'proxies' ],
'wp-fail2ban-remote-ips',
'wp-fail2ban-proxies'
);
// phpcs:enable
}
/**
* {@inheritDoc}
*
* @since 4.0.0
*
* @param array $settings
* @param array $input
*/
public function sanitize( array $settings, array $input = null )
{
return $settings;
}
/**
* Section blurb.
*
* @since 4.0.0
*/
public function section()
{
echo '' ;
}
/**
* Proxies.
*
* @since 4.0.0
*/
public function proxies()
{
$value = '';
if ( defined( 'WP_FAIL2BAN_PROXIES' ) ) {
if ( is_array( WP_FAIL2BAN_PROXIES ) ) {
$value = join( "\n", WP_FAIL2BAN_PROXIES );
} else {
$value = join( "\n", array_map( 'trim', explode( ',', WP_FAIL2BAN_PROXIES ) ) );
}
}
printf( '<fieldset><textarea class="code" cols="20" rows="10" disabled="disabled">%s</textarea></fieldset>', esc_html( $value ) );
}
}
new TabRemoteIPs();

159
admin/config/syslog.php Normal file
View File

@ -0,0 +1,159 @@
<?php
/**
* Settings - syslog
*
* @package wp-fail2ban
* @since 4.0.0
*/
namespace org\lecklider\charles\wordpress\wp_fail2ban;
if ( !defined( 'ABSPATH' ) ) {
exit;
}
/**
* Tab: Syslog
*
* @since 4.0.0
*/
class TabSyslog extends Tab
{
/**
* {@inheritDoc}
*/
public function __construct()
{
add_action( 'admin_init', [ $this, 'admin_init' ], 100 );
parent::__construct( 'syslog', '<tt>syslog</tt>' );
}
/**
* {@inheritDoc}
*
* @since 4.0.0
*/
public function admin_init()
{
// phpcs:disable Generic.Functions.FunctionCallArgumentSpacing
add_settings_section(
'wp-fail2ban-connection',
__( 'Connection' ),
[ $this, 'sectionConnection' ],
'wp-fail2ban-syslog'
);
add_settings_field(
'logging-connection',
parent::doc_link( 'WP_FAIL2BAN_OPENLOG_OPTIONS', __( 'Options' ) ),
[ $this, 'connection' ],
'wp-fail2ban-syslog',
'wp-fail2ban-connection'
);
add_settings_section(
'wp-fail2ban-workarounds',
__( 'Workarounds' ),
[ $this, 'sectionWorkarounds' ],
'wp-fail2ban-syslog'
);
add_settings_field(
'logging-workarounds',
parent::doc_link( '../syslog', __( 'Options' ) ),
[ $this, 'workarounds' ],
'wp-fail2ban-syslog',
'wp-fail2ban-workarounds'
);
// phpcs:enable
}
/**
* {@inheritDoc}
*
* @since 4.0.0
*
* @param array $settings {@inheritDoc}
* @param array $input {@inheritDoc}
*
* @return array {@inheritDoc}
*/
public function sanitize( array $settings, array $input = null )
{
return $settings;
}
/**
* Connection section blurb.
*
* @since 4.0.0
*/
public function sectionConnection()
{
echo '' ;
}
/**
* Connection.
*
* @since 4.0.0
*/
public function connection()
{
$class = '';
$fmt = <<<___STR___
<fieldset>
<label><input type="checkbox" disabled="disabled" %s> <code>LOG_CONS</code></label><br>
<label><input type="checkbox" disabled="disabled" %s> <code>LOG_PERROR</code></label><br>
<label><input type="checkbox" disabled="disabled" %s> <code>LOG_PID</code> <em>(%s)</em></label><br>
<label><input type="radio" disabled="disabled" %s> <code>LOG_NDELAY</code> <em>(%s)</em></label><br>
<label><input type="radio" disabled="disabled" %s> <code>LOG_ODELAY</code></label>
</fieldset>
___STR___;
printf(
$fmt,
checked( WP_FAIL2BAN_OPENLOG_OPTIONS & LOG_CONS, LOG_CONS, false ),
checked( WP_FAIL2BAN_OPENLOG_OPTIONS & LOG_PERROR, LOG_PERROR, false ),
checked( WP_FAIL2BAN_OPENLOG_OPTIONS & LOG_PID, LOG_PID, false ),
__( 'default' ),
checked( WP_FAIL2BAN_OPENLOG_OPTIONS & LOG_NDELAY, LOG_NDELAY, false ),
__( 'default' ),
checked( WP_FAIL2BAN_OPENLOG_OPTIONS & LOG_ODELAY, LOG_ODELAY, false )
);
}
/**
* Workarounds section blurb.
*
* @since 4.0.0
*/
public function sectionWorkarounds()
{
echo '' ;
}
/**
* Workarounds.
*
* @since 4.0.0
*/
public function workarounds()
{
$fmt = <<<___STR___
<fieldset>
<label><input type="checkbox" disabled="disabled" %s> %s</label>
<br>
<label><input type="checkbox" disabled="disabled" %s> %s</label>
<br>
<label><input type="checkbox" disabled="disabled" %s> %s</label>
</fieldset>
___STR___;
printf(
$fmt,
checked( @WP_FAIL2BAN_SYSLOG_SHORT_TAG, true, false ),
__( 'Short Tag' ),
checked( @WP_FAIL2BAN_HTTP_HOST, true, false ),
__( 'Specify Host' ),
checked( @WP_FAIL2BAN_TRUNCATE_HOST, true, false ),
__( 'Truncate Host' )
);
}
}
new TabSyslog();

6
admin/img/docs.svg Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 26 26" version="1.1" width="16px" height="16px">
<g id="surface1">
<path style=" " d="M 7 0 C 4.796875 0 3 1.796875 3 4 L 3 22 C 3 24.203125 4.796875 26 7 26 L 19 26 C 21.203125 26 23 24.203125 23 22 L 23 8 C 23 6.9375 22.027344 5.929688 20.28125 4.21875 C 20.039063 3.980469 19.777344 3.714844 19.53125 3.46875 C 19.285156 3.222656 19.019531 2.992188 18.78125 2.75 C 17.070313 1.003906 16.0625 0 15 0 Z M 7 2 L 14.28125 2 C 15.003906 2.183594 15 3.050781 15 3.9375 L 15 7 C 15 7.550781 15.449219 8 16 8 L 19 8 C 19.996094 8 21 8.003906 21 9 L 21 22 C 21 23.105469 20.105469 24 19 24 L 7 24 C 5.894531 24 5 23.105469 5 22 L 5 4 C 5 2.894531 5.894531 2 7 2 Z M 7.8125 10 C 7.261719 10.050781 6.855469 10.542969 6.90625 11.09375 C 6.957031 11.644531 7.449219 12.050781 8 12 L 18 12 C 18.359375 12.003906 18.695313 11.816406 18.878906 11.503906 C 19.058594 11.191406 19.058594 10.808594 18.878906 10.496094 C 18.695313 10.183594 18.359375 9.996094 18 10 L 8 10 C 7.96875 10 7.9375 10 7.90625 10 C 7.875 10 7.84375 10 7.8125 10 Z M 7.8125 14 C 7.261719 14.050781 6.855469 14.542969 6.90625 15.09375 C 6.957031 15.644531 7.449219 16.050781 8 16 L 16 16 C 16.359375 16.003906 16.695313 15.816406 16.878906 15.503906 C 17.058594 15.191406 17.058594 14.808594 16.878906 14.496094 C 16.695313 14.183594 16.359375 13.996094 16 14 L 8 14 C 7.96875 14 7.9375 14 7.90625 14 C 7.875 14 7.84375 14 7.8125 14 Z M 7.8125 18 C 7.261719 18.050781 6.855469 18.542969 6.90625 19.09375 C 6.957031 19.644531 7.449219 20.050781 8 20 L 18 20 C 18.359375 20.003906 18.695313 19.816406 18.878906 19.503906 C 19.058594 19.191406 19.058594 18.808594 18.878906 18.496094 C 18.695313 18.183594 18.359375 17.996094 18 18 L 8 18 C 7.96875 18 7.9375 18 7.90625 18 C 7.875 18 7.84375 18 7.8125 18 Z "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

143
admin/lib/about.php Normal file
View File

@ -0,0 +1,143 @@
<?php
/**
* About
*
* @package wp-fail2ban
* @since 4.2.0
*/
namespace org\lecklider\charles\wordpress\wp_fail2ban;
if ( !defined( 'ABSPATH' ) ) {
exit;
}
/**
* About content
*
* @since 4.2.0
*
* @param bool $hide_title
*/
function about( $hide_title = false )
{
$wp_f2b_ver = substr( WP_FAIL2BAN_VER, 0, strrpos( WP_FAIL2BAN_VER, '.' ) );
?>
<div class="wrap">
<style>
div.inside ul {
list-style: disc;
padding-left: 2em;
}
</style>
<?php
if ( !$hide_title ) {
?>
<h1>WP fail2ban</h1>
<?php
}
?>
<div id="poststuff">
<div id="post-body" class="metabox-holder columns-2">
<div id="post-body-content">
<div class="meta-box-sortables ui-sortable">
<div class="postbox">
<h2>Version 4.2.5</h2>
<div class="inside">
<ul>
<li>Properly fix PHP 5.3 support; tested on CentOS 6. Does not support any UI or Premium features.</li>
<li>Fix potential issue with <tt>WP_FAIL2BAN_BLOCK_USER_ENUMERATION</tt> if calling REST API or XMLRPC from admin area.</li>
</ul>
</div>
</div>
</div>
<div class="meta-box-sortables ui-sortable">
<div class="postbox">
<h2>Version 4.2.4</h2>
<div class="inside">
<ul>
<li>Add filter for login failed message.</li>
<li>Fix logging spam comments from admin area.</li>
<li>Fix Settings link from Plugins page.</li>
<li>Update Freemius library.</li>
</ul>
</div>
</div>
</div>
<div class="meta-box-sortables ui-sortable">
<div class="postbox">
<h2>Version 4.2.3</h2>
<div class="inside">
<ul>
<li>Workaround for some versions of PHP 7.x that would cause <tt>define()</tt>s to be ignored.</li>
<li>Add config note to settings tabs.</li>
<li>Fix documentation links.</li>
</ul>
</div>
</div>
</div>
<div class="meta-box-sortables ui-sortable">
<div class="postbox">
<h2>Version 4.2.2</h2>
<div class="inside">
<ul>
<li>Fix 5.3 compatibility.</li>
</ul>
</div>
</div>
</div>
<div class="meta-box-sortables ui-sortable">
<div class="postbox">
<h2>Version 4.2.1</h2>
<div class="inside">
<ul>
<li>Completed support for <tt><a href="https://docs.wp-fail2ban.com/en/4.2/defines/WP_FAIL2BAN_COMMENT_EXTRA_LOG.html" target="docs.wp-fail2ban.com">WP_FAIL2BAN_COMMENT_EXTRA_LOG</a></tt>.</li>
<li>Add support for 3rd-party plugins; see <a href="https://docs.wp-fail2ban.com/en/4.2/developers.html" target="docs.wp-fail2ban.com">Developers</a>.<br>
<p><ul>
<li>Add-on for <a href="https://wordpress.org/plugins/wp-fail2ban-addon-contact-form-7/">Contact Form 7</a> (experimental).</li>
<li>Add-on for <a href="https://wordpress.org/plugins/wp-fail2ban-addon-gravity-forms/">Gravity Forms</a> (experimental).</li>
</ul></p>
</li>
<li>Change logging for known-user with incorrect password; previously logged as unknown user and matched by <tt>hard</tt> filters (due to limitations in older versions of WordPress), now logged as known user and matched by <tt>soft</tt>.</li>
<li>Bugfix for email-as-username - now logged correctly and matched by <tt>soft</tt>, not <tt>hard</tt>, filters.</li>
<li>Bugfix for regression in code to prevent Free/Premium conflict.</li>
</ul>
</div>
</div>
</div>
</div>
<div id="postbox-container-1" class="postbox-container">
<div class="meta-box-sortables">
<div class="postbox">
<h2>Getting Started</h2>
<div class="inside">
<ol>
<li><a href="https://docs.wp-fail2ban.com/en/<?php
echo $wp_f2b_ver ;
?>/introduction.html" target="docs.wp-fail2ban.com">Introduction</a></li>
<li><a href="https://docs.wp-fail2ban.com/en/<?php
echo $wp_f2b_ver ;
?>/configuration.html" target="docs.wp-fail2ban.com">Configuration</a></li>
</ol>
</div>
</div>
<div class="postbox">
<h2>Getting Help</h2>
<div class="inside">
<ul>
<?php
if ( wf_fs()->is_free_plan() ) {
?>
<li><a href="https://wordpress.org/support/plugin/wp-fail2ban/" target="_blank">WordPress.org Forum</a></li>
<?php
}
?>
</div>
</div>
</div>
</div>
</div>
&nbsp;
</div>
</div>
<?php
}

260
admin/lib/tab.php Normal file
View File

@ -0,0 +1,260 @@
<?php
/**
* Tab base class
*
* @package wp-fail2ban-premium
* @since 4.0.0
*/
namespace org\lecklider\charles\wordpress\wp_fail2ban;
if ( !defined( 'ABSPATH' ) ) {
exit;
}
/**
* Base Tab class
*
* @since 4.0.0
*/
abstract class Tab
{
/**
* @var array Array of Tab objects
*/
protected static $tabs = array() ;
/**
* @var string Active tab slug
*/
protected static $active_tab ;
/**
* @var string Tab slug
*/
protected $tab_slug ;
/**
* @var string Tab name
*/
protected $tab_name ;
/**
* Hook: admin_init
*
* @since 4.0.0
*/
public abstract function admin_init();
/**
* Sanitize and store form fields
*
* @since 4.0.0
*
* @param array $settings Settings to update
* @param array $input Form fields
*
* @return array $settings
*/
public abstract function sanitize( array $settings, array $input = null );
/**
* Contruct.
*
* @since 4.0.0
*
* @param string $slug Tab slug
* @param string $name Tab name
*/
public function __construct( $slug, $name )
{
$this->tab_slug = $slug;
$this->tab_name = $name;
self::$tabs[$slug] = $this;
}
/**
* Getter - slug
*
* @since 4.0.0
*
* @return string Tab slug
*/
public function getSlug()
{
return $this->tab_slug;
}
/**
* Getter - name
*
* @since 4.0.0
*
* @return string Tab name
*/
public function getName()
{
return $this->tab_name;
}
/**
* Render settings section
*
* @since 4.0.0
*/
public function render()
{
do_settings_sections( 'wp-fail2ban-' . $this->tab_slug );
}
/**
* Helper - tab
*
* @since 4.0.0
*
* @param string $slug Tab slug
*
* @return Tab Tab
*/
public static function getTab( $slug )
{
return self::$tabs[$slug];
}
/**
* Helper - current tab
*
* @since 4.0.0
*
* @param string $default Default slug
*
* @return Tab Tab
*/
public static function getActiveTab( $default = null )
{
if ( !empty(self::$active_tab) ) {
return self::$active_tab;
}
return self::$active_tab = ( array_key_exists( @$_GET['tab'], self::$tabs ) ? self::$tabs[$_GET['tab']] : self::$tabs[$default] );
}
/**
* Helper - tab name
*
* @since 4.0.0
*
* @param string $slug Tab slug
*
* @return string Tab name
*/
public static function getTabName( $slug )
{
return self::getTab( $slug )->getName();
}
/**
* Link to documentation
*
* @since 4.2.0
*
* @param string $define
* @param string $name
*
* @return string
*/
public static function doc_link( $define, $name )
{
static $wp_f2b_ver ;
if ( empty($wp_f2b_ver) ) {
$wp_f2b_ver = substr( WP_FAIL2BAN_VER, 0, strrpos( WP_FAIL2BAN_VER, '.' ) );
}
return sprintf(
'<a href="https://docs.wp-fail2ban.com/en/%s/defines/constants/%s.html" style="text-decoration: none;" target="_blank" title="Documentation"><span class="dashicons dashicons-external" style="vertical-align: text-bottom"></span></a> %s',
$wp_f2b_ver,
$define,
$name
);
}
/**
* Helper - drop-down list of facilities
*
* @since 4.0.0
*
* @param string $def Name of define for selected value
* @param bool $_enabled Enabled?
*/
protected function getLogFacilities( $def, $_enabled = false )
{
$enabled = false;
$facilities = [
LOG_AUTH => 'LOG_AUTH',
LOG_AUTHPRIV => 'LOG_AUTHPRIV',
LOG_CRON => 'LOG_CRON',
LOG_DAEMON => 'LOG_DAEMON',
LOG_KERN => 'LOG_KERN',
LOG_LOCAL0 => 'LOG_LOCAL0',
LOG_LOCAL1 => 'LOG_LOCAL1',
LOG_LOCAL2 => 'LOG_LOCAL2',
LOG_LOCAL3 => 'LOG_LOCAL3',
LOG_LOCAL4 => 'LOG_LOCAL4',
LOG_LOCAL5 => 'LOG_LOCAL5',
LOG_LOCAL6 => 'LOG_LOCAL6',
LOG_LOCAL7 => 'LOG_LOCAL7',
LOG_LPR => 'LOG_LPR',
LOG_MAIL => 'LOG_MAIL',
LOG_NEWS => 'LOG_NEWS',
LOG_SYSLOG => 'LOG_SYSLOG',
LOG_USER => 'LOG_USER',
LOG_UUCP => 'LOG_UUCP',
];
$default = constant( "DEFAULT_{$def}" );
$value = ( defined( $def ) ? constant( $def ) : $default );
$str = '<select disabled="disabled">';
foreach ( $facilities as $facility => $name ) {
$str .= sprintf(
'<option value="%s" %s>%s%s</option>',
$facility,
selected( $value, $facility, false ),
$name,
( $facility == $default ? __( ' (default)' ) : '' )
);
}
$str .= '</select>';
return $str;
}
/**
* Log helper - enable/disable+facility
*
* @since 4.2.0 Moved to Tab
* @since 4.0.0
*
* @param string $define_name Name of define to enable logging
* @param string $define_log Name of define for log facility
* @param string $description Description
* @param array $toggle Array of IDs to sync toggle state
*/
protected function log(
$define_name,
$define_log,
$description = '',
array $toggle = array()
)
{
$enabled = defined( $define_name ) && true === constant( $define_name );
$fmt = <<<___FMT___
<label><input type="checkbox" disabled="disabled" %s> Enable logging</label>,
<label>use facility: %s</label>
<p class="description">%s</p>
___FMT___;
$html = sprintf(
$fmt,
checked( $enabled, true, false ),
$this->getLogFacilities( $define_log ),
$description
);
echo apply_filters(
"wp_fail2ban_log_{$define_name}",
$html,
$define_name,
$define_log
) ;
}
}

193
feature/comments.php Normal file
View File

@ -0,0 +1,193 @@
<?php
/**
* Comment logging
*
* @package wp-fail2ban
* @since 4.0.0
*/
namespace org\lecklider\charles\wordpress\wp_fail2ban;
if ( !defined( 'ABSPATH' ) ) {
exit;
}
/**
* @since 4.0.5 Guard
*/
if ( !function_exists( __NAMESPACE__ . '\\notify_post_author' ) ) {
/**
* Log new comment
*
* @since 3.5.0
*
* @param bool $maybe_notify
* @param int $comment_ID
*
* @return bool
*
* @wp-f2b-extra Comment \d+
*/
function notify_post_author( $maybe_notify, $comment_ID )
{
openlog( 'WP_FAIL2BAN_COMMENT_LOG' );
syslog( LOG_INFO, "Comment {$comment_ID}" );
closelog();
// @codeCoverageIgnoreEnd
return $maybe_notify;
}
add_filter(
'notify_post_author',
__NAMESPACE__ . '\\notify_post_author',
10,
2
);
}
if ( defined( 'WP_FAIL2BAN_LOG_COMMENTS_EXTRA' ) ) {
/** WPF2B_EVENT_COMMENT_NOT_FOUND */
if ( WP_FAIL2BAN_LOG_COMMENTS_EXTRA & 0x20002 ) {
/**
* @since 4.0.5 Guard
*/
if ( !function_exists( __NAMESPACE__ . '\\comment_id_not_found' ) ) {
/**
* Log attempted comment on non-existent post
*
* @since 4.0.0
*
* @param int $comment_post_ID
*
* @wp-f2b-extra Comment post not found \d+
*/
function comment_id_not_found( $comment_post_ID )
{
openlog( 'WP_FAIL2BAN_COMMENT_EXTRA_LOG' );
syslog( LOG_NOTICE, "Comment post not found {$comment_post_ID}" );
closelog();
// @codeCoverageIgnoreEnd
}
add_action( 'comment_id_not_found', __NAMESPACE__ . '\\comment_id_not_found' );
}
}
/** LOG_ACTION_LOG_COMMENT_CLOSED */
if ( WP_FAIL2BAN_LOG_COMMENTS_EXTRA & 0x20004 ) {
/**
* @since 4.0.5 Guard
*/
if ( !function_exists( __NAMESPACE__ . '\\comment_closed' ) ) {
/**
* Log attempted comment on closed post
*
* @since 4.0.0
*
* @param int $comment_post_ID
*
* @wp-f2b-extra Comments closed on post \d+
*/
function comment_closed( $comment_post_ID )
{
openlog( 'WP_FAIL2BAN_COMMENT_EXTRA_LOG' );
syslog( LOG_NOTICE, "Comments closed on post {$comment_post_ID}" );
closelog();
// @codeCoverageIgnoreEnd
}
add_action( 'comment_closed', __NAMESPACE__ . '\\comment_closed' );
}
}
/** LOG_ACTION_LOG_COMMENT_TRASH */
if ( WP_FAIL2BAN_LOG_COMMENTS_EXTRA & 0x20008 ) {
/**
* @since 4.0.5 Guard
*/
if ( !function_exists( __NAMESPACE__ . '\\comment_on_trash' ) ) {
/**
* Log attempted comment on trashed post
*
* @since 4.0.2 Fix message
* @since 4.0.0
*
* @param int $comment_post_ID
*
* @wp-f2b-extra Comment attempt on trash post \d+
*/
function comment_on_trash( $comment_post_ID )
{
openlog( 'WP_FAIL2BAN_COMMENT_EXTRA_LOG' );
syslog( LOG_NOTICE, "Comment attempt on trash post {$comment_post_ID}" );
closelog();
// @codeCoverageIgnoreEnd
}
add_action( 'comment_on_trash', __NAMESPACE__ . '\\comment_on_trash' );
}
}
/** LOG_ACTION_LOG_COMMENT_DRAFT */
if ( WP_FAIL2BAN_LOG_COMMENTS_EXTRA & 0x20010 ) {
/**
* @since 4.0.5 Guard
*/
if ( !function_exists( __NAMESPACE__ . '\\comment_on_draft' ) ) {
/**
* Log attempted comment on draft post
*
* @since 4.0.2 Fix message
* @since 4.0.0
*
* @param int $comment_post_ID
*
* @wp-f2b-extra Comment attempt on draft post \d+
*/
function comment_on_draft( $comment_post_ID )
{
openlog( 'WP_FAIL2BAN_COMMENT_EXTRA_LOG' );
syslog( LOG_NOTICE, "Comment attempt on draft post {$comment_post_ID}" );
closelog();
// @codeCoverageIgnoreEnd
}
add_action( 'comment_on_draft', __NAMESPACE__ . '\\comment_on_draft' );
}
}
/** LOG_ACTION_LOG_COMMENT_PASSWORD */
if ( WP_FAIL2BAN_LOG_COMMENTS_EXTRA & 0x20020 ) {
/**
* @since 4.0.5 Guard
*/
if ( !function_exists( __NAMESPACE__ . '\\comment_on_password_protected' ) ) {
/**
* Log attempted comment on password-protected post
*
* @since 4.0.2 Fix message
* @since 4.0.0
*
* @param int $comment_post_ID
*
* @wp-f2b-extra Comment attempt on password-protected post \d+
*/
function comment_on_password_protected( $comment_post_ID )
{
openlog( 'WP_FAIL2BAN_COMMENT_EXTRA_LOG' );
syslog( LOG_NOTICE, "Comment attempt on password-protected post {$comment_post_ID}" );
closelog();
// @codeCoverageIgnoreEnd
}
add_action( 'comment_on_password_protected', __NAMESPACE__ . '\\comment_on_password_protected' );
}
}
}

144
feature/lib.php Normal file
View File

@ -0,0 +1,144 @@
<?php
/**
* Library functions
*
* @package wp-fail2ban
* @since 4.0.0
*/
namespace org\lecklider\charles\wordpress\wp_fail2ban;
if ( !defined( 'ABSPATH' ) ) {
exit;
}
/**
* Wrapper for \openlog
*
* @since 3.5.0 Refactored for unit testing
*
* @param string $log
*/
function openlog( $log = 'WP_FAIL2BAN_AUTH_LOG' )
{
$tag = ( defined( 'WP_FAIL2BAN_SYSLOG_SHORT_TAG' ) && true === WP_FAIL2BAN_SYSLOG_SHORT_TAG ? 'wp' : 'wordpress' );
$host = ( array_key_exists( 'WP_FAIL2BAN_HTTP_HOST', $_ENV ) ? $_ENV['WP_FAIL2BAN_HTTP_HOST'] : $_SERVER['HTTP_HOST'] );
/**
* Some varieties of syslogd have difficulty if $host is too long
* @since 3.5.0
*/
if ( defined( 'WP_FAIL2BAN_TRUNCATE_HOST' ) && 1 < intval( WP_FAIL2BAN_TRUNCATE_HOST ) ) {
$host = substr( $host, 0, intval( WP_FAIL2BAN_TRUNCATE_HOST ) );
}
if ( false === \openlog( "{$tag}({$host})", WP_FAIL2BAN_OPENLOG_OPTIONS, constant( $log ) ) ) {
error_log( 'WPf2b: Cannot open syslog', 0 );
// @codeCoverageIgnore
} elseif ( defined( 'WP_FAIL2BAN_TRACE' ) ) {
error_log( 'WPf2b: Opened syslog', 0 );
// @codeCoverageIgnore
}
}
/**
* Wrapper for \syslog
*
* @since 3.5.0
*
* @param int $level
* @param string $msg
* @param string|null $remote_addr
*/
function syslog( $level, $msg, $remote_addr = null )
{
$msg .= ' from ';
$msg .= ( is_null( $remote_addr ) ? remote_addr() : $remote_addr );
if ( false === \syslog( $level, $msg ) ) {
error_log( "WPf2b: Cannot write to syslog: '{$msg}'", 0 );
// @codeCoverageIgnore
} elseif ( defined( 'WP_FAIL2BAN_TRACE' ) ) {
error_log( "WPf2b: Wrote to syslog: '{$msg}'", 0 );
// @codeCoverageIgnore
}
\closelog();
if ( defined( 'PHPUNIT_COMPOSER_INSTALL' ) ) {
echo "{$level}|{$msg}" ;
}
}
/**
* Graceful immediate exit
*
* @since 4.0.5 Add JSON support
* @since 3.5.0 Refactored for unit testing
*
* @param bool $is_json
*/
function bail( $is_json = false )
{
if ( $is_json ) {
return new \WP_Error( 403, 'Forbidden' );
} else {
wp_die( 'Forbidden', 'Forbidden', array(
'response' => 403,
) );
}
}
/**
* Compute remote IP address
*
* @return string
*
* @todo Test me!
* @codeCoverageIgnore
*/
function remote_addr()
{
static $remote_addr = null ;
/**
* @since 4.0.0
*/
if ( is_null( $remote_addr ) ) {
if ( defined( 'WP_FAIL2BAN_PROXIES' ) ) {
if ( array_key_exists( 'HTTP_X_FORWARDED_FOR', $_SERVER ) ) {
$ip = ip2long( $_SERVER['REMOTE_ADDR'] );
/**
* PHP 7 lets you define an array
* @since 3.5.4
*/
$proxies = ( is_array( WP_FAIL2BAN_PROXIES ) ? WP_FAIL2BAN_PROXIES : explode( ',', WP_FAIL2BAN_PROXIES ) );
foreach ( $proxies as $proxy ) {
if ( '#' == $proxy[0] ) {
continue;
} elseif ( 2 == count( $cidr = explode( '/', $proxy ) ) ) {
$net = ip2long( $cidr[0] );
$mask = ~(pow( 2, 32 - $cidr[1] ) - 1);
} else {
$net = ip2long( $proxy );
$mask = -1;
}
if ( $net == ($ip & $mask) ) {
return ( false === ($len = strpos( $_SERVER['HTTP_X_FORWARDED_FOR'], ',' )) ? $_SERVER['HTTP_X_FORWARDED_FOR'] : substr( $_SERVER['HTTP_X_FORWARDED_FOR'], 0, $len ) );
}
}
}
}
/**
* For plugins and themes that anonymise requests
* @since 3.6.0
*/
$remote_addr = ( defined( 'WP_FAIL2BAN_REMOTE_ADDR' ) ? WP_FAIL2BAN_REMOTE_ADDR : $_SERVER['REMOTE_ADDR'] );
}
return $remote_addr;
}

37
feature/password.php Normal file
View File

@ -0,0 +1,37 @@
<?php
/**
* Password-related functionality
*
* @package wp-fail2ban
* @since 4.0.0
*/
namespace org\lecklider\charles\wordpress\wp_fail2ban;
if ( !defined( 'ABSPATH' ) ) {
exit;
}
/**
* @since 4.0.5
*/
if ( !function_exists( __NAMESPACE__ . '\\retrieve_password' ) ) {
/**
* Log password reset requests
*
* @since 3.5.0
*
* @param string $user_login
*
* @wp-f2b-extra Password reset requested for .*
*/
function retrieve_password( $user_login )
{
openlog( 'WP_FAIL2BAN_PASSWORD_REQUEST_LOG' );
syslog( LOG_NOTICE, "Password reset requested for {$user_login}" );
closelog();
// @codeCoverageIgnoreEnd
}
add_action( 'retrieve_password', __NAMESPACE__ . '\\retrieve_password' );
}

233
feature/plugins.php Normal file
View File

@ -0,0 +1,233 @@
<?php
/**
* Library functions
*
* @package wp-fail2ban
* @since 4.2.0
*/
namespace org\lecklider\charles\wordpress\wp_fail2ban;
if ( !defined( 'ABSPATH' ) ) {
exit;
}
/**
* Hook: plugins_loaded
*
* @since 4.2.0
*/
function plugins_loaded()
{
do_action( 'wp_fail2ban_register' );
}
add_action( 'plugins_loaded', __NAMESPACE__ . '\\plugins_loaded' );
/**
* Register plugin
*
* @since 4.2.0
*
* @param string $slug Plugin slug. This must be the actual plugin slug. Maximum length is 255 which should be more than enough.
* @param string $name Plugin display name. This should be an unescaped string - HTML is allowed.
*
* @return int|false ID
*/
function register_plugin( $slug, $name )
{
global $wp_fail2ban, $wpdb ;
if ( 255 < strlen( $slug ) ) {
throw new \LengthException( 'slug too long' );
}
if ( 255 < strlen( $name ) ) {
throw new \LengthException( 'name too long' );
}
if ( !is_array( @$wp_fail2ban['plugins'] ) ) {
$wp_fail2ban['plugins'] = [];
}
if ( array_key_exists( $slug, $wp_fail2ban['plugins'] ) ) {
return $wp_fail2ban['plugins'][$slug];
}
static $id = 0 ;
return $wp_fail2ban['plugins'][$slug] = [
'id' => ++$id,
'name' => $name,
'messages' => [],
];
}
add_action(
'wp_fail2ban_register_plugin',
__NAMESPACE__ . '\\register_plugin',
1,
2
);
/**
* Check if plugin is registered.
*
* @since 4.2.0
*
* @param string $plugin_slug
*
* @return bool
*/
function is_registered_plugin( $plugin_slug )
{
global $wp_fail2ban ;
return array_key_exists( $plugin_slug, $wp_fail2ban['plugins'] );
}
/**
* Register plugin message.
*
* @since 4.2.0
*
* @param string $plugin_slug
* @param array $msg [
* string slug: Message slug
* string fail: hard|soft|extra
* int facility: syslog facility
* int priority: syslog priority
* string event_class: Event Class
* int event_id: Event ID
* string message: Message with placeholders
* HOST: Remote IP
* USER: Current user name
* array vars: Array of [name => regex] pairs
*/
function register_message( $plugin_slug, array $msg )
{
global $wp_fail2ban ;
$event_classes = [
'auth' => WPF2B_EVENT_CLASS_AUTH,
'comment' => WPF2B_EVENT_CLASS_COMMENT,
'password' => WPF2B_EVENT_CLASS_PASSWORD,
'rest' => WPF2B_EVENT_CLASS_REST,
'spam' => WPF2B_EVENT_CLASS_SPAM,
'xmlrpc' => WPF2B_EVENT_CLASS_XMLRPC,
'other' => 0,
];
$args = [];
if ( !is_registered_plugin( $plugin_slug ) ) {
throw new \InvalidArgumentException( 'plugin not registered' );
}
if ( !array_key_exists( 'slug', $msg ) ) {
throw new \InvalidArgumentException( "Missing 'slug'" );
}
if ( !is_string( $msg['slug'] ) ) {
throw new \InvalidArgumentException( "'slug' must be string" );
}
if ( !array_key_exists( 'fail', $msg ) ) {
throw new \InvalidArgumentException( "Missing 'fail'" );
}
if ( !in_array( $msg['fail'], [ 'hard', 'soft', 'extra' ] ) ) {
throw new \UnexpectedValueException( "'fail' must be one of 'hard', 'soft', 'extra'" );
}
$args['fail'] = $msg['fail'];
if ( !array_key_exists( 'priority', $msg ) ) {
throw new \InvalidArgumentException( "Missing 'priority'" );
}
if ( !in_array( $msg['priority'], [
LOG_CRIT,
LOG_ERR,
LOG_WARNING,
LOG_NOTICE,
LOG_INFO,
LOG_DEBUG
] ) ) {
throw new \UnexpectedValueException( "Invalid 'priority'" );
}
$args['priority'] = $msg['priority'];
if ( !array_key_exists( 'event_class', $msg ) ) {
throw new \InvalidArgumentException( "Missing 'event_class'" );
}
if ( !array_key_exists( $event_class = strtolower( $msg['event_class'] ), $event_classes ) ) {
throw new \UnexpectedValueException( "Invalid 'event_class'" );
}
$args['class'] = $event_class;
$event_class = $event_classes[$event_class];
$log = sprintf( "WP_FAIL2BAN_%s_LOG", strtoupper( $event_class ) );
if ( !array_key_exists( 'event_id', $msg ) ) {
throw new \InvalidArgumentException( "Missing 'event_id'" );
}
if ( ($msg['event_id'] & 0xffff) !== $msg['event_id'] ) {
throw new \UnexpectedValueException( "Invalid 'event_id'" );
}
$args['event_id'] = WPF2B_EVENT_TYPE_PLUGIN | $event_class | $msg['event_id'];
if ( !array_key_exists( 'message', $msg ) ) {
throw new \InvalidArgumentException( "Missing 'message'" );
}
if ( !is_string( $msg['message'] ) ) {
throw new \UnexpectedValueException( "Invalid 'message'" );
}
$args['message'] = $msg['message'];
if ( !array_key_exists( 'vars', $msg ) ) {
throw new \InvalidArgumentException( "Missing 'vars'" );
}
if ( !is_array( $msg['vars'] ) ) {
throw new \UnexpectedValueException( "Invalid 'vars'" );
}
$args['vars'] = $msg['vars'];
$wp_fail2ban['plugins'][$plugin_slug]['messages'][$msg['slug']] = $args;
}
add_action(
'wp_fail2ban_register_message',
__NAMESPACE__ . '\\register_message',
1,
2
);
/**
* Check if message is registered.
*
* NB: Assumes plugin is registered.
*
* @since 4.2.0
*
* @param string $plugin_slug
* @param string $message_slug
*
* @return bool
*/
function is_registered_plugin_message( $plugin_slug, $message_slug )
{
global $wp_fail2ban ;
return array_key_exists( $message_slug, $wp_fail2ban['plugins'][$plugin_slug]['messages'] );
}
/**
* Log plugin message.
*
* @since 4.2.0
*
* @param string $plugin_slug Plugin slug for registered message
* @param string $message_slug Message slug for registered message
* @param array $vars Substitution vars
*/
function log_message( $plugin_slug, $message_slug = null, array $vars = array() )
{
global $wp_fail2ban ;
if ( !is_registered_plugin( $plugin_slug ) ) {
throw new \InvalidArgumentException( 'plugin not registered' );
}
if ( !is_registered_plugin_message( $plugin_slug, $message_slug ) ) {
throw new \InvalidArgumentException( 'message not registered' );
}
$args = $wp_fail2ban['plugins'][$plugin_slug]['messages'][$message_slug];
$msg = $args['message'];
foreach ( $args['vars'] as $name => $regex ) {
if ( array_key_exists( $name, $vars ) ) {
$msg = str_replace( "___{$name}___", $vars[$name], $msg );
}
}
openlog( sprintf( 'WP_FAIL2BAN_PLUGIN_%s_LOG', strtoupper( $args['class'] ) ) );
syslog( $args['priority'], "({$plugin_slug}) {$msg}" );
closelog();
// @codeCoverageIgnoreEnd
}
add_action(
'wp_fail2ban_log_message',
__NAMESPACE__ . '\\log_message',
1,
3
);

60
feature/spam.php Normal file
View File

@ -0,0 +1,60 @@
<?php
/**
* Spam comments
*
* @package wp-fail2ban
* @since 4.0.0
*/
namespace org\lecklider\charles\wordpress\wp_fail2ban;
if ( !defined( 'ABSPATH' ) ) {
exit;
}
/**
* @since 4.0.5
*/
if ( !function_exists( __NAMESPACE__ . '\\log_spam_comment' ) ) {
/**
* Catch comments marked as spam
*
* @since 3.5.0
*
* @param int $comment_id
* @param string $comment_status
*
* @wp-f2b-hard Spam comment \d+
*/
function log_spam_comment( $comment_id, $comment_status )
{
if ( 'spam' === $comment_status ) {
if ( is_null( $comment = get_comment( $comment_id, ARRAY_A ) ) ) {
/**
* @todo: decide what to do about this
*/
} else {
$remote_addr = ( empty($comment['comment_author_IP']) ? 'unknown' : $comment['comment_author_IP'] );
openlog( 'WP_FAIL2BAN_SPAM_LOG' );
syslog( LOG_NOTICE, "Spam comment {$comment_id}", $remote_addr );
closelog();
// @codeCoverageIgnoreEnd
}
}
}
add_action(
'comment_post',
__NAMESPACE__ . '\\log_spam_comment',
10,
2
);
add_action(
'wp_set_comment_status',
__NAMESPACE__ . '\\log_spam_comment',
10,
2
);
}

104
feature/user-enum.php Normal file
View File

@ -0,0 +1,104 @@
<?php
/**
* User enumeration
*
* @package wp-fail2ban
* @since 4.0.0
*/
namespace org\lecklider\charles\wordpress\wp_fail2ban;
if ( !defined( 'ABSPATH' ) ) {
exit;
}
/**
* @since 4.0.5 Guard
*/
if ( !function_exists( __NAMESPACE__ . '\\_log_bail_user_enum' ) ) {
/**
* Common enumeration handling
*
* @since 4.1.0 Add JSON support
* @since 4.0.0
*
* @param bool $is_json
*
* @return \WP_Error
*
* @wp-f2b-hard Blocked user enumeration attempt
*/
function _log_bail_user_enum( $is_json = false )
{
openlog();
syslog( LOG_NOTICE, 'Blocked user enumeration attempt' );
closelog();
// @codeCoverageIgnoreEnd
return bail( $is_json );
}
}
/**
* @since 4.0.5 Guard
*/
if ( !function_exists( __NAMESPACE__ . '\\parse_request' ) ) {
/**
* Catch traditional user enum
*
* @see \WP::parse_request()
*
* @since 3.5.0 Refactored for unit testing
* @since 2.1.0
*
* @param \WP $query
*
* @return \WP
*/
function parse_request( $query )
{
if ( !current_user_can( 'list_users' ) && intval( @$query->query_vars['author'] ) ) {
_log_bail_user_enum();
}
return $query;
}
add_filter(
'parse_request',
__NAMESPACE__ . '\\parse_request',
1,
2
);
}
/**
* @since 4.0.5 Guard
*/
if ( !function_exists( __NAMESPACE__ . '\\rest_user_query' ) ) {
/**
* Catch RESTful user list
*
* @see \WP_REST_Users_Controller::get_items()
*
* @since 4.0.0
*
* @param array $prepared_args
* @param \WP_REST_Request $request
*
* @return array|\WP_Error
*/
function rest_user_query( $prepared_args, $request )
{
if ( !current_user_can( 'list_users' ) ) {
return _log_bail_user_enum( true );
}
return $prepared_args;
}
add_filter(
'rest_user_query',
__NAMESPACE__ . '\\rest_user_query',
10,
2
);
}

61
feature/user.php Normal file
View File

@ -0,0 +1,61 @@
<?php
/**
* Blocked user functionality
*
* @package wp-fail2ban
* @since 4.0.0
*/
namespace org\lecklider\charles\wordpress\wp_fail2ban;
if ( !defined( 'ABSPATH' ) ) {
exit;
}
/**
* @since 4.0.5 Guard
*/
if ( !function_exists( __NAMESPACE__ . '\\authenticate' ) ) {
/**
* Catched blocked users
*
* @since 3.5.0 Refactored for unit testing
* @since 2.0.0
*
* @param mixed|null $user
* @param string $username
* @param string $password
*
* @return mixed|null
*
* @wp-f2b-hard Blocked authentication attempt for .*
*/
function authenticate( $user, $username, $password )
{
if ( !empty($username) ) {
/**
* @since 3.5.0 Arrays allowed in PHP 7
*/
$matched = ( is_array( WP_FAIL2BAN_BLOCKED_USERS ) ? in_array( $username, WP_FAIL2BAN_BLOCKED_USERS ) : preg_match( '/' . WP_FAIL2BAN_BLOCKED_USERS . '/i', $username ) );
if ( $matched ) {
openlog();
syslog( LOG_NOTICE, "Blocked authentication attempt for {$username}" );
closelog();
// @codeCoverageIgnoreEnd
bail();
}
}
return $user;
}
add_filter(
'authenticate',
__NAMESPACE__ . '\\authenticate',
1,
3
);
}

108
feature/xmlrpc.php Normal file
View File

@ -0,0 +1,108 @@
<?php
/**
* XML-RPC functionality
*
* @package wp-fail2ban
* @since 4.0.0
*/
namespace org\lecklider\charles\wordpress\wp_fail2ban;
if ( !defined( 'ABSPATH' ) ) {
exit;
}
/**
* @since 4.0.5 Guard
*/
if ( !function_exists( __NAMESPACE__ . '\\xmlrpc_login_error' ) ) {
/**
* Catch multiple XML-RPC authentication failures
*
* @see \wp_xmlrpc_server::login()
*
* @since 4.0.0 Return $error
* @since 3.5.0 Refactored for unit testing
* @since 3.0.0
*
* @param \IXR_Error $error
* @param \WP_Error $user
*
* @return \IXR_Error
*
* @wp-f2b-hard XML-RPC multicall authentication failure
*/
function xmlrpc_login_error( $error, $user )
{
static $attempts = 0 ;
if ( ++$attempts > 1 ) {
openlog();
syslog( LOG_NOTICE, 'XML-RPC multicall authentication failure' );
closelog();
// @codeCoverageIgnoreEnd
bail();
} else {
return $error;
}
}
add_action(
'xmlrpc_login_error',
__NAMESPACE__ . '\\xmlrpc_login_error',
10,
2
);
}
/**
* @since 4.0.5 Guard
*/
if ( !function_exists( __NAMESPACE__ . '\\xmlrpc_pingback_error' ) ) {
/**
* Catch failed pingbacks
*
* @see \wp_xmlrpc_server::pingback_error()
*
* @since 4.0.0 Return $ixr_error
* @since 3.5.0 Refactored for unit testing
* @since 3.0.0
*
* @param \IXR_Error $ixr_error
*
* @return \IXR_Error
*
* @wp-f2b-hard Pingback error .* generated
*/
function xmlrpc_pingback_error( $ixr_error )
{
if ( 48 !== $ixr_error->code ) {
openlog();
syslog( LOG_NOTICE, 'Pingback error ' . $ixr_error->code . ' generated' );
closelog();
// @codeCoverageIgnoreEnd
}
return $ixr_error;
}
add_filter( 'xmlrpc_pingback_error', __NAMESPACE__ . '\\xmlrpc_pingback_error', 5 );
}
/**
* @since 4.0.0 Refactored
* @since 2.2.0
*/
if ( defined( 'WP_FAIL2BAN_LOG_PINGBACKS' ) && true === WP_FAIL2BAN_LOG_PINGBACKS ) {
require_once 'xmlrpc/pingback.php';
}
/**
* @since 4.0.0 Refactored
* @since 3.6.0
*/
if ( defined( 'WP_FAIL2BAN_XMLRPC_LOG' ) && '' < WP_FAIL2BAN_XMLRPC_LOG ) {
require_once 'xmlrpc/log.php';
}

35
feature/xmlrpc/log.php Normal file
View File

@ -0,0 +1,35 @@
<?php
/**
* XML-RPC Request logging
*
* @package wp-fail2ban
* @since 4.0.0
*/
namespace org\lecklider\charles\wordpress\wp_fail2ban;
if (!defined('ABSPATH')) {
exit;
}
/**
* Log XML-RPC requests
*
* It seems attackers are doing weird things with XML-RPC. This makes it easy to
* log them for analysis and future blocking.
*
* @since 4.0.0 Fix: Removed HTTP_RAW_POST_DATA
* https://wordpress.org/support/?p=10971843
* @since 3.6.0
*
* @codeCoverageIgnore
*/
if (false === ($fp = fopen(WP_FAIL2BAN_XMLRPC_LOG, 'a+'))) {
// TODO: decided whether to log this
} else {
$raw_data = (version_compare(PHP_VERSION, '7.0.0') >= 0)
? file_get_contents('php://input')
: $HTTP_RAW_POST_DATA;
fprintf($fp, "# ---\n# Date: %s\n# IP: %s\n\n%s\n", date(DATE_ATOM), remote_addr(), $raw_data);
fclose($fp);
}

View File

@ -0,0 +1,40 @@
<?php
/**
* pingback logging
*
* @package wp-fail2ban
* @since 4.0.0
*/
namespace org\lecklider\charles\wordpress\wp_fail2ban;
if ( !defined( 'ABSPATH' ) ) {
exit;
}
/**
* @since 4.0.5 Guard
*/
if ( !function_exists( __NAMESPACE__ . '\\xmlrpc_call' ) ) {
/**
* Log pingbacks
*
* @since 3.5.0 Refactored for unit testing
* @since 2.2.0
*
* @param string $call
*/
function xmlrpc_call( $call )
{
if ( 'pingback.ping' == $call ) {
openlog( 'WP_FAIL2BAN_PINGBACK_LOG' );
syslog( LOG_INFO, 'Pingback requested' );
closelog();
// @codeCoverageIgnoreEnd
}
}
add_action( 'xmlrpc_call', __NAMESPACE__ . '\\xmlrpc_call' );
}

View File

@ -0,0 +1,27 @@
# Fail2Ban filter for WordPress extra failures
# Auto-generated: 2019-07-15T18:00:14+00:00
#
[INCLUDES]
before = common.conf
[Definition]
_daemon = (?:wordpress|wp)
failregex = ^%(__prefix_line)sComment \d+ from <HOST>$
^%(__prefix_line)sComment post not found \d+ from <HOST>$
^%(__prefix_line)sComments closed on post \d+ from <HOST>$
^%(__prefix_line)sComment attempt on trash post \d+ from <HOST>$
^%(__prefix_line)sComment attempt on draft post \d+ from <HOST>$
^%(__prefix_line)sComment attempt on password-protected post \d+ from <HOST>$
^%(__prefix_line)sPassword reset requested for .* from <HOST>$
ignoreregex =
# DEV Notes:
# Requires the 'WP fail2ban' plugin:
# https://wp-fail2ban.com/
#
# Author: Charles Lecklider

View File

@ -0,0 +1,28 @@
# Fail2Ban filter for WordPress hard failures
# Auto-generated: 2019-07-15T18:00:14+00:00
#
[INCLUDES]
before = common.conf
[Definition]
_daemon = (?:wordpress|wp)
failregex = ^%(__prefix_line)sAuthentication attempt for unknown user .* from <HOST>$
^%(__prefix_line)sREST authentication attempt for unknown user .* from <HOST>$
^%(__prefix_line)sXML-RPC authentication attempt for unknown user .* from <HOST>$
^%(__prefix_line)sSpam comment \d+ from <HOST>$
^%(__prefix_line)sBlocked user enumeration attempt from <HOST>$
^%(__prefix_line)sBlocked authentication attempt for .* from <HOST>$
^%(__prefix_line)sXML-RPC multicall authentication failure from <HOST>$
^%(__prefix_line)sPingback error .* generated from <HOST>$
ignoreregex =
# DEV Notes:
# Requires the 'WP fail2ban' plugin:
# https://wp-fail2ban.com/
#
# Author: Charles Lecklider

View File

@ -0,0 +1,23 @@
# Fail2Ban filter for WordPress soft failures
# Auto-generated: 2019-07-15T18:00:14+00:00
#
[INCLUDES]
before = common.conf
[Definition]
_daemon = (?:wordpress|wp)
failregex = ^%(__prefix_line)sAuthentication failure for .* from <HOST>$
^%(__prefix_line)sREST authentication failure for .* from <HOST>$
^%(__prefix_line)sXML-RPC authentication failure for .* from <HOST>$
ignoreregex =
# DEV Notes:
# Requires the 'WP fail2ban' plugin:
# https://wp-fail2ban.com/
#
# Author: Charles Lecklider

144
lib/constants.php Normal file
View File

@ -0,0 +1,144 @@
<?php
/**
* Constants
*
* @package wp-fail2ban
* @since 4.2.0
*/
namespace org\lecklider\charles\wordpress\wp_fail2ban;
if (!defined('ABSPATH')) {
exit;
}
// phpcs:disable Generic.Functions.FunctionCallArgumentSpacing
/**
* Defaults
*
* @since 4.0.0
*/
define('DEFAULT_WP_FAIL2BAN_OPENLOG_OPTIONS', LOG_PID|LOG_NDELAY);
define('DEFAULT_WP_FAIL2BAN_AUTH_LOG', LOG_AUTH);
define('DEFAULT_WP_FAIL2BAN_COMMENT_LOG', LOG_USER);
define('DEFAULT_WP_FAIL2BAN_PINGBACK_LOG', LOG_USER);
define('DEFAULT_WP_FAIL2BAN_PASSWORD_REQUEST_LOG', LOG_USER);
define('DEFAULT_WP_FAIL2BAN_SPAM_LOG', LOG_AUTH);
/**
* @since 4.0.5
*/
define('DEFAULT_WP_FAIL2BAN_COMMENT_EXTRA_LOG', LOG_AUTH);
define('DEFAULT_WP_FAIL2BAN_PINGBACK_ERROR_LOG', LOG_AUTH);
/**
* @since 4.2.0
*/
define('DEFAULT_WP_FAIL2BAN_PLUGIN_AUTH_LOG', LOG_AUTH);
define('DEFAULT_WP_FAIL2BAN_PLUGIN_COMMENT_LOG', LOG_USER);
define('DEFAULT_WP_FAIL2BAN_PLUGIN_OTHER_LOG', LOG_USER);
define('DEFAULT_WP_FAIL2BAN_PLUGIN_PASSWORD_LOG', LOG_USER);
define('DEFAULT_WP_FAIL2BAN_PLUGIN_REST_LOG', LOG_USER);
define('DEFAULT_WP_FAIL2BAN_PLUGIN_SPAM_LOG', LOG_AUTH);
define('DEFAULT_WP_FAIL2BAN_PLUGIN_XMLRPC_LOG', LOG_USER);
/*
31 | Test
30 | Plugin
29 |
28 |
27 |
26 |
25 |
24 |
---
23 | Event Class
22 | ..
21 | ..
20 | ..
19 | ..
18 | ..
17 | ..
16 | ..
---
15 | ID
14 | ..
13 | ..
12 | ..
11 | ..
10 | ..
09 | ..
08 | ..
---
07 | ..
06 | ..
05 | ..
04 | ..
03 | ..
02 | ..
01 | ..
00 | ..
*/
define('WPF2B_EVENT_CLASS_AUTH', 0x00010000);
define('WPF2B_EVENT_CLASS_COMMENT', 0x00020000);
define('WPF2B_EVENT_CLASS_XMLRPC', 0x00040000);
define('WPF2B_EVENT_CLASS_PASSWORD', 0x00080000);
define('WPF2B_EVENT_CLASS_REST', 0x00100000); /** @since 4.1.0 */
define('WPF2B_EVENT_CLASS_SPAM', 0x00200000); /** @since 4.2.0 */
define('WPF2B_EVENT_TYPE_PLUGIN', 0x40000000); /** @since 4.2.0 */
define('WPF2B_EVENT_TYPE_TEST', 0x80000000); /** @since 4.2.0 */
/**
*
*/
define('WPF2B_EVENT_ACTIVATED', 0xffffffff);
/**
* Auth
*/
define('WPF2B_EVENT_AUTH_OK', WPF2B_EVENT_CLASS_AUTH | 0x0001);
define('WPF2B_EVENT_AUTH_FAIL', WPF2B_EVENT_CLASS_AUTH | 0x0002);
define('WPF2B_EVENT_AUTH_BLOCK_USER', WPF2B_EVENT_CLASS_AUTH | 0x0004);
define('WPF2B_EVENT_AUTH_BLOCK_USER_ENUM', WPF2B_EVENT_CLASS_AUTH | 0x0008);
/**
* Comment
*/
define('WPF2B_EVENT_COMMENT', WPF2B_EVENT_CLASS_COMMENT | 0x0001); // 0x00020001
define('WPF2B_EVENT_COMMENT_SPAM', WPF2B_EVENT_CLASS_COMMENT | WPF2B_EVENT_CLASS_SPAM | 0x0001); // 0x00220001
// comment extra
define('WPF2B_EVENT_COMMENT_NOT_FOUND', WPF2B_EVENT_CLASS_COMMENT | 0x0002); // 0x00020002
define('WPF2B_EVENT_COMMENT_CLOSED', WPF2B_EVENT_CLASS_COMMENT | 0x0004); // 0x00020004
define('WPF2B_EVENT_COMMENT_TRASH', WPF2B_EVENT_CLASS_COMMENT | 0x0008); // 0x00020008
define('WPF2B_EVENT_COMMENT_DRAFT', WPF2B_EVENT_CLASS_COMMENT | 0x0010); // 0x00020010
define('WPF2B_EVENT_COMMENT_PASSWORD', WPF2B_EVENT_CLASS_COMMENT | WPF2B_EVENT_CLASS_PASSWORD | 0x0020); // 0x00020020
/**
* XML-RPC
*/
define('WPF2B_EVENT_XMLRPC_PINGBACK', WPF2B_EVENT_CLASS_XMLRPC | 0x0001);
define('WPF2B_EVENT_XMLRPC_PINGBACK_ERROR', WPF2B_EVENT_CLASS_XMLRPC | 0x0002);
define('WPF2B_EVENT_XMLRPC_MULTI_AUTH_FAIL', WPF2B_EVENT_CLASS_XMLRPC | WPF2B_EVENT_CLASS_AUTH | 0x0004);
define('WPF2B_EVENT_XMLRPC_AUTH_OK', WPF2B_EVENT_CLASS_XMLRPC | WPF2B_EVENT_CLASS_AUTH | 0x0008);
define('WPF2B_EVENT_XMLRPC_AUTH_FAIL', WPF2B_EVENT_CLASS_XMLRPC | WPF2B_EVENT_CLASS_AUTH | 0x0010);
/**
* Password
*/
define('WPF2B_ACTION_PASSWORD_REQUEST', WPF2B_EVENT_CLASS_PASSWORD | 0x0001);
/**
* REST
* @since 4.1.0
*/
define('WPF2B_EVENT_REST_AUTH_OK', WPF2B_EVENT_CLASS_REST | WPF2B_EVENT_CLASS_AUTH | 0x0001);
define('WPF2B_EVENT_REST_AUTH_FAIL', WPF2B_EVENT_CLASS_REST | WPF2B_EVENT_CLASS_AUTH | 0x0002);
/**
*
*/
define('WPF2B_EVENT_DEACTIVATED', 0x00000000);
// phpcs:enable

79
lib/defaults.php Normal file
View File

@ -0,0 +1,79 @@
<?php
/**
* Default Constants
*
* @package wp-fail2ban
* @since 4.2.0
*/
namespace org\lecklider\charles\wordpress\wp_fail2ban;
if (!defined('ABSPATH')) {
exit;
}
/**
* Allow custom openlog options.
* e.g. you may not want the PID if logging remotely.
*
* @since 3.6.0 Add LOG_NDELAY
* @since 3.5.0
*/
if (!defined('WP_FAIL2BAN_OPENLOG_OPTIONS')) {
define('WP_FAIL2BAN_OPENLOG_OPTIONS', DEFAULT_WP_FAIL2BAN_OPENLOG_OPTIONS);
}
/**
* Make sure all custom logs are defined.
* @since 3.5.0
*/
if (!defined('WP_FAIL2BAN_AUTH_LOG')) {
define('WP_FAIL2BAN_AUTH_LOG', DEFAULT_WP_FAIL2BAN_AUTH_LOG);
}
if (!defined('WP_FAIL2BAN_COMMENT_LOG')) {
define('WP_FAIL2BAN_COMMENT_LOG', DEFAULT_WP_FAIL2BAN_COMMENT_LOG);
}
if (!defined('WP_FAIL2BAN_PINGBACK_LOG')) {
define('WP_FAIL2BAN_PINGBACK_LOG', DEFAULT_WP_FAIL2BAN_PINGBACK_LOG);
}
/**
* @since 4.0.0
*/
if (!defined('WP_FAIL2BAN_PASSWORD_REQUEST_LOG')) {
define('WP_FAIL2BAN_PASSWORD_REQUEST_LOG', DEFAULT_WP_FAIL2BAN_PASSWORD_REQUEST_LOG);
}
if (!defined('WP_FAIL2BAN_SPAM_LOG')) {
define('WP_FAIL2BAN_SPAM_LOG', DEFAULT_WP_FAIL2BAN_SPAM_LOG);
}
/**
* @since 4.0.5
*/
if (!defined('WP_FAIL2BAN_COMMENT_EXTRA_LOG')) {
define('WP_FAIL2BAN_COMMENT_EXTRA_LOG', DEFAULT_WP_FAIL2BAN_COMMENT_EXTRA_LOG);
}
if (!defined('WP_FAIL2BAN_PINGBACK_ERROR_LOG')) {
define('WP_FAIL2BAN_PINGBACK_ERROR_LOG', DEFAULT_WP_FAIL2BAN_PINGBACK_ERROR_LOG);
}
/**
* @since 4.2.0
*/
if (!defined('WP_FAIL2BAN_PLUGIN_AUTH_LOG')) {
define('WP_FAIL2BAN_PLUGIN_AUTH_LOG', DEFAULT_WP_FAIL2BAN_PLUGIN_AUTH_LOG);
}
if (!defined('WP_FAIL2BAN_PLUGIN_COMMENT_LOG')) {
define('WP_FAIL2BAN_PLUGIN_COMMENT_LOG', DEFAULT_WP_FAIL2BAN_PLUGIN_COMMENT_LOG);
}
if (!defined('WP_FAIL2BAN_PLUGIN_OTHER_LOG')) {
define('WP_FAIL2BAN_PLUGIN_OTHER_LOG', DEFAULT_WP_FAIL2BAN_PLUGIN_OTHER_LOG);
}
if (!defined('WP_FAIL2BAN_PLUGIN_PASSWORD_LOG')) {
define('WP_FAIL2BAN_PLUGIN_PASSWORD_LOG', DEFAULT_WP_FAIL2BAN_PLUGIN_PASSWORD_LOG);
}
if (!defined('WP_FAIL2BAN_PLUGIN_REST_LOG')) {
define('WP_FAIL2BAN_PLUGIN_REST_LOG', DEFAULT_WP_FAIL2BAN_PLUGIN_REST_LOG);
}
if (!defined('WP_FAIL2BAN_PLUGIN_SPAM_LOG')) {
define('WP_FAIL2BAN_PLUGIN_SPAM_LOG', DEFAULT_WP_FAIL2BAN_PLUGIN_SPAM_LOG);
}
if (!defined('WP_FAIL2BAN_PLUGIN_XMLRPC_LOG')) {
define('WP_FAIL2BAN_PLUGIN_XMLRPC_LOG', DEFAULT_WP_FAIL2BAN_PLUGIN_XMLRPC_LOG);
}

327
lib/loader.php Normal file
View File

@ -0,0 +1,327 @@
<?php
/**
* Loader
*
* @package wp-fail2ban
* @since 4.2.0
*/
namespace {
if ( !defined( 'ABSPATH' ) ) {
exit;
}
if ( defined( 'PHPUNIT_COMPOSER_INSTALL' ) ) {
return;
}
if ( !function_exists( 'boolval' ) ) {
/**
* PHP 5.3 helper
*
* @since 4.2.5
*
* @param mixed $val
*
* @return bool
*/
function boolval( $val )
{
return (bool) $val;
}
}
}
namespace org\lecklider\charles\wordpress\wp_fail2ban {
/**
* Helper
*
* @since 4.0.0
*
* @param string $define
* @param callable $cast
* @param bool $unset
* @param array $field
*/
function _load(
$define,
$cast,
$unset,
array $field
)
{
global $wp_fail2ban ;
$wp_fail2ban['config'][$define] = array(
'validate' => $cast,
'unset' => $unset,
'field' => $field,
'ndef' => !defined( $define ),
);
if ( !defined( $define ) ) {
if ( defined( "DEFAULT_{$define}" ) ) {
// we've got a default
define( $define, $cast( constant( "DEFAULT_{$define}" ) ) );
} else {
// bah
define( $define, $cast( false ) );
}
}
}
/**
* Validate IP list
*
* @since 4.0.0
*
* @param array|string $value
*
* @return string
*/
function validate_ips( $value )
{
return $value;
}
// phpcs:disable Generic.Functions.FunctionCallArgumentSpacing
_load(
'WP_FAIL2BAN_AUTH_LOG',
'intval',
true,
array( 'logging', 'authentication', 'facility' )
);
_load(
'WP_FAIL2BAN_LOG_COMMENTS',
'boolval',
true,
array( 'logging', 'comments', 'enabled' )
);
_load(
'WP_FAIL2BAN_LOG_COMMENTS_EXTRA',
'intval',
true,
array( 'logging', 'comments', 'extra' )
);
_load(
'WP_FAIL2BAN_COMMENT_LOG',
'intval',
false,
array( 'logging', 'comments', 'facility' )
);
_load(
'WP_FAIL2BAN_COMMENT_EXTRA_LOG',
'intval',
false,
array( 'logging', 'comments-extra', 'facility' )
);
_load(
'WP_FAIL2BAN_LOG_PASSWORD_REQUEST',
'boolval',
true,
array( 'logging', 'password-request', 'enabled' )
);
_load(
'WP_FAIL2BAN_PASSWORD_REQUEST_LOG',
'intval',
false,
array( 'logging', 'password-request', 'facility' )
);
_load(
'WP_FAIL2BAN_LOG_PINGBACKS',
'boolval',
true,
array( 'logging', 'pingback', 'enabled' )
);
_load(
'WP_FAIL2BAN_PINGBACK_LOG',
'intval',
false,
array( 'logging', 'pingback', 'facility' )
);
_load(
'WP_FAIL2BAN_LOG_SPAM',
'boolval',
true,
array( 'logging', 'spam', 'enabled' )
);
_load(
'WP_FAIL2BAN_SPAM_LOG',
'intval',
false,
array( 'logging', 'spam', 'facility' )
);
_load(
'WP_FAIL2BAN_OPENLOG_OPTIONS',
'intval',
true,
array( 'syslog', 'connection' )
);
_load(
'WP_FAIL2BAN_SYSLOG_SHORT_TAG',
'boolval',
true,
array( 'syslog', 'workaround', 'short_tag' )
);
_load(
'WP_FAIL2BAN_HTTP_HOST',
'boolval',
true,
array( 'syslog', 'workaround', 'http_host' )
);
_load(
'WP_FAIL2BAN_TRUNCATE_HOST',
'boolval',
true,
array( 'syslog', 'workaround', 'truncate_host' )
);
_load(
'WP_FAIL2BAN_BLOCK_USER_ENUMERATION',
'boolval',
true,
array( 'block', 'user_enumeration' )
);
_load(
'WP_FAIL2BAN_BLOCKED_USERS',
'strval',
true,
array( 'block', 'users' )
);
_load(
'WP_FAIL2BAN_PROXIES',
__NAMESPACE__ . '\\validate_ips',
true,
array( 'remote-ip', 'proxies' )
);
_load(
'WP_FAIL2BAN_PLUGIN_LOG_AUTH',
'boolval',
true,
array(
'logging',
'plugins',
'auth',
'enabled'
)
);
_load(
'WP_FAIL2BAN_PLUGIN_LOG_COMMENT',
'boolval',
true,
array(
'logging',
'plugins',
'comment',
'enabled'
)
);
_load(
'WP_FAIL2BAN_PLUGIN_LOG_PASSWORD',
'boolval',
true,
array(
'logging',
'plugins',
'password',
'enabled'
)
);
_load(
'WP_FAIL2BAN_PLUGIN_LOG_REST',
'boolval',
true,
array(
'logging',
'plugins',
'rest',
'enabled'
)
);
_load(
'WP_FAIL2BAN_PLUGIN_LOG_SPAM',
'boolval',
true,
array(
'logging',
'plugins',
'spam',
'enabled'
)
);
_load(
'WP_FAIL2BAN_PLUGIN_LOG_XMLRPC',
'boolval',
true,
array(
'logging',
'plugins',
'xmlrpc',
'enabled'
)
);
_load(
'WP_FAIL2BAN_PLUGIN_AUTH_LOG',
'intval',
false,
array(
'logging',
'plugins',
'auth',
'facility'
)
);
_load(
'WP_FAIL2BAN_PLUGIN_COMMENT_LOG',
'intval',
false,
array(
'logging',
'plugins',
'comment',
'facility'
)
);
_load(
'WP_FAIL2BAN_PLUGIN_PASSWORD_LOG',
'intval',
false,
array(
'logging',
'plugins',
'password',
'facility'
)
);
_load(
'WP_FAIL2BAN_PLUGIN_REST_LOG',
'intval',
false,
array(
'logging',
'plugins',
'rest',
'facility'
)
);
_load(
'WP_FAIL2BAN_PLUGIN_SPAM_LOG',
'intval',
false,
array(
'logging',
'plugins',
'spam',
'facility'
)
);
_load(
'WP_FAIL2BAN_PLUGIN_XMLRPC_LOG',
'intval',
false,
array(
'logging',
'plugins',
'xmlrpc',
'facility'
)
);
// phpcs:enable
}

280
readme.txt Normal file
View File

@ -0,0 +1,280 @@
=== WP fail2ban ===
Contributors: invisnet
Donate link: https://paypal.me/invisnet/
Author URI: https://charles.lecklider.org/
Plugin URI: https://wp-fail2ban.com/
Tags: fail2ban, login, security, syslog
Requires at least: 4.2
Tested up to: 5.2
Stable tag: 4.2.5
Requires PHP: 5.3
License: GPLv2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
Write a myriad of WordPress events to syslog for integration with fail2ban.
== Description ==
[fail2ban](http://www.fail2ban.org/) is one of the simplest and most effective security measures you can implement to prevent brute-force attacks.
*WP fail2ban* logs all login attempts - including via XML-RPC, whether successful or not, to syslog using LOG_AUTH. For example:
Oct 17 20:59:54 foobar wordpress(www.example.com)[1234]: Authentication failure for admin from 192.168.0.1
Oct 17 21:00:00 foobar wordpress(www.example.com)[2345]: Accepted password for admin from 192.168.0.1
*WPf2b* comes with three `fail2ban` filters: `wordpress-hard.conf`, `wordpress-soft.conf`, and `wordpress-extra.conf`. These are designed to allow a split between immediate banning (hard) and the traditional more graceful approach (soft), with extra rules for custom configurations.
= Features =
* **NEW - Support for 3rd-party Plugins**
Version 4.2 introduces a simple API for authors to integrate their plugins with *WPf2b*, with 2 *experimental* add-ons:
* [Contact Form 7](https://wordpress.org/plugins/wp-fail2ban-addon-contact-form-7/)
* [Gravity Forms](https://wordpress.org/plugins/wp-fail2ban-addon-gravity-forms/)
**NB:** Requires PHP >= 5.6
* **CloudFlare and Proxy Servers**
*WPf2b* can be configured to work with CloudFlare and other proxy servers. For an overview see [`WP_FAIL2BAN_PROXIES`](https://docs.wp-fail2ban.com/en/4.2/defines.html#wp-fail2ban-proxies).
* **Comments**
*WPf2b* can log comments (see [`WP_FAIL2BAN_LOG_COMMENTS`](https://docs.wp-fail2ban.com/en/4.2/defines.html#wp-fail2ban-log-comments)) and attempted comments (see [`WP_FAIL2BAN_LOG_COMMENTS_EXTRA`](https://docs.wp-fail2ban.com/en/4.2/defines.html#wp-fail2ban-log-comments-extra)).
* **Pingbacks**
*WPf2b* logs failed pingbacks, and can log all pingbacks. For an overview see [`WP_FAIL2BAN_LOG_PINGBACKS`](https://docs.wp-fail2ban.com/en/4.2/defines.html#wp-fail2ban-log-pingbacks).
* **Spam**
*WPf2b* can log comments marked as spam. See [`WP_FAIL2BAN_LOG_SPAM`](https://docs.wp-fail2ban.com/en/4.2/defines.html#wp-fail2ban-log-spam).
* **Block User Enumeration**
*WPf2b* can block user enumeration. See [`WP_FAIL2BAN_BLOCK_USER_ENUMERATION`](https://docs.wp-fail2ban.com/en/4.2/defines.html#wp-fail2ban-block-user-enumeration).
* **Work-Arounds for Broken syslogd**
*WPf2b* can be configured to work around most syslogd weirdness. For an overview see [`WP_FAIL2BAN_SYSLOG_SHORT_TAG`](https://docs.wp-fail2ban.com/en/4.2/defines.html#wp-fail2ban-syslog-short-tag) and [`WP_FAIL2BAN_HTTP_HOST`](https://docs.wp-fail2ban.com/en/4.2/defines.html#wp-fail2ban-http-host).
* **Blocking Users**
*WPf2b* can be configured to short-cut the login process when the username matches a regex. For an overview see [`WP_FAIL2BAN_BLOCKED_USERS`](https://docs.wp-fail2ban.com/en/4.2/defines.html#wp-fail2ban-blocked-users).
* **`mu-plugins` Support**
*WPf2b* can easily be configured as a must-use plugin - see [Configuration](https://docs.wp-fail2ban.com/en/4.2/configuration.html#mu-plugins-support).
== Installation ==
1. Install via the Plugin Directory, or upload to your plugins directory.
1. Activate the plugin through the 'Plugins' menu in WordPress.
1. Edit `wp-config.php` to suit your needs - see [Configuration](https://docs.wp-fail2ban.com/en/4.2/configuration.html).
== Changelog ==
= 4.2.5 =
* Properly fix PHP 5.3 support; tested on CentOS 6. Does not support any UI or Premium features.
* Fix potential issue with `WP_FAIL2BAN_BLOCK_USER_ENUMERATION` if calling REST API or XMLRPC from admin area.
= 4.2.4 =
* Add filter for login failed message.
* Fix logging spam comments from admin area.
* Fix Settings link from Plugins page.
* Update Freemius library
= 4.2.3 =
* Workaround for some versions of PHP 7.x that would cause `define()`s to be ignored.
* Add config note to settings tabs.
* Fix documentation links.
= 4.2.2 =
* Fix 5.3 compatibility.
= 4.2.1 =
* Completed support for [`WP_FAIL2BAN_COMMENT_EXTRA_LOG`](https://docs.wp-fail2ban.com/en/4.2/defines/WP_FAIL2BAN_COMMENT_EXTRA_LOG.html).
* Add support for 3rd-party plugins; see [Developers](https://docs.wp-fail2ban.com/en/4.2/developers.html).
* Add-on for [Contact Form 7](https://wordpress.org/plugins/wp-fail2ban-addon-contact-form-7/) (experimental).
* Add-on for [Gravity Forms](https://wordpress.org/plugins/wp-fail2ban-addon-gravity-forms/) (experimental).
* Change logging for known-user with incorrect password; previously logged as unknown user and matched by `hard` filters (due to limitations in older versions of WordPress), now logged as known user and matched by `soft`.
* Bugfix for email-as-username - now logged correctly and matched by `soft`, not `hard`, filters.
* Bugfix for regression in code to prevent Free/Premium conflict.
= 4.2.0 =
* Not released.
= 4.1.0 =
* Add separate logging for REST authentication.
* Fix conflict with earlier versions pre-installed in `mu-plugins`. See [Is *WPf2b* Already Installed?](https://docs.wp-fail2ban.com/en/4.1/installation.html#is-wp-fail2ban-already-installed).
= 4.0.5 =
* Add [`WP_FAIL2BAN_COMMENT_EXTRA_LOG`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_COMMENT_EXTRA_LOG.html).
* Add [`WP_FAIL2BAN_PINGBACK_ERROR_LOG`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_PINGBACK_ERROR_LOG.html) (future functionality).
* Change `WP_FAIL2BAN_LOG_SPAM` to use `LOG_NOTICE`.
* Change `WP_FAIL2BAN_SPAM_LOG` to `LOG_AUTH`.
* Change `WP_FAIL2BAN_LOG_COMMENTS_EXTRA` events to use `LOG_NOTICE` by default.
* Fix conflict with 3.x in `mu-plugins`.
= 4.0.2 =
* Fix PHP 5.3 compatibility.
* Bugfix for `WP_FAIL2BAN_LOG_COMMENTS_EXTRA`.
* Bugfix for `WP_FAIL2BAN_REMOTE_ADDR` summary.
= 4.0.1 =
* Add extra features via Freemius. **This is entirely optional.** *WPf2b* works as before, including new features listed here.
* Add settings summary page (Settings -> WP fail2ban).
* Add [`WP_FAIL2BAN_PASSWORD_REQUEST_LOG`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_PASSWORD_REQUEST_LOG.html).
* Add [`WP_FAIL2BAN_SPAM_LOG`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_SPAM_LOG.html).
* Add [`WP_FAIL2BAN_LOG_COMMENTS_EXTRA`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_LOG_COMMENTS_EXTRA.html) - enable logging for attempted comments on posts which are:
* not found,
* closed for commenting,
* in the trash,
* drafts,
* password protected
* Block user enumeration via REST API.
= 4.0.0 =
* Not released.
= 3.6.0 =
* The [filter files](https://docs.wp-fail2ban.com/en/4.1/filters.html) are now generated from PHPDoc in the code. There were too many times when the filters were out of sync with the code (programmer error) - this should resolve that by bringing the patterns closer to the code that emits them.
* Added [PHPUnit tests](https://docs.wp-fail2ban.com/en/4.1/tests.html). Almost 100% code coverage, with the exception of [`WP_FAIL2BAN_PROXIES`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_PROXIES.html) which is quite hard to test properly.
* Bugfix for [`wordpress-soft.conf`](https://docs.wp-fail2ban.com/en/4.1/filters.html#wordpress-soft-conf).
* Add [`WP_FAIL2BAN_XMLRPC_LOG`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_XMLRPC_LOG.html).
* Add [`WP_FAIL2BAN_REMOTE_ADDR`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_REMOTE_ADDR.html).
* [`WP_FAIL2BAN_PROXIES`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_PROXIES.html) now supports an array of IPs with PHP 7.
* Moved all documentation to [https://docs.wp-fail2ban.com/](https://docs.wp-fail2ban.com/).
= 3.5.3 =
* Bugfix for [`wordpress-hard.conf`](https://docs.wp-fail2ban.com/en/4.1/filters.html#wordpress-hard-conf).
= 3.5.1 =
* Bugfix for [`WP_FAIL2BAN_BLOCK_USER_ENUMERATION`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_BLOCK_USER_ENUMERATION.html).
= 3.5.0 =
* Add [`WP_FAIL2BAN_OPENLOG_OPTIONS`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_OPENLOG_OPTIONS.html).
* Add [`WP_FAIL2BAN_LOG_COMMENTS`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_LOG_COMMENTS.html) and [`WP_FAIL2BAN_COMMENT_LOG`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_COMMENT_LOG.html).
* Add [`WP_FAIL2BAN_LOG_PASSWORD_REQUEST`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_LOG_PASSWORD_REQUEST.html).
* Add [`WP_FAIL2BAN_LOG_SPAM`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_LOG_SPAM.html).
* Add [`WP_FAIL2BAN_TRUNCATE_HOST`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_TRUNCATE_HOST.html).
* [`WP_FAIL2BAN_BLOCKED_USERS`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_BLOCKED_USERS.html) now supports an array of users with PHP 7.
= 3.0.3 =
* Fix regex in [`wordpress-hard.conf`](https://docs.wp-fail2ban.com/en/4.1/filters.html#wordpress-hard-conf).
= 3.0.2 =
* Prevent double logging in WP 4.5.x for XML-RPC authentication failure
= 3.0.1 =
* Fix regex in [`wordpress-hard.conf`](https://docs.wp-fail2ban.com/en/4.1/filters.html#wordpress-hard-conf).
= 3.0.0 =
* Add [`WP_FAIL2BAN_SYSLOG_SHORT_TAG`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_SYSLOG_SHORT_TAG.html).
* Add [`WP_FAIL2BAN_HTTP_HOST`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_HTTP_HOST.html).
* Log XML-RPC authentication failure.
* Add better support for MU deployment.
= 2.3.2 =
* Bugfix [`WP_FAIL2BAN_BLOCKED_USERS`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_BLOCKED_USERS.html).
= 2.3.0 =
* Bugfix in *experimental* [`WP_FAIL2BAN_PROXIES`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_PROXIES.html) code (thanks to KyleCartmell).
= 2.2.1 =
* Fix stupid mistake with [`WP_FAIL2BAN_BLOCKED_USERS`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_BLOCKED_USERS.html).
= 2.2.0 =
* Custom authentication log is now called [`WP_FAIL2BAN_AUTH_LOG`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_AUTH_LOG.html).
* Add logging for pingbacks; see [`WP_FAIL2BAN_LOG_PINGBACKS`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_LOG_PINGBACKS.html).
* Custom pingback log is called [`WP_FAIL2BAN_PINGBACK_LOG`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_PINGBACK_LOG.html).
= 2.1.1 =
* Minor bugfix.
= 2.1.0 =
* Add support for blocking user enumeration; see [`WP_FAIL2BAN_BLOCK_USER_ENUMERATION`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_BLOCK_USER_ENUMERATION.html).
* Add support for CIDR notation in [`WP_FAIL2BAN_PROXIES`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_PROXIES.html).
= 2.0.1 =
* Bugfix in *experimental* [`WP_FAIL2BAN_PROXIES`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_PROXIES.html) code.
= 2.0.0 =
* Add *experimental* support for X-Forwarded-For header; see [`WP_FAIL2BAN_PROXIES`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_PROXIES.html).
* Add *experimental* support for regex-based login blocking; see [`WP_FAIL2BAN_BLOCKED_USERS`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_BLOCKED_USERS.html).
= 1.2.1 =
* Update FAQ.
= 1.2 =
* Fix harmless warning.
= 1.1 =
* Minor cosmetic updates.
= 1.0 =
* Initial release.
== Upgrade Notice ==
= 4.2.5 =
This is a minor release. You do not need to update your filters from 4.1.0.
= 4.2.4 =
This is a minor release. You do not need to update your filters from 4.1.0.
= 4.2.3 =
This is a bugfix release. You do not need to update your filters from 4.1.0.
= 4.2.2 =
You do not need to update your filters from 4.1.0.
= 4.2.1 =
You do not need to update your filters from 4.1.0.
= 4.1.0 =
To take advantage of the new features you will need up update your `fail2ban` filters; existing filters will continue to work as before.
= 4.0.5 =
This is a security fix (Freemius SDK): all 4.x users are strongly advised to upgrade immediately. You do not need to update your filters from 4.0.1.
= 4.0.4 =
This is a bugfix. You do not need to update your filters from 4.0.1.
= 4.0.3 =
This is a bugfix. You do not need to update your filters from 4.0.1.
= 4.0.2 =
This is a bugfix. You do not need to update your filters from 4.0.1.
= 4.0.1 =
To take advantage of the new features you will need up update your `fail2ban` filters; existing filters will continue to work as before.
= 3.6.0 =
You will need up update your `fail2ban` filters.
= 3.5.3 =
You will need up update your `fail2ban` filters.
= 3.5.1 =
Bugfix: disable [`WP_FAIL2BAN_BLOCK_USER_ENUMERATION`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_BLOCK_USER_ENUMERATION.html) in admin area....
= 3.5.0 =
You will need up update your `fail2ban` filters.
= 3.0.3 =
You will need up update your `fail2ban` filters.
= 3.0.0 =
BREAKING CHANGE: The `fail2ban` filters have been split into two files. You will need up update your `fail2ban` configuration.
= 2.3.0 =
Fix for [`WP_FAIL2BAN_PROXIES`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_PROXIES.html); if you're not using it you can safely skip this release.
= 2.2.1 =
Bugfix.
= 2.2.0 =
BREAKING CHANGE: `WP_FAIL2BAN_LOG` has been renamed to [`WP_FAIL2BAN_AUTH_LOG`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_AUTH_LOG.html).
Pingbacks are getting a lot of attention recently, so *WPf2b* can now log them.
The `wordpress.conf` filter has been updated; you will need to update your `fail2ban` configuration.
= 2.1.0 =
The `wordpress.conf` filter has been updated; you will need to update your `fail2ban` configuration.
= 2.0.1 =
Bugfix in experimental code; still an experimental release.
= 2.0.0 =
This is an experimental release. If your current version is working and you're not interested in the new features, skip this version - wait for 2.1.0. For those that do want to test this release, note that `wordpress.conf` has changed - you'll need to copy it to `fail2ban/filters.d` again.

254
wp-fail2ban-main.php Normal file
View File

@ -0,0 +1,254 @@
<?php
/**
* WP fail2ban main file
*
* @since 4.0.0
* @package wp-fail2ban
*/
namespace org\lecklider\charles\wordpress\wp_fail2ban;
if ( !defined( 'ABSPATH' ) ) {
exit;
}
require_once __DIR__ . '/lib/constants.php';
require_once __DIR__ . '/lib/loader.php';
require_once __DIR__ . '/lib/defaults.php';
register_activation_hook( WP_FAIL2BAN_FILE, function () {
foreach ( get_mu_plugins() as $plugin => $data ) {
if ( 0 === strpos( $data['Name'], 'WP fail2ban' ) ) {
$wp_f2b_ver = substr( WP_FAIL2BAN_VER, 0, strrpos( WP_FAIL2BAN_VER, '.' ) );
$wpf2b = 'WP fail2ban';
$error_msg = sprintf( __( '<h1>Cannot activate %s</h1>' ), $wpf2b );
$mu_file = WPMU_PLUGIN_DIR . '/' . $plugin;
if ( is_link( $mu_file ) ) {
if ( false === ($link = readlink( $mu_file )) || false === ($path = realpath( $mu_file )) ) {
$h3 = __( 'A broken symbolic link was found in <tt>mu-plugins</tt>:' );
$error_msg .= <<<__ERROR__
<h3>{$h3}</h3>
<p><tt>{$mu_file}</tt></p>
__ERROR__;
} elseif ( WP_FAIL2BAN_FILE == $path ) {
// OK, we're linking to ourself
} else {
$mu_file = str_replace( '/', '/<wbr>', $mu_file );
$mu_file = substr( $mu_file, strlen( WPMU_PLUGIN_DIR ) - 1 );
$h3 = __( 'A conflicting symbolic link was found in <tt>mu-plugins</tt>:' );
$error_msg .= <<<__ERROR__
<h3>{$h3}</h3>
<style>
table { text-align: center; }
td { width: 50%; }
th { font-size: 200%; }
td, th { font-family: monospace; }
span.tt { font-weight: bold; }
</style>
<table>
<tr>
<td>{$mu_file}</td>
<th>&DoubleRightArrow;</th>
<td>{$link}</td>
</tr>
<tr>
<td colspan="3"><span class="tt">&equiv;</span> <span>{$path}</span></td>
</tr>
<tr>
<td colspan="3"></td>
</tr>
</table>
__ERROR__;
}
} else {
$mu_file = str_replace( '/', '/<wbr>', $mu_file );
$mu_file = substr( $mu_file, strlen( WPMU_PLUGIN_DIR ) - 1 );
$h3 = __( 'A conflicting file was found in <tt>mu-plugins</tt>:' );
$error_msg .= <<<__ERROR__
<h3>{$h3}</h3>
<p><tt>{$mu_file}</tt></p>
__ERROR__;
}
$error_msg .= sprintf( __( '<p>Please see the <a href="%s" target="_blank">documentation</a> for how to configure %s for <tt>mu-plugins</tt>.</p>' ), "https://docs.wp-fail2ban.com/en/{$wp_f2b_ver}/configuration.html#mu-plugins-support", $wpf2b );
$error_msg .= sprintf( __( '<p>Click <a href="%s">here</a> to return to the plugins page.</p>' ), admin_url( 'plugins.php' ) );
deactivate_plugins( plugin_basename( WP_FAIL2BAN_FILE ) );
wp_die( $error_msg );
}
}
} );
require __DIR__ . '/feature/lib.php';
/**
* @since 4.2.5
*/
if ( version_compare( PHP_VERSION, '5.6.0', '>=' ) ) {
/**
* @since 4.2.0
*/
global $wp_fail2ban ;
$wp_fail2ban['plugins'] = array();
require __DIR__ . '/feature/plugins.php';
if ( is_admin() ) {
require 'admin/admin.php';
}
} elseif ( is_admin() ) {
require __DIR__ . '/admin/lib/about.php';
add_action( 'admin_menu', function () {
add_menu_page(
'WP fail2ban',
'WP fail2ban',
'manage_options',
'wp-fail2ban',
__NAMESPACE__ . '\\about',
'dashicons-analytics'
);
} );
}
/**
* @since 4.0.5
*/
if ( !function_exists( __NAMESPACE__ . '\\wp_login' ) ) {
/**
* Hook: wp_login
*
* @since 4.1.0 Add REST support
* @since 3.5.0 Refactored for unit testing
* @since 1.0.0
*
* @param string $user_login
* @param mixed $user
*/
function wp_login( $user_login, $user )
{
global $wp_xmlrpc_server ;
openlog();
syslog( LOG_INFO, "Accepted password for {$user_login}" );
closelog();
// @codeCoverageIgnoreEnd
}
add_action(
'wp_login',
__NAMESPACE__ . '\\wp_login',
10,
2
);
}
/**
* @since 4.0.5
*/
if ( !function_exists( __NAMESPACE__ . '\\wp_login_failed' ) ) {
/**
* Hook: wp_login_failed
*
* @since 4.2.4 Add message filter
* @since 4.2.0 Change username check
* @since 4.1.0 Add REST support
* @since 3.5.0 Refactored for unit testing
* @since 1.0.0
*
* @param string $username
*
* @wp-f2b-hard Authentication attempt for unknown user .*
* @wp-f2b-hard REST authentication attempt for unknown user .*
* @wp-f2b-hard XML-RPC authentication attempt for unknown user .*
* @wp-f2b-soft Authentication failure for .*
* @wp-f2b-soft REST authentication failure for .*
* @wp-f2b-soft XML-RPC authentication failure for .*
*/
function wp_login_failed( $username )
{
global $wp_xmlrpc_server ;
if ( defined( 'REST_REQUEST' ) ) {
$msg = 'REST a';
$filter = '::REST';
} elseif ( $wp_xmlrpc_server ) {
$msg = 'XML-RPC a';
$filter = '::XML-RPC';
} else {
$msg = 'A';
$filter = '';
}
$username = trim( $username );
$msg .= ( wp_cache_get( $username, 'useremail' ) || wp_cache_get( sanitize_user( $username ), 'userlogins' ) ? "uthentication failure for {$username}" : "uthentication attempt for unknown user {$username}" );
$msg = apply_filters( "wp_fail2ban::wp_login_failed{$filter}", $msg );
openlog();
syslog( LOG_NOTICE, $msg );
closelog();
// @codeCoverageIgnoreEnd
}
add_action( 'wp_login_failed', __NAMESPACE__ . '\\wp_login_failed' );
}
/**
* @since 4.2.5
*/
if ( !is_admin() ) {
/**
* User enumeration
*
* @since 4.0.0 Refactored
* @since 2.1.0
*/
if ( defined( 'WP_FAIL2BAN_BLOCK_USER_ENUMERATION' ) && true === WP_FAIL2BAN_BLOCK_USER_ENUMERATION ) {
require_once __DIR__ . '/feature/user-enum.php';
}
/**
* XML-RPC
*
* @since 4.0.0 Refactored
* @since 3.0.0
*/
if ( defined( 'XMLRPC_REQUEST' ) && true === XMLRPC_REQUEST ) {
require_once __DIR__ . '/feature/xmlrpc.php';
}
}
/**
* Comments
*
* @since 4.0.0 Refactored
* @since 3.5.0
*/
if ( defined( 'WP_FAIL2BAN_LOG_COMMENTS' ) && true === WP_FAIL2BAN_LOG_COMMENTS ) {
require_once __DIR__ . '/feature/comments.php';
}
/**
* Password
*
* @since 4.0.0 Refactored
* @since 3.5.0
*/
if ( defined( 'WP_FAIL2BAN_LOG_PASSWORD_REQUEST' ) && true === WP_FAIL2BAN_LOG_PASSWORD_REQUEST ) {
require_once __DIR__ . '/feature/password.php';
}
/**
* Spam
*
* @since 4.0.0 Refactored
* @since 3.5.0
*/
if ( defined( 'WP_FAIL2BAN_LOG_SPAM' ) && true === WP_FAIL2BAN_LOG_SPAM ) {
require_once __DIR__ . '/feature/spam.php';
}
/**
* Users
*
* @since 4.0.0 Refactored
* @since 2.0.0
*/
if ( defined( 'WP_FAIL2BAN_BLOCKED_USERS' ) && '' < WP_FAIL2BAN_BLOCKED_USERS ) {
require_once __DIR__ . '/feature/user.php';
}

105
wp-fail2ban.php Normal file
View File

@ -0,0 +1,105 @@
<?php
/*
* Plugin Name: WP fail2ban
* Plugin URI: https://wp-fail2ban.com/
* Description: Write a myriad of WordPress events to syslog for integration with fail2ban.
* Text Domain: wp-fail2ban
* Version: 4.2.5
* Author: Charles Lecklider
* Author URI: https://charles.lecklider.org/
* License: GPLv2
* SPDX-License-Identifier: GPL-2.0
* Requires PHP: 5.3
*
*/
/*
* Copyright 2012-19 Charles Lecklider (email : wordpress@charles.lecklider.org)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License, version 2, as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/**
* WP fail2ban
*
* @package wp-fail2ban
*/
namespace org\lecklider\charles\wordpress\wp_fail2ban;
/**
* @since 4.0.5
*/
define( 'WP_FAIL2BAN_VER', '4.2.5' );
define( 'WP_FAIL2BAN_FILE', __FILE__ );
/**
* Freemius integration
*
* @since 4.0.0
*/
if ( function_exists( __NAMESPACE__ . '\\wf_fs' ) ) {
// @codeCoverageIgnoreStart
wf_fs()->set_basename( false, __FILE__ );
return;
} else {
/**
* Create a helper function for easy SDK access.
*/
function wf_fs()
{
global $wf_fs ;
if ( !isset( $wf_fs ) ) {
// Include Freemius SDK.
require_once dirname( __FILE__ ) . '/vendor/freemius/wordpress-sdk/start.php';
$wf_fs = fs_dynamic_init( array(
'id' => '3072',
'slug' => 'wp-fail2ban',
'type' => 'plugin',
'public_key' => 'pk_146d2c2a5bee3b157e43501ef8682',
'is_premium' => false,
'has_addons' => true,
'has_paid_plans' => true,
'trial' => array(
'days' => 7,
'is_require_payment' => false,
),
'menu' => array(
'slug' => 'wp-fail2ban',
'first-path' => 'admin.php?page=wp-fail2ban',
'support' => false,
),
'is_live' => true,
) );
}
return $wf_fs;
}
// Init Freemius.
wf_fs();
// Set currency to GBP
wf_fs()->add_filter( 'default_currency', function () {
return 'gbp';
} );
// Signal that SDK was initiated.
do_action( 'wf_fs_loaded' );
}
// @codeCoverageIgnoreEnd
/**
* Freemius insists on mangling the formatting of the main plugin file
*
* @since 4.0.0 Refactored
*/
require_once 'wp-fail2ban-main.php';