WP fail2ban - Version 3.5.0

Version Description

  • Add WP_FAIL2BAN_OPENLOG_OPTIONS.
  • Add WP_FAIL2BAN_LOG_COMMENTS and WP_FAIL2BAN_COMMENT_LOG.
  • Add WP_FAIL2BAN_LOG_PASSWORD_REQUEST.
  • Add WP_FAIL2BAN_LOG_SPAM.
  • Add WP_FAIL2BAN_TRUNCATE_HOST.
  • WP_FAIL2BAN_BLOCKED_USERS now supports an array of users with PHP 7. *
Download this release

Release Info

Developer invisnet
Plugin Icon 128x128 WP fail2ban
Version 3.5.0
Comparing to
See all releases

Code changes from version 3.0.3 to 3.5.0

filters.d/wordpress-hard.conf ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Fail2Ban filter for WordPress hard failures
2
+ #
3
+
4
+ [INCLUDES]
5
+
6
+ before = common.conf
7
+
8
+ [Definition]
9
+
10
+ _daemon = (?:wordpress|wp)
11
+
12
+ failregex = ^%(__prefix_line)sBlocked user enumeration attempt from <HOST>$
13
+ ^%(__prefix_line)sBlocked authentication attempt for .* from <HOST>$
14
+ ^%(__prefix_line)sPingback error .* generated from <HOST>$
15
+ ^%(__prefix_line)sSpam comment \d+ from <HOST>$
16
+ ^%(__prefix_line)sXML-RPC multicall authentication failure from <HOST>$
17
+
18
+ ignoreregex =
19
+
20
+ # DEV Notes:
21
+ # Requires the 'WP fail2ban' plugin:
22
+ # https://wordpress.org/plugins/wp-fail2ban/
23
+ #
24
+ # Author: Charles Lecklider
wordpress-soft.conf → filters.d/wordpress-soft.conf RENAMED
File without changes
readme.txt CHANGED
@@ -4,18 +4,18 @@ Author URI: https://charles.lecklider.org/
4
  Plugin URI: https://charles.lecklider.org/wordpress/wp-fail2ban/
5
  Tags: fail2ban, login, security, syslog
6
  Requires at least: 3.4.0
7
- Tested up to: 4.5.3
8
- Stable tag: 3.0.3
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
 
12
- Write all login attempts to syslog for integration with fail2ban.
13
 
14
  == Description ==
15
 
16
  [fail2ban](http://www.fail2ban.org/) is one of the simplest and most effective security measures you can implement to prevent brute-force password-guessing attacks.
17
 
18
- *WP fail2ban* logs all login attempts, whether successful or not, to syslog using LOG_AUTH. To make log parsing as simple as possible *WPf2b* uses the same format as sshd. For example:
19
 
20
  Oct 17 20:59:54 foobar wordpress(www.example.com)[1234]: Authentication failure for admin from 192.168.0.1
21
  Oct 17 21:00:00 foobar wordpress(www.example.com)[2345]: Accepted password for admin from 192.168.0.1
@@ -30,11 +30,19 @@ Requires PHP 5.3 or later.
30
 
31
  *WPf2b* can be configured to work with CloudFlare and other proxy servers. See `WP_FAIL2BAN_PROXIES` in the FAQ.
32
 
 
 
 
 
33
  **Pingbacks**
34
 
35
  *WPf2b* logs failed pingbacks, and can log all pingbacks. See `WP_FAIL2BAN_LOG_PINGBACKS` in the FAQ.
36
 
37
- **WP_FAIL2BAN_BLOCK_USER_ENUMERATION**
 
 
 
 
38
 
39
  *WPf2b* can block user enumeration. See `WP_FAIL2BAN_BLOCK_USER_ENUMERATION` in the FAQ.
40
 
@@ -99,7 +107,15 @@ Adding:
99
 
100
  define('WP_FAIL2BAN_SYSLOG_SHORT_TAG',true);
101
 
102
- to `functions.php` will make *WPf2b* use `wp` as the syslog tag, rather than the normal `wordpress`. This buys you 7 characters which may be enough to work around the problem, but if it's not enough you should look at `WP_FAIL2BAN_HTTP_HOST` too.
 
 
 
 
 
 
 
 
103
 
104
  = WP_FAIL2BAN_BLOCKED_USERS – what’s it all about? =
105
 
@@ -115,6 +131,10 @@ will block any attempt to log in as `admin` before most of the core WordPress co
115
 
116
  *WPf2b* doesn't do anything to the regex other than make it case-insensitive.
117
 
 
 
 
 
118
  = WP_FAIL2BAN_PROXIES – what’s it all about? =
119
 
120
  The idea here is to list the IP addresses of the trusted proxies that will appear as the remote IP for the request. When defined:
@@ -147,6 +167,24 @@ By default, *WPf2b* uses LOG_USER for logging pingbacks. If you'd rather it used
147
 
148
  define('WP_FAIL2BAN_PINGBACK_LOG',LOG_LOCAL3);
149
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
  = WP_FAIL2BAN_AUTH_LOG – what’s it all about? =
151
 
152
  By default, *WPf2b* uses LOG_AUTH for logging authentication success or failure. However, some systems use LOG_AUTHPRIV instead, but there's no good run-time way to tell. If your system uses LOG_AUTHPRIV you should add the following to `wp-config.php`:
@@ -155,6 +193,15 @@ By default, *WPf2b* uses LOG_AUTH for logging authentication success or failure.
155
 
156
  == Changelog ==
157
 
 
 
 
 
 
 
 
 
 
158
  = 3.0.3 =
159
  * Fix regex in `wordpress-hard.conf`
160
 
@@ -212,6 +259,9 @@ By default, *WPf2b* uses LOG_AUTH for logging authentication success or failure.
212
 
213
  == Upgrade Notice ==
214
 
 
 
 
215
  = 3.0.3 =
216
  You will need up update your `fail2ban` filters.
217
 
4
  Plugin URI: https://charles.lecklider.org/wordpress/wp-fail2ban/
5
  Tags: fail2ban, login, security, syslog
6
  Requires at least: 3.4.0
7
+ Tested up to: 4.6.0
8
+ Stable tag: 3.5.0
9
  License: GPLv2 or later
10
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
11
 
12
+ Write a myriad of WordPress events to syslog for integration with fail2ban.
13
 
14
  == Description ==
15
 
16
  [fail2ban](http://www.fail2ban.org/) is one of the simplest and most effective security measures you can implement to prevent brute-force password-guessing attacks.
17
 
18
+ *WP fail2ban* logs all login attempts - including via XML-RPC, whether successful or not, to syslog using LOG_AUTH. For example:
19
 
20
  Oct 17 20:59:54 foobar wordpress(www.example.com)[1234]: Authentication failure for admin from 192.168.0.1
21
  Oct 17 21:00:00 foobar wordpress(www.example.com)[2345]: Accepted password for admin from 192.168.0.1
30
 
31
  *WPf2b* can be configured to work with CloudFlare and other proxy servers. See `WP_FAIL2BAN_PROXIES` in the FAQ.
32
 
33
+ **Comments**
34
+
35
+ *WPf2b* can log comments. See `WP_FAIL2BAN_LOG_COMMENTS`.
36
+
37
  **Pingbacks**
38
 
39
  *WPf2b* logs failed pingbacks, and can log all pingbacks. See `WP_FAIL2BAN_LOG_PINGBACKS` in the FAQ.
40
 
41
+ **Spam**
42
+
43
+ *WPf2b* can log comments marked as spam. See `WP_FAIL2BAN_LOG_SPAM` in the FAQ.
44
+
45
+ **User Enumeration**
46
 
47
  *WPf2b* can block user enumeration. See `WP_FAIL2BAN_BLOCK_USER_ENUMERATION` in the FAQ.
48
 
107
 
108
  define('WP_FAIL2BAN_SYSLOG_SHORT_TAG',true);
109
 
110
+ to `functions.php` will make *WPf2b* use `wp` as the syslog tag, rather than the normal `wordpress`. This buys you 7 characters which may be enough to work around the problem, but if it's not enough you should look at `WP_FAIL2BAN_HTTP_HOST` or `WP_FAIL2BAN_TRUNCATE_HOST` too.
111
+
112
+ = WP_FAIL2BAN_TRUNCATE_HOST =
113
+
114
+ If you've set `WP_FAIL2BAN_SYSLOG_SHORT_TAG` and defining `WP_FAIL2BAN_HTTP_HOST` for each virtual host isn't appropriate, you can set `WP_FAIL2BAN_TRUNCATE_HOST` to whatever value you need to make syslog happy:
115
+
116
+ define('WP_FAIL2BAN_TRUNCATE_HOST',8);
117
+
118
+ This does exactly what the name suggests: truncates the host name to the length you specify. As a result there's no guarantee that what's left will be enough to identify the site.
119
 
120
  = WP_FAIL2BAN_BLOCKED_USERS – what’s it all about? =
121
 
131
 
132
  *WPf2b* doesn't do anything to the regex other than make it case-insensitive.
133
 
134
+ If you're running PHP 7, you can now specify an array of users instead:
135
+
136
+ define('WP_FAIL2BAN_BLOCKED_USERS',['admin','another','user']);
137
+
138
  = WP_FAIL2BAN_PROXIES – what’s it all about? =
139
 
140
  The idea here is to list the IP addresses of the trusted proxies that will appear as the remote IP for the request. When defined:
167
 
168
  define('WP_FAIL2BAN_PINGBACK_LOG',LOG_LOCAL3);
169
 
170
+ = WP_FAIL2BAN_LOG_COMMENTS =
171
+
172
+ *WPf2b* can now log comments. To enable this feature, add the following to `wp-config.php`:
173
+
174
+ define('WP_FAIL2BAN_LOG_COMMENTS',true);
175
+
176
+ By default, *WPf2b* uses LOG_USER for logging comments. If you'd rather it used a different facility you can change it by adding something like the following to `wp-config.php`:
177
+
178
+ define('WP_FAIL2BAN_COMMENT_LOG',LOG_LOCAL3);
179
+
180
+ = WP_FAIL2BAN_LOG_SPAM =
181
+
182
+ *WPf2b* can now log spam comments. To enable this feature, add the following to `wp-config.php`:
183
+
184
+ define('WP_FAIL2BAN_LOG_SPAM',true);
185
+
186
+ The comment ID and IP will be written to `WP_FAIL2BAN_AUTH_LOG` and matched by `wordpress-hard`.
187
+
188
  = WP_FAIL2BAN_AUTH_LOG – what’s it all about? =
189
 
190
  By default, *WPf2b* uses LOG_AUTH for logging authentication success or failure. However, some systems use LOG_AUTHPRIV instead, but there's no good run-time way to tell. If your system uses LOG_AUTHPRIV you should add the following to `wp-config.php`:
193
 
194
  == Changelog ==
195
 
196
+ = 3.5.0 =
197
+ * Add `WP_FAIL2BAN_OPENLOG_OPTIONS`.
198
+ * Add `WP_FAIL2BAN_LOG_COMMENTS` and `WP_FAIL2BAN_COMMENT_LOG`.
199
+ * Add `WP_FAIL2BAN_LOG_PASSWORD_REQUEST`.
200
+ * Add `WP_FAIL2BAN_LOG_SPAM`.
201
+ * Add `WP_FAIL2BAN_TRUNCATE_HOST`.
202
+ * `WP_FAIL2BAN_BLOCKED_USERS` now supports an array of users with PHP 7.
203
+ *
204
+
205
  = 3.0.3 =
206
  * Fix regex in `wordpress-hard.conf`
207
 
259
 
260
  == Upgrade Notice ==
261
 
262
+ = 3.5.0 =
263
+ You will need up update your `fail2ban` filters.
264
+
265
  = 3.0.3 =
266
  You will need up update your `fail2ban` filters.
267
 
wordpress-hard.conf DELETED
@@ -1,33 +0,0 @@
1
- # Fail2Ban configuration file
2
- #
3
- # Author: Charles Lecklider
4
- #
5
-
6
- [INCLUDES]
7
-
8
- # Read common prefixes. If any customizations available -- read them from
9
- # common.local
10
- before = common.conf
11
-
12
-
13
- [Definition]
14
-
15
- _daemon = (?:wordpress|wp)
16
-
17
- # Option: failregex
18
- # Notes.: regex to match the password failures messages in the logfile. The
19
- # host must be matched by a group named "host". The tag "<HOST>" can
20
- # be used for standard IP/hostname matching and is only an alias for
21
- # (?:::f{4,6}:)?(?P<host>[\w\-.^_]+)
22
- # Values: TEXT
23
- #
24
- failregex = ^%(__prefix_line)sAuthentication attempt for unknown user .* from <HOST>( via XML-RPC)?$
25
- ^%(__prefix_line)sBlocked authentication attempt for .* from <HOST>( via XML-RPC)?$
26
- ^%(__prefix_line)sBlocked user enumeration attempt from <HOST>$
27
- ^%(__prefix_line)sPingback error .* generated from <HOST>$
28
-
29
- # Option: ignoreregex
30
- # Notes.: regex to ignore. If this regex matches, the line is ignored.
31
- # Values: TEXT
32
- #
33
- ignoreregex =
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
wp-fail2ban.php CHANGED
@@ -4,7 +4,7 @@
4
  * Plugin URI: https://charles.lecklider.org/wordpress/wp-fail2ban/
5
  * Description: Write all login attempts to syslog for integration with fail2ban.
6
  * Text Domain: wp-fail2ban
7
- * Version: 3.0.3
8
  * Author: Charles Lecklider
9
  * Author URI: https://charles.lecklider.org/
10
  * License: GPL2
@@ -31,148 +31,296 @@
31
  namespace org\lecklider\charles\wordpress\wp_fail2ban;
32
 
33
 
34
- if (!defined('WP_FAIL2BAN')) {
35
- define('WP_FAIL2BAN', true);
36
-
37
- function openlog($log = LOG_AUTH, $custom_log = 'WP_FAIL2BAN_AUTH_LOG')
38
- {
39
- $tag = (defined('WP_FAIL2BAN_SYSLOG_SHORT_TAG') && true === WP_FAIL2BAN_SYSLOG_SHORT_TAG)
40
- ? 'wp'
41
- : 'wordpress';
42
- $host = (array_key_exists('WP_FAIL2BAN_HTTP_HOST',$_ENV))
43
- ? $_ENV['WP_FAIL2BAN_HTTP_HOST']
44
- : $_SERVER['HTTP_HOST'];
45
- \openlog("$tag($host)",
46
- LOG_NDELAY|LOG_PID,
47
- defined($custom_log) ? constant($custom_log) : $log);
48
- }
49
 
50
- function bail()
51
- {
52
- ob_end_clean();
53
- header('HTTP/1.0 403 Forbidden');
54
- header('Content-Type: text/plain');
55
- exit('Forbidden');
56
- }
57
 
58
- function remote_addr()
59
- {
60
- if (defined('WP_FAIL2BAN_PROXIES')) {
61
- if (array_key_exists('HTTP_X_FORWARDED_FOR',$_SERVER)) {
62
- $ip = ip2long($_SERVER['REMOTE_ADDR']);
63
- foreach(explode(',',WP_FAIL2BAN_PROXIES) as $proxy) {
64
- if (2 == count($cidr = explode('/',$proxy))) {
65
- $net = ip2long($cidr[0]);
66
- $mask = ~ ( pow(2, (32 - $cidr[1])) - 1 );
67
- } else {
68
- $net = ip2long($proxy);
69
- $mask = -1;
70
- }
71
- if ($net == ($ip & $mask)) {
72
- return (false===($len = strpos($_SERVER['HTTP_X_FORWARDED_FOR'],',')))
73
- ? $_SERVER['HTTP_X_FORWARDED_FOR']
74
- : substr($_SERVER['HTTP_X_FORWARDED_FOR'],0,$len);
75
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  }
77
  }
78
  }
79
-
80
- return $_SERVER['REMOTE_ADDR'];
81
  }
82
- /*
83
- * @since 2.0.0
84
- */
85
- if (defined('WP_FAIL2BAN_BLOCKED_USERS')) {
86
- add_filter( 'authenticate',
87
- function($user, $username, $password)
88
- {
89
- if (!empty($username) && preg_match('/'.WP_FAIL2BAN_BLOCKED_USERS.'/i', $username)) {
90
- openlog();
91
- \syslog(LOG_NOTICE,"Blocked authentication attempt for $username from ".remote_addr());
92
- bail();
93
- }
94
-
95
- return $user;
96
- },1,3);
97
- }
98
- /*
99
- * @since 2.1.0
100
- */
101
- if (defined('WP_FAIL2BAN_BLOCK_USER_ENUMERATION') && true === WP_FAIL2BAN_BLOCK_USER_ENUMERATION) {
102
- add_filter( 'redirect_canonical',
103
- function($redirect_url, $requested_url)
104
- {
105
- if (intval(@$_GET['author'])) {
106
- openlog();
107
- \syslog(LOG_NOTICE,'Blocked user enumeration attempt from '.remote_addr());
108
- bail();
109
- }
110
-
111
- return $redirect_url;
112
- },10,2);
113
- }
114
- /*
115
- * @since 2.2.0
116
- */
117
- if (defined('WP_FAIL2BAN_LOG_PINGBACKS') && true === WP_FAIL2BAN_LOG_PINGBACKS) {
118
- add_action( 'xmlrpc_call',
119
- function($call)
120
- {
121
- if ('pingback.ping' == $call) {
122
- openlog(LOG_USER,'WP_FAIL2BAN_PINGBACK_LOG');
123
- \syslog(LOG_INFO,'Pingback requested from '.remote_addr());
124
- }
125
- });
126
- }
127
- /*
128
- * @since 1.0.0
129
- */
130
- add_action( 'wp_login',
131
- function($user_login, $user)
132
- {
133
- openlog();
134
- \syslog(LOG_INFO,"Accepted password for $user_login from ".remote_addr());
135
- },10,2);
136
- /*
137
- * @since 1.0.0
138
- */
139
- add_action( 'wp_login_failed',
140
- function($username)
141
- {
142
- $msg = (wp_cache_get($username, 'userlogins'))
143
- ? "Authentication failure for $username from "
144
- : "Authentication attempt for unknown user $username from ";
145
- $msg .= remote_addr();
146
- if (class_exists('wp_xmlrpc_server',false)) {
147
- $msg .= ' via XML-RPC';
148
- }
149
- openlog();
150
- \syslog(LOG_NOTICE,$msg);
151
- });
152
- /*
153
- * @since 3.0.0
154
- */
155
- $v = explode('.',$wp_version);
156
- if (4 == $v[0] && 5 > $v[1]) {
157
- // prevent double logging
158
- // will be removed for WP4.7
159
- add_action( 'xmlrpc_login_error',
160
- function($error, $user)
161
- {
162
- openlog();
163
- \syslog(LOG_NOTICE,'XML-RPC authentication failure from '.remote_addr());
164
- bail();
165
- },10,2);
166
- }
167
- /*
168
- * @since 3.0.0
169
- */
170
- add_filter( 'xmlrpc_pingback_error',
171
- function($ixr_error)
172
- {
173
- if (48 === $ixr_error->code)
174
- return $ixr_error;
175
- openlog();
176
- \syslog(LOG_NOTICE,'Pingback error '.$ixr_error->code.' generated from '.remote_addr());
177
- },5);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
178
  }
 
4
  * Plugin URI: https://charles.lecklider.org/wordpress/wp-fail2ban/
5
  * Description: Write all login attempts to syslog for integration with fail2ban.
6
  * Text Domain: wp-fail2ban
7
+ * Version: 3.5.0
8
  * Author: Charles Lecklider
9
  * Author URI: https://charles.lecklider.org/
10
  * License: GPL2
31
  namespace org\lecklider\charles\wordpress\wp_fail2ban;
32
 
33
 
34
+ /**
35
+ * Guard for MU
36
+ */
37
+ global $wp_fail2ban;
38
+ if (empty($wp_fail2ban) && defined('WP_FAIL2BAN')) return;
39
+ define('WP_FAIL2BAN',true);
 
 
 
 
 
 
 
 
 
40
 
 
 
 
 
 
 
 
41
 
42
+ /**
43
+ * Allow custom openlog openssl_get_cert_locations.
44
+ * e.g. you may not want the PID if logging remotely.
45
+ * @since 3.5.0
46
+ */
47
+ if (!defined('WP_FAIL2BAN_OPENLOG_OPTIONS')) {
48
+ define('WP_FAIL2BAN_OPENLOG_OPTIONS', LOG_PID);
49
+ }
50
+ /**
51
+ * Make sure all custom logs are defined.
52
+ * @since 3.5.0
53
+ */
54
+ if (!defined('WP_FAIL2BAN_AUTH_LOG')) {
55
+ define('WP_FAIL2BAN_AUTH_LOG', LOG_AUTH);
56
+ }
57
+ if (!defined('WP_FAIL2BAN_COMMENT_LOG')) {
58
+ define('WP_FAIL2BAN_COMMENT_LOG', LOG_USER);
59
+ }
60
+ if (!defined('WP_FAIL2BAN_PINGBACK_LOG')) {
61
+ define('WP_FAIL2BAN_PINGBACK_LOG', LOG_USER);
62
+ }
63
+
64
+
65
+ /**
66
+ * @since 3.5.0 Refactored for unit testing
67
+ */
68
+ function openlog($log = 'WP_FAIL2BAN_AUTH_LOG')
69
+ {
70
+ $tag = (defined('WP_FAIL2BAN_SYSLOG_SHORT_TAG') && true === WP_FAIL2BAN_SYSLOG_SHORT_TAG)
71
+ ? 'wp'
72
+ : 'wordpress';
73
+ $host = (array_key_exists('WP_FAIL2BAN_HTTP_HOST', $_ENV))
74
+ ? $_ENV['WP_FAIL2BAN_HTTP_HOST']
75
+ : $_SERVER['HTTP_HOST'];
76
+ /**
77
+ * Some varieties of syslogd have difficulty if $host is too long
78
+ * @since 3.5.0
79
+ */
80
+ if (defined('WP_FAIL2BAN_TRUNCATE_HOST') && 1 < intval(WP_FAIL2BAN_TRUNCATE_HOST)) {
81
+ $host = substr($host, 0, intval(WP_FAIL2BAN_TRUNCATE_HOST));
82
+ }
83
+ if (false === \openlog("$tag($host)", WP_FAIL2BAN_OPENLOG_OPTIONS, constant($log))) {
84
+ error_log('WPf2b: Cannot open syslog', 0);
85
+ } elseif (defined('WP_DEBUG') && true === WP_DEBUG) {
86
+ error_log('WPf2b: Opened syslog', 0);
87
+ }
88
+ }
89
+
90
+ /**
91
+ * @since 3.5.0
92
+ */
93
+ function syslog($level, $msg, $remote_addr = null)
94
+ {
95
+ $msg .= ' from ';
96
+ $msg .= (is_null($remote_addr))
97
+ ? remote_addr()
98
+ : $remote_addr;
99
+
100
+ if (false === \syslog($level, $msg)) {
101
+ error_log("WPf2b: Cannot write to syslog: '{$msg}'", 0);
102
+ } elseif (defined('WP_DEBUG') && true === WP_DEBUG) {
103
+ error_log("WPf2b: Wrote to syslog: '{$msg}'", 0);
104
+ }
105
+ \closelog();
106
+
107
+ /**
108
+ * @todo Remove this once phpunit can handle stderr.
109
+ */
110
+ if (!defined('ABSPATH')) {
111
+ echo "$level|$msg";
112
+ }
113
+ }
114
+
115
+ /**
116
+ * @since 3.5.0 Refactored for unit testing
117
+ */
118
+ function bail()
119
+ {
120
+ wp_die('Forbidden', 'Forbidden', array('response' => 403));
121
+ }
122
+
123
+ function remote_addr()
124
+ {
125
+ if (defined('WP_FAIL2BAN_PROXIES')) {
126
+ if (array_key_exists('HTTP_X_FORWARDED_FOR', $_SERVER)) {
127
+ $ip = ip2long($_SERVER['REMOTE_ADDR']);
128
+ foreach(explode(',', WP_FAIL2BAN_PROXIES) as $proxy) {
129
+ if (2 == count($cidr = explode('/', $proxy))) {
130
+ $net = ip2long($cidr[0]);
131
+ $mask = ~ ( pow(2, (32 - $cidr[1])) - 1 );
132
+ } else {
133
+ $net = ip2long($proxy);
134
+ $mask = -1;
135
+ }
136
+ if ($net == ($ip & $mask)) {
137
+ return (false === ($len = strpos($_SERVER['HTTP_X_FORWARDED_FOR'], ',')))
138
+ ? $_SERVER['HTTP_X_FORWARDED_FOR']
139
+ : substr($_SERVER['HTTP_X_FORWARDED_FOR'], 0, $len);
140
  }
141
  }
142
  }
 
 
143
  }
144
+
145
+ return $_SERVER['REMOTE_ADDR'];
146
+ }
147
+
148
+
149
+ /**
150
+ * @since 2.0.0
151
+ * @since 3.5.0 Refactored for unit testing
152
+ */
153
+ function authenticate($user, $username, $password)
154
+ {
155
+ if (!empty($username)) {
156
+ /**
157
+ * @since 3.5.0 Arrays allowed in PHP 7
158
+ */
159
+ $matched = (is_array(WP_FAIL2BAN_BLOCKED_USERS))
160
+ ? in_array($username, WP_FAIL2BAN_BLOCKED_USERS)
161
+ : preg_match('/'.WP_FAIL2BAN_BLOCKED_USERS.'/i', $username);
162
+
163
+ if ($matched) {
164
+ openlog();
165
+ syslog(LOG_NOTICE, "Blocked authentication attempt for {$username}");
166
+ bail();
167
+ }
168
+ }
169
+
170
+ return $user;
171
+ }
172
+ if (defined('WP_FAIL2BAN_BLOCKED_USERS')) {
173
+ add_filter('authenticate', __NAMESPACE__.'\authenticate', 1, 3);
174
+ }
175
+
176
+
177
+ /**
178
+ * @since 2.1.0
179
+ * @since 3.5.0 Refactored for unit testing
180
+ */
181
+ if (defined('WP_FAIL2BAN_BLOCK_USER_ENUMERATION') && true === WP_FAIL2BAN_BLOCK_USER_ENUMERATION) {
182
+ function parse_request($query)
183
+ {
184
+ if (intval(@$query->query_vars['author'])) {
185
+ openlog();
186
+ syslog(LOG_NOTICE, 'Blocked user enumeration attempt');
187
+ bail();
188
+ }
189
+
190
+ return $query;
191
+ }
192
+ add_filter('parse_request', __NAMESPACE__.'\parse_request', 1, 2);
193
+ }
194
+
195
+
196
+ /**
197
+ * @since 2.2.0
198
+ * @since 3.5.0 Refactored for unit testing
199
+ */
200
+ if (defined('WP_FAIL2BAN_LOG_PINGBACKS') && true === WP_FAIL2BAN_LOG_PINGBACKS) {
201
+ function xmlrpc_call($call)
202
+ {
203
+ if ('pingback.ping' == $call) {
204
+ openlog('WP_FAIL2BAN_PINGBACK_LOG');
205
+ syslog(LOG_INFO, 'Pingback requested');
206
+ }
207
+ }
208
+ add_action('xmlrpc_call', __NAMESPACE__.'\xmlrpc_call');
209
+ }
210
+
211
+
212
+ /**
213
+ * @since 3.5.0
214
+ */
215
+ if (defined('WP_FAIL2BAN_LOG_COMMENTS') && true === WP_FAIL2BAN_LOG_COMMENTS) {
216
+ function notify_post_author($maybe_notify, $comment_ID)
217
+ {
218
+ openlog('WP_FAIL2BAN_COMMENT_LOG');
219
+ syslog(LOG_INFO, "Comment {$comment_ID}");
220
+
221
+ return $maybe_notify;
222
+ }
223
+ add_filter('notify_post_author', __NAMESPACE__.'\notify_post_author', 10, 2);
224
+ }
225
+
226
+
227
+ /**
228
+ * @since 3.5.0
229
+ */
230
+ if (defined('WP_FAIL2BAN_LOG_SPAM') && true === WP_FAIL2BAN_LOG_SPAM) {
231
+ function log_spam_comment($comment_id, $comment_status)
232
+ {
233
+ if ('spam' === $comment_status) {
234
+ if (is_null($comment = get_comment($comment_id, ARRAY_A))) {
235
+ /**
236
+ * @todo: decide what to do about this
237
+ */
238
+ } else {
239
+ $remote_addr = (empty($comment['comment_author_IP']))
240
+ ? 'unknown'
241
+ : $comment['comment_author_IP'];
242
+
243
+ openlog();
244
+ syslog(LOG_INFO, "Spam comment {$comment_id}", $remote_addr);
245
+ }
246
+ }
247
+ };
248
+ add_action('comment_post', __NAMESPACE__.'\log_spam_comment', 10, 2);
249
+ add_action('wp_set_comment_status', __NAMESPACE__.'\log_spam_comment', 10, 2);
250
+ }
251
+
252
+
253
+ /**
254
+ * @since 3.5.0
255
+ */
256
+ if (defined('WP_FAIL2BAN_LOG_PASSWORD_REQUEST') && true === WP_FAIL2BAN_LOG_PASSWORD_REQUEST) {
257
+ function retrieve_password($user_login)
258
+ {
259
+ openlog();
260
+ syslog(LOG_NOTICE, "Password reset requested for {$user_login}");
261
+ }
262
+ add_action('retrieve_password', __NAMESPACE__.'\retrieve_password');
263
+ }
264
+
265
+
266
+ /**
267
+ * @since 1.0.0
268
+ * @since 3.5.0 Refactored for unit testing
269
+ */
270
+ function wp_login($user_login, $user)
271
+ {
272
+ openlog();
273
+ syslog(LOG_INFO, "Accepted password for {$user_login}");
274
+ }
275
+ add_action('wp_login', __NAMESPACE__.'\wp_login', 10, 2);
276
+
277
+
278
+ /**
279
+ * @since 1.0.0
280
+ * @since 3.5.0 Refactored for unit testing
281
+ */
282
+ function wp_login_failed($username)
283
+ {
284
+ global $wp_xmlrpc_server;
285
+
286
+ $msg = ($wp_xmlrpc_server)
287
+ ? 'XML-RPC a'
288
+ : 'A';
289
+ $msg .= (wp_cache_get($username, 'userlogins'))
290
+ ? "uthentication failure for {$username}"
291
+ : "uthentication attempt for unknown user {$username}";
292
+ openlog();
293
+ syslog(LOG_NOTICE, $msg);
294
+ }
295
+ add_action('wp_login_failed', __NAMESPACE__.'\wp_login_failed');
296
+
297
+
298
+ /**
299
+ * @since 3.0.0
300
+ * @since 3.5.0 Refactored for unit testing
301
+ */
302
+ function xmlrpc_login_error($error, $user)
303
+ {
304
+ static $attempts = 0;
305
+
306
+ if (++$attempts > 1) {
307
+ openlog();
308
+ syslog(LOG_NOTICE, 'XML-RPC multicall authentication failure');
309
+ bail();
310
+ }
311
+ }
312
+ add_action('xmlrpc_login_error', __NAMESPACE__.'\xmlrpc_login_error', 10, 2);
313
+
314
+
315
+ /**
316
+ * @since 3.0.0
317
+ * @since 3.5.0 Refactored for unit testing
318
+ */
319
+ function xmlrpc_pingback_error($ixr_error)
320
+ {
321
+ if (48 === $ixr_error->code)
322
+ return $ixr_error;
323
+ openlog();
324
+ syslog(LOG_NOTICE, 'Pingback error '.$ixr_error->code.' generated');
325
  }
326
+ add_filter('xmlrpc_pingback_error', __NAMESPACE__.'\xmlrpc_pingback_error', 5);