WP fail2ban - Version 4.1.0

Version Description

  • Add separate logging for REST authentication.
  • Fix conflict with earlier versions pre-installed in mu-plugins. See Is WPf2b Already Installed?.
Download this release

Release Info

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

Code changes from version 4.0.2 to 4.1.0

admin/summary.php CHANGED
@@ -40,7 +40,7 @@ function _doc_link($define, $name)
40
  static $wp_f2b_ver;
41
 
42
  if (empty($wp_f2b_ver)) {
43
- $wp_f2b_ver = substr(WP_FAIL2BAN, 0, strrpos(WP_FAIL2BAN, '.'));
44
  }
45
 
46
  return sprintf('<a href="https://wp-fail2ban.readthedocs.io/en/%s/defines.html#%s" target="_blank">%s <span class="dashicons dashicons-external"></span></a>', $wp_f2b_ver, str_replace('_', '-', strtolower($define)), $name);
@@ -228,48 +228,54 @@ function summary()
228
  <hr>
229
  <?php endif; ?>
230
  <?php endif; ?>
231
- <h2><?php _e('Logging'); ?></h2>
232
- <table class="form-table">
233
- <tbody>
234
- <?php
235
- _log(__('Comments'), 'WP_FAIL2BAN_LOG_COMMENTS', 'WP_FAIL2BAN_COMMENT_LOG');
236
- _log(__('Password Requests'), 'WP_FAIL2BAN_LOG_PASSWORD_REQUEST', 'WP_FAIL2BAN_PASSWORD_REQUEST_LOG');
237
- _log(__('Pingbacks'), 'WP_FAIL2BAN_LOG_PINGBACKS', 'WP_FAIL2BAN_PINGBACK_LOG');
238
- _log(__('Spam'), 'WP_FAIL2BAN_LOG_SPAM', 'WP_FAIL2BAN_SPAM_LOG');
239
- _log_options();
240
- ?>
241
- </tbody>
242
- </table>
243
- <h3><?php _e('Workarounds'); ?></h3>
244
- <table class="form-table">
245
- <tbody>
246
- <?php
247
- _yes_no_array(__('Short Tag'), 'WP_FAIL2BAN_SYSLOG_SHORT_TAG');
248
- _yes_no_array(__('Specify Host'), 'WP_FAIL2BAN_HTTP_HOST');
249
- _yes_no_array(__('Truncate Host'), 'WP_FAIL2BAN_TRUNCATE_HOST');
250
- ?>
251
- </tbody>
252
- </table>
253
- <hr>
254
- <h2><?php _e('Block'); ?></h2>
255
- <table class="form-table">
256
- <tbody>
257
- <?php
258
- _yes_no_array(__('User Enumeration'), 'WP_FAIL2BAN_BLOCK_USER_ENUMERATION');
259
- _yes_no_array(__('Usernames'), 'WP_FAIL2BAN_BLOCKED_USERS', null);
260
- ?>
261
- </tbody>
262
- </table>
263
- <hr>
264
- <h2><?php _e('Misc'); ?></h2>
265
- <table class="form-table">
266
- <tbody>
267
- <?php
268
- _yes_no_array(__('Proxies'), 'WP_FAIL2BAN_PROXIES', true);
269
- _yes_no_array(__('Remote Address'), 'WP_FAIL2BAN_REMOTE_ADDR', null);
270
- ?>
271
- </tbody>
272
- </table>
 
 
 
 
 
 
273
  </div>
274
  <?php
275
  }
40
  static $wp_f2b_ver;
41
 
42
  if (empty($wp_f2b_ver)) {
43
+ $wp_f2b_ver = substr(WP_FAIL2BAN_VER, 0, strrpos(WP_FAIL2BAN_VER, '.'));
44
  }
45
 
46
  return sprintf('<a href="https://wp-fail2ban.readthedocs.io/en/%s/defines.html#%s" target="_blank">%s <span class="dashicons dashicons-external"></span></a>', $wp_f2b_ver, str_replace('_', '-', strtolower($define)), $name);
228
  <hr>
229
  <?php endif; ?>
230
  <?php endif; ?>
231
+ <div class="card">
232
+ <h2 class="title"><?php _e('Logging'); ?></h2>
233
+ <table class="form-table">
234
+ <tbody>
235
+ <?php
236
+ _log(__('Comments'), 'WP_FAIL2BAN_LOG_COMMENTS', 'WP_FAIL2BAN_COMMENT_LOG');
237
+ _log(__('Password Requests'), 'WP_FAIL2BAN_LOG_PASSWORD_REQUEST', 'WP_FAIL2BAN_PASSWORD_REQUEST_LOG');
238
+ _log(__('Pingbacks'), 'WP_FAIL2BAN_LOG_PINGBACKS', 'WP_FAIL2BAN_PINGBACK_LOG');
239
+ _log(__('Spam'), 'WP_FAIL2BAN_LOG_SPAM', 'WP_FAIL2BAN_SPAM_LOG');
240
+ _log_options();
241
+ ?>
242
+ </tbody>
243
+ </table>
244
+ </div>
245
+ <div class="card">
246
+ <h2 class="title"><?php _e('Workarounds'); ?></h2>
247
+ <table class="form-table">
248
+ <tbody>
249
+ <?php
250
+ _yes_no_array(__('Short Tag'), 'WP_FAIL2BAN_SYSLOG_SHORT_TAG');
251
+ _yes_no_array(__('Specify Host'), 'WP_FAIL2BAN_HTTP_HOST');
252
+ _yes_no_array(__('Truncate Host'), 'WP_FAIL2BAN_TRUNCATE_HOST');
253
+ ?>
254
+ </tbody>
255
+ </table>
256
+ </div>
257
+ <div class="card">
258
+ <h2 class="title"><?php _e('Block'); ?></h2>
259
+ <table class="form-table">
260
+ <tbody>
261
+ <?php
262
+ _yes_no_array(__('User Enumeration'), 'WP_FAIL2BAN_BLOCK_USER_ENUMERATION');
263
+ _yes_no_array(__('Usernames'), 'WP_FAIL2BAN_BLOCKED_USERS', null);
264
+ ?>
265
+ </tbody>
266
+ </table>
267
+ </div>
268
+ <div class="card">
269
+ <h2 class="title"><?php _e('Misc'); ?></h2>
270
+ <table class="form-table">
271
+ <tbody>
272
+ <?php
273
+ _yes_no_array(__('Proxies'), 'WP_FAIL2BAN_PROXIES', true);
274
+ _yes_no_array(__('Remote Address'), 'WP_FAIL2BAN_REMOTE_ADDR', null);
275
+ ?>
276
+ </tbody>
277
+ </table>
278
+ </div>
279
  </div>
280
  <?php
281
  }
feature/comments.php CHANGED
@@ -12,144 +12,176 @@ if ( !defined( 'ABSPATH' ) ) {
12
  exit;
13
  }
14
  /**
15
- * Log new comment
16
- *
17
- * @since 3.5.0
18
- *
19
- * @param bool $maybe_notify
20
- * @param int $comment_ID
21
- *
22
- * @return bool
23
- *
24
- * @wp-f2b-extra Comment \d+
25
  */
26
- function notify_post_author( $maybe_notify, $comment_ID )
27
- {
28
- openlog( 'WP_FAIL2BAN_COMMENT_LOG' );
29
- syslog( LOG_INFO, "Comment {$comment_ID}" );
30
- // @codeCoverageIgnoreEnd
31
- return $maybe_notify;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  }
33
 
34
- add_filter(
35
- 'notify_post_author',
36
- __NAMESPACE__ . '\\notify_post_author',
37
- 10,
38
- 2
39
- );
40
 
41
  if ( defined( 'WP_FAIL2BAN_LOG_COMMENTS_EXTRA' ) ) {
42
  /** WPF2B_ACTION_COMMENT_NOT_FOUND */
43
-
44
  if ( WP_FAIL2BAN_LOG_COMMENTS_EXTRA & 0x20002 ) {
45
  /**
46
- * Log attempted comment on non-existent post
47
- *
48
- * @since 4.0.0
49
- *
50
- * @param int $comment_post_ID
51
- *
52
- * @wp-f2b-extra Comment post not found \d+
53
  */
54
- function comment_id_not_found( $comment_post_ID )
55
- {
56
- openlog( 'WP_FAIL2BAN_COMMENT_LOG' );
57
- syslog( LOG_INFO, "Comment post not found {$comment_post_ID}" );
58
- // @codeCoverageIgnoreEnd
59
- }
60
 
61
- add_action( 'comment_id_not_found', __NAMESPACE__ . '\\comment_id_not_found' );
62
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
 
64
  /** LOG_ACTION_LOG_COMMENT_CLOSED */
65
-
66
  if ( WP_FAIL2BAN_LOG_COMMENTS_EXTRA & 0x20004 ) {
67
  /**
68
- * Log attempted comment on closed post
69
- *
70
- * @since 4.0.0
71
- *
72
- * @param int $comment_post_ID
73
- *
74
- * @wp-f2b-extra Comments closed on post \d+
75
  */
76
- function comment_closed( $comment_post_ID )
77
- {
78
- openlog( 'WP_FAIL2BAN_COMMENT_LOG' );
79
- syslog( LOG_INFO, "Comments closed on post {$comment_post_ID}" );
80
- // @codeCoverageIgnoreEnd
81
- }
82
 
83
- add_action( 'comment_closed', __NAMESPACE__ . '\\comment_closed' );
84
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
 
 
86
  /** LOG_ACTION_LOG_COMMENT_TRASH */
87
-
88
  if ( WP_FAIL2BAN_LOG_COMMENTS_EXTRA & 0x20008 ) {
89
  /**
90
- * Log attempted comment on trashed post
91
- *
92
- * @since 4.0.2 Fix message
93
- * @since 4.0.0
94
- *
95
- * @param int $comment_post_ID
96
- *
97
- * @wp-f2b-extra Comment attempt on trash post \d+
98
  */
99
- function comment_on_trash( $comment_post_ID )
100
- {
101
- openlog( 'WP_FAIL2BAN_COMMENT_LOG' );
102
- syslog( LOG_INFO, "Comment attempt on trash post {$comment_post_ID}" );
103
- // @codeCoverageIgnoreEnd
104
- }
105
 
106
- add_action( 'comment_on_trash', __NAMESPACE__ . '\\comment_on_trash' );
107
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
 
 
109
  /** LOG_ACTION_LOG_COMMENT_DRAFT */
110
-
111
  if ( WP_FAIL2BAN_LOG_COMMENTS_EXTRA & 0x20010 ) {
112
  /**
113
- * Log attempted comment on draft post
114
- *
115
- * @since 4.0.2 Fix message
116
- * @since 4.0.0
117
- *
118
- * @param int $comment_post_ID
119
- *
120
- * @wp-f2b-extra Comment attempt on draft post \d+
121
  */
122
- function comment_on_draft( $comment_post_ID )
123
- {
124
- openlog( 'WP_FAIL2BAN_COMMENT_LOG' );
125
- syslog( LOG_INFO, "Comment attempt on draft post {$comment_post_ID}" );
126
- // @codeCoverageIgnoreEnd
127
- }
128
 
129
- add_action( 'comment_on_draft', __NAMESPACE__ . '\\comment_on_draft' );
130
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131
 
 
132
  /** LOG_ACTION_LOG_COMMENT_PASSWORD */
133
-
134
  if ( WP_FAIL2BAN_LOG_COMMENTS_EXTRA & 0x20020 ) {
135
  /**
136
- * Log attempted comment on password-protected post
137
- *
138
- * @since 4.0.2 Fix message
139
- * @since 4.0.0
140
- *
141
- * @param int $comment_post_ID
142
- *
143
- * @wp-f2b-extra Comment attempt on password-protected post \d+
144
  */
145
- function comment_on_password_protected( $comment_post_ID )
146
- {
147
- openlog( 'WP_FAIL2BAN_COMMENT_LOG' );
148
- syslog( LOG_INFO, "Comment attempt on password-protected post {$comment_post_ID}" );
149
- // @codeCoverageIgnoreEnd
150
- }
151
 
152
- add_action( 'comment_on_password_protected', __NAMESPACE__ . '\\comment_on_password_protected' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
  }
154
-
155
  }
12
  exit;
13
  }
14
  /**
15
+ * @since 4.0.5 Guard
 
 
 
 
 
 
 
 
 
16
  */
17
+
18
+ if ( !function_exists( __NAMESPACE__ . '\\notify_post_author' ) ) {
19
+ /**
20
+ * Log new comment
21
+ *
22
+ * @since 3.5.0
23
+ *
24
+ * @param bool $maybe_notify
25
+ * @param int $comment_ID
26
+ *
27
+ * @return bool
28
+ *
29
+ * @wp-f2b-extra Comment \d+
30
+ */
31
+ function notify_post_author( $maybe_notify, $comment_ID )
32
+ {
33
+ openlog( 'WP_FAIL2BAN_COMMENT_LOG' );
34
+ syslog( LOG_INFO, "Comment {$comment_ID}" );
35
+ // @codeCoverageIgnoreEnd
36
+ return $maybe_notify;
37
+ }
38
+
39
+ add_filter(
40
+ 'notify_post_author',
41
+ __NAMESPACE__ . '\\notify_post_author',
42
+ 10,
43
+ 2
44
+ );
45
  }
46
 
 
 
 
 
 
 
47
 
48
  if ( defined( 'WP_FAIL2BAN_LOG_COMMENTS_EXTRA' ) ) {
49
  /** WPF2B_ACTION_COMMENT_NOT_FOUND */
 
50
  if ( WP_FAIL2BAN_LOG_COMMENTS_EXTRA & 0x20002 ) {
51
  /**
52
+ * @since 4.0.5 Guard
 
 
 
 
 
 
53
  */
 
 
 
 
 
 
54
 
55
+ if ( !function_exists( __NAMESPACE__ . '\\comment_id_not_found' ) ) {
56
+ /**
57
+ * Log attempted comment on non-existent post
58
+ *
59
+ * @since 4.0.0
60
+ *
61
+ * @param int $comment_post_ID
62
+ *
63
+ * @wp-f2b-extra Comment post not found \d+
64
+ */
65
+ function comment_id_not_found( $comment_post_ID )
66
+ {
67
+ openlog( 'WP_FAIL2BAN_COMMENT_LOG' );
68
+ syslog( LOG_NOTICE, "Comment post not found {$comment_post_ID}" );
69
+ // @codeCoverageIgnoreEnd
70
+ }
71
+
72
+ add_action( 'comment_id_not_found', __NAMESPACE__ . '\\comment_id_not_found' );
73
+ }
74
 
75
+ }
76
  /** LOG_ACTION_LOG_COMMENT_CLOSED */
 
77
  if ( WP_FAIL2BAN_LOG_COMMENTS_EXTRA & 0x20004 ) {
78
  /**
79
+ * @since 4.0.5 Guard
 
 
 
 
 
 
80
  */
 
 
 
 
 
 
81
 
82
+ if ( !function_exists( __NAMESPACE__ . '\\comment_closed' ) ) {
83
+ /**
84
+ * Log attempted comment on closed post
85
+ *
86
+ * @since 4.0.0
87
+ *
88
+ * @param int $comment_post_ID
89
+ *
90
+ * @wp-f2b-extra Comments closed on post \d+
91
+ */
92
+ function comment_closed( $comment_post_ID )
93
+ {
94
+ openlog( 'WP_FAIL2BAN_COMMENT_LOG' );
95
+ syslog( LOG_NOTICE, "Comments closed on post {$comment_post_ID}" );
96
+ // @codeCoverageIgnoreEnd
97
+ }
98
+
99
+ add_action( 'comment_closed', __NAMESPACE__ . '\\comment_closed' );
100
+ }
101
 
102
+ }
103
  /** LOG_ACTION_LOG_COMMENT_TRASH */
 
104
  if ( WP_FAIL2BAN_LOG_COMMENTS_EXTRA & 0x20008 ) {
105
  /**
106
+ * @since 4.0.5 Guard
 
 
 
 
 
 
 
107
  */
 
 
 
 
 
 
108
 
109
+ if ( !function_exists( __NAMESPACE__ . '\\comment_on_trash' ) ) {
110
+ /**
111
+ * Log attempted comment on trashed post
112
+ *
113
+ * @since 4.0.2 Fix message
114
+ * @since 4.0.0
115
+ *
116
+ * @param int $comment_post_ID
117
+ *
118
+ * @wp-f2b-extra Comment attempt on trash post \d+
119
+ */
120
+ function comment_on_trash( $comment_post_ID )
121
+ {
122
+ openlog( 'WP_FAIL2BAN_COMMENT_LOG' );
123
+ syslog( LOG_NOTICE, "Comment attempt on trash post {$comment_post_ID}" );
124
+ // @codeCoverageIgnoreEnd
125
+ }
126
+
127
+ add_action( 'comment_on_trash', __NAMESPACE__ . '\\comment_on_trash' );
128
+ }
129
 
130
+ }
131
  /** LOG_ACTION_LOG_COMMENT_DRAFT */
 
132
  if ( WP_FAIL2BAN_LOG_COMMENTS_EXTRA & 0x20010 ) {
133
  /**
134
+ * @since 4.0.5 Guard
 
 
 
 
 
 
 
135
  */
 
 
 
 
 
 
136
 
137
+ if ( !function_exists( __NAMESPACE__ . '\\comment_on_draft' ) ) {
138
+ /**
139
+ * Log attempted comment on draft post
140
+ *
141
+ * @since 4.0.2 Fix message
142
+ * @since 4.0.0
143
+ *
144
+ * @param int $comment_post_ID
145
+ *
146
+ * @wp-f2b-extra Comment attempt on draft post \d+
147
+ */
148
+ function comment_on_draft( $comment_post_ID )
149
+ {
150
+ openlog( 'WP_FAIL2BAN_COMMENT_LOG' );
151
+ syslog( LOG_NOTICE, "Comment attempt on draft post {$comment_post_ID}" );
152
+ // @codeCoverageIgnoreEnd
153
+ }
154
+
155
+ add_action( 'comment_on_draft', __NAMESPACE__ . '\\comment_on_draft' );
156
+ }
157
 
158
+ }
159
  /** LOG_ACTION_LOG_COMMENT_PASSWORD */
 
160
  if ( WP_FAIL2BAN_LOG_COMMENTS_EXTRA & 0x20020 ) {
161
  /**
162
+ * @since 4.0.5 Guard
 
 
 
 
 
 
 
163
  */
 
 
 
 
 
 
164
 
165
+ if ( !function_exists( __NAMESPACE__ . '\\comment_on_password_protected' ) ) {
166
+ /**
167
+ * Log attempted comment on password-protected post
168
+ *
169
+ * @since 4.0.2 Fix message
170
+ * @since 4.0.0
171
+ *
172
+ * @param int $comment_post_ID
173
+ *
174
+ * @wp-f2b-extra Comment attempt on password-protected post \d+
175
+ */
176
+ function comment_on_password_protected( $comment_post_ID )
177
+ {
178
+ openlog( 'WP_FAIL2BAN_COMMENT_LOG' );
179
+ syslog( LOG_NOTICE, "Comment attempt on password-protected post {$comment_post_ID}" );
180
+ // @codeCoverageIgnoreEnd
181
+ }
182
+
183
+ add_action( 'comment_on_password_protected', __NAMESPACE__ . '\\comment_on_password_protected' );
184
+ }
185
+
186
  }
 
187
  }
feature/lib.php CHANGED
@@ -71,13 +71,20 @@ function syslog( $level, $msg, $remote_addr = null )
71
  /**
72
  * Graceful immediate exit
73
  *
 
74
  * @since 3.5.0 Refactored for unit testing
75
  */
76
- function bail()
77
  {
78
- wp_die( 'Forbidden', 'Forbidden', array(
79
- 'response' => 403,
80
- ) );
 
 
 
 
 
 
81
  }
82
 
83
  /**
71
  /**
72
  * Graceful immediate exit
73
  *
74
+ * @since 4.0.5 Add JSON support
75
  * @since 3.5.0 Refactored for unit testing
76
  */
77
+ function bail( $is_json = false )
78
  {
79
+
80
+ if ( $is_json ) {
81
+ return new \WP_Error( 403, 'Forbidden' );
82
+ } else {
83
+ wp_die( 'Forbidden', 'Forbidden', array(
84
+ 'response' => 403,
85
+ ) );
86
+ }
87
+
88
  }
89
 
90
  /**
feature/password.php CHANGED
@@ -12,19 +12,25 @@ if ( !defined( 'ABSPATH' ) ) {
12
  exit;
13
  }
14
  /**
15
- * Log password reset requests
16
- *
17
- * @since 3.5.0
18
- *
19
- * @param string $user_login
20
- *
21
- * @wp-f2b-extra Password reset requested for .*
22
  */
23
- function retrieve_password( $user_login )
24
- {
25
- openlog( 'WP_FAIL2BAN_PASSWORD_REQUEST_LOG' );
26
- syslog( LOG_NOTICE, "Password reset requested for {$user_login}" );
27
- // @codeCoverageIgnoreEnd
28
- }
29
 
30
- add_action( 'retrieve_password', __NAMESPACE__ . '\\retrieve_password' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  exit;
13
  }
14
  /**
15
+ * @since 4.0.5
 
 
 
 
 
 
16
  */
 
 
 
 
 
 
17
 
18
+ if ( !function_exists( __NAMESPACE__ . '\\retrieve_password' ) ) {
19
+ /**
20
+ * Log password reset requests
21
+ *
22
+ * @since 3.5.0
23
+ *
24
+ * @param string $user_login
25
+ *
26
+ * @wp-f2b-extra Password reset requested for .*
27
+ */
28
+ function retrieve_password( $user_login )
29
+ {
30
+ openlog( 'WP_FAIL2BAN_PASSWORD_REQUEST_LOG' );
31
+ syslog( LOG_NOTICE, "Password reset requested for {$user_login}" );
32
+ // @codeCoverageIgnoreEnd
33
+ }
34
+
35
+ add_action( 'retrieve_password', __NAMESPACE__ . '\\retrieve_password' );
36
+ }
feature/spam.php CHANGED
@@ -12,42 +12,48 @@ if ( !defined( 'ABSPATH' ) ) {
12
  exit;
13
  }
14
  /**
15
- * Catch comments marked as spam
16
- *
17
- * @since 3.5.0
18
- *
19
- * @param int $comment_id
20
- * @param string $comment_status
21
- *
22
- * @wp-f2b-hard Spam comment \d+
23
  */
24
- function log_spam_comment( $comment_id, $comment_status )
25
- {
26
- if ( 'spam' === $comment_status ) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
 
28
- if ( is_null( $comment = get_comment( $comment_id, ARRAY_A ) ) ) {
29
- /**
30
- * @todo: decide what to do about this
31
- */
32
- } else {
33
- $remote_addr = ( empty($comment['comment_author_IP']) ? 'unknown' : $comment['comment_author_IP'] );
34
- openlog( 'WP_FAIL2BAN_SPAM_LOG' );
35
- syslog( LOG_INFO, "Spam comment {$comment_id}", $remote_addr );
36
- // @codeCoverageIgnoreEnd
37
  }
38
-
39
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  }
41
-
42
- add_action(
43
- 'comment_post',
44
- __NAMESPACE__ . '\\log_spam_comment',
45
- 10,
46
- 2
47
- );
48
- add_action(
49
- 'wp_set_comment_status',
50
- __NAMESPACE__ . '\\log_spam_comment',
51
- 10,
52
- 2
53
- );
12
  exit;
13
  }
14
  /**
15
+ * @since 4.0.5
 
 
 
 
 
 
 
16
  */
17
+
18
+ if ( !function_exists( __NAMESPACE__ . '\\log_spam_comment' ) ) {
19
+ /**
20
+ * Catch comments marked as spam
21
+ *
22
+ * @since 3.5.0
23
+ *
24
+ * @param int $comment_id
25
+ * @param string $comment_status
26
+ *
27
+ * @wp-f2b-hard Spam comment \d+
28
+ */
29
+ function log_spam_comment( $comment_id, $comment_status )
30
+ {
31
+ if ( 'spam' === $comment_status ) {
32
+
33
+ if ( is_null( $comment = get_comment( $comment_id, ARRAY_A ) ) ) {
34
+ /**
35
+ * @todo: decide what to do about this
36
+ */
37
+ } else {
38
+ $remote_addr = ( empty($comment['comment_author_IP']) ? 'unknown' : $comment['comment_author_IP'] );
39
+ openlog( 'WP_FAIL2BAN_SPAM_LOG' );
40
+ syslog( LOG_NOTICE, "Spam comment {$comment_id}", $remote_addr );
41
+ // @codeCoverageIgnoreEnd
42
+ }
43
 
 
 
 
 
 
 
 
 
 
44
  }
 
45
  }
46
+
47
+ add_action(
48
+ 'comment_post',
49
+ __NAMESPACE__ . '\\log_spam_comment',
50
+ 10,
51
+ 2
52
+ );
53
+ add_action(
54
+ 'wp_set_comment_status',
55
+ __NAMESPACE__ . '\\log_spam_comment',
56
+ 10,
57
+ 2
58
+ );
59
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
feature/user-enum.php CHANGED
@@ -12,69 +12,92 @@ if ( !defined( 'ABSPATH' ) ) {
12
  exit;
13
  }
14
  /**
15
- * Common enumeration handling
16
- *
17
- * @since 4.0.0
18
- *
19
- * @wp-f2b-hard Blocked user enumeration attempt
20
  */
21
- function _log_bail_user_enum()
22
- {
23
- openlog();
24
- syslog( LOG_NOTICE, 'Blocked user enumeration attempt' );
25
- // @codeCoverageIgnoreEnd
26
- bail();
27
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
28
 
 
29
  /**
30
- * Catch traditional user enum
31
- *
32
- * @see \WP::parse_request()
33
- *
34
- * @since 3.5.0 Refactored for unit testing
35
- * @since 2.1.0
36
- *
37
- * @param \WP $query
38
- *
39
- * @return \WP
40
  */
41
- function parse_request( $query )
42
- {
43
- if ( !current_user_can( 'list_users' ) && intval( @$query->query_vars['author'] ) ) {
44
- _log_bail_user_enum();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  }
46
- return $query;
 
 
 
 
 
 
47
  }
48
 
49
- add_filter(
50
- 'parse_request',
51
- __NAMESPACE__ . '\\parse_request',
52
- 1,
53
- 2
54
- );
55
  /**
56
- * Catch RESTful user enum
57
- *
58
- * @see \WP_REST_Users_Controller::get_items()
59
- *
60
- * @since 4.0.0
61
- *
62
- * @param array $prepared_args
63
- * @param \WP_REST_Request $request
64
- *
65
- * @return array
66
  */
67
- function rest_user_query( $prepared_args, $request )
68
- {
69
- if ( !current_user_can( 'list_users' ) ) {
70
- _log_bail_user_enum();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
  }
72
- return $prepared_args;
 
 
 
 
 
 
73
  }
74
-
75
- add_filter(
76
- 'rest_user_query',
77
- __NAMESPACE__ . '\\rest_user_query',
78
- 10,
79
- 2
80
- );
12
  exit;
13
  }
14
  /**
15
+ * @since 4.0.5 Guard
 
 
 
 
16
  */
17
+ if ( !function_exists( __NAMESPACE__ . '\\_log_bail_user_enum' ) ) {
18
+ /**
19
+ * Common enumeration handling
20
+ *
21
+ * @since 4.1.0 Add JSON support
22
+ * @since 4.0.0
23
+ *
24
+ * @param bool $is_json
25
+ *
26
+ * @return \WP_Error
27
+ *
28
+ * @wp-f2b-hard Blocked user enumeration attempt
29
+ */
30
+ function _log_bail_user_enum( $is_json = false )
31
+ {
32
+ openlog();
33
+ syslog( LOG_NOTICE, 'Blocked user enumeration attempt' );
34
+ // @codeCoverageIgnoreEnd
35
+ return bail( $is_json );
36
+ }
37
 
38
+ }
39
  /**
40
+ * @since 4.0.5 Guard
 
 
 
 
 
 
 
 
 
41
  */
42
+
43
+ if ( !function_exists( __NAMESPACE__ . '\\parse_request' ) ) {
44
+ /**
45
+ * Catch traditional user enum
46
+ *
47
+ * @see \WP::parse_request()
48
+ *
49
+ * @since 3.5.0 Refactored for unit testing
50
+ * @since 2.1.0
51
+ *
52
+ * @param \WP $query
53
+ *
54
+ * @return \WP
55
+ */
56
+ function parse_request( $query )
57
+ {
58
+ if ( !current_user_can( 'list_users' ) && intval( @$query->query_vars['author'] ) ) {
59
+ _log_bail_user_enum();
60
+ }
61
+ return $query;
62
  }
63
+
64
+ add_filter(
65
+ 'parse_request',
66
+ __NAMESPACE__ . '\\parse_request',
67
+ 1,
68
+ 2
69
+ );
70
  }
71
 
 
 
 
 
 
 
72
  /**
73
+ * @since 4.0.5 Guard
 
 
 
 
 
 
 
 
 
74
  */
75
+
76
+ if ( !function_exists( __NAMESPACE__ . '\\rest_user_query' ) ) {
77
+ /**
78
+ * Catch RESTful user list
79
+ *
80
+ * @see \WP_REST_Users_Controller::get_items()
81
+ *
82
+ * @since 4.0.0
83
+ *
84
+ * @param array $prepared_args
85
+ * @param \WP_REST_Request $request
86
+ *
87
+ * @return array|\WP_Error
88
+ */
89
+ function rest_user_query( $prepared_args, $request )
90
+ {
91
+ if ( !current_user_can( 'list_users' ) ) {
92
+ return _log_bail_user_enum( true );
93
+ }
94
+ return $prepared_args;
95
  }
96
+
97
+ add_filter(
98
+ 'rest_user_query',
99
+ __NAMESPACE__ . '\\rest_user_query',
100
+ 10,
101
+ 2
102
+ );
103
  }
 
 
 
 
 
 
 
feature/user.php CHANGED
@@ -12,43 +12,49 @@ if ( !defined( 'ABSPATH' ) ) {
12
  exit;
13
  }
14
  /**
15
- * Catched blocked users
16
- *
17
- * @since 3.5.0 Refactored for unit testing
18
- * @since 2.0.0
19
- *
20
- * @param mixed|null $user
21
- * @param string $username
22
- * @param string $password
23
- *
24
- * @return mixed|null
25
- *
26
- * @wp-f2b-hard Blocked authentication attempt for .*
27
  */
28
- function authenticate( $user, $username, $password )
29
- {
30
-
31
- if ( !empty($username) ) {
32
- /**
33
- * @since 3.5.0 Arrays allowed in PHP 7
34
- */
35
- $matched = ( is_array( WP_FAIL2BAN_BLOCKED_USERS ) ? in_array( $username, WP_FAIL2BAN_BLOCKED_USERS ) : preg_match( '/' . WP_FAIL2BAN_BLOCKED_USERS . '/i', $username ) );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
 
37
- if ( $matched ) {
38
- openlog();
39
- syslog( LOG_NOTICE, "Blocked authentication attempt for {$username}" );
40
- // @codeCoverageIgnoreEnd
41
- bail();
42
  }
43
-
 
44
  }
45
 
46
- return $user;
 
 
 
 
 
47
  }
48
-
49
- add_filter(
50
- 'authenticate',
51
- __NAMESPACE__ . '\\authenticate',
52
- 1,
53
- 3
54
- );
12
  exit;
13
  }
14
  /**
15
+ * @since 4.0.5 Guard
 
 
 
 
 
 
 
 
 
 
 
16
  */
17
+
18
+ if ( !function_exists( __NAMESPACE__ . '\\authenticate' ) ) {
19
+ /**
20
+ * Catched blocked users
21
+ *
22
+ * @since 3.5.0 Refactored for unit testing
23
+ * @since 2.0.0
24
+ *
25
+ * @param mixed|null $user
26
+ * @param string $username
27
+ * @param string $password
28
+ *
29
+ * @return mixed|null
30
+ *
31
+ * @wp-f2b-hard Blocked authentication attempt for .*
32
+ */
33
+ function authenticate( $user, $username, $password )
34
+ {
35
+
36
+ if ( !empty($username) ) {
37
+ /**
38
+ * @since 3.5.0 Arrays allowed in PHP 7
39
+ */
40
+ $matched = ( is_array( WP_FAIL2BAN_BLOCKED_USERS ) ? in_array( $username, WP_FAIL2BAN_BLOCKED_USERS ) : preg_match( '/' . WP_FAIL2BAN_BLOCKED_USERS . '/i', $username ) );
41
+
42
+ if ( $matched ) {
43
+ openlog();
44
+ syslog( LOG_NOTICE, "Blocked authentication attempt for {$username}" );
45
+ // @codeCoverageIgnoreEnd
46
+ bail();
47
+ }
48
 
 
 
 
 
 
49
  }
50
+
51
+ return $user;
52
  }
53
 
54
+ add_filter(
55
+ 'authenticate',
56
+ __NAMESPACE__ . '\\authenticate',
57
+ 1,
58
+ 3
59
+ );
60
  }
 
 
 
 
 
 
 
feature/xmlrpc.php CHANGED
@@ -12,70 +12,84 @@ if ( !defined( 'ABSPATH' ) ) {
12
  exit;
13
  }
14
  /**
15
- * Catch multiple XML-RPC authentication failures
16
- *
17
- * @see \wp_xmlrpc_server::login()
18
- *
19
- * @since 4.0.0 Return $error
20
- * @since 3.5.0 Refactored for unit testing
21
- * @since 3.0.0
22
- *
23
- * @param \IXR_Error $error
24
- * @param \WP_Error $user
25
- *
26
- * @return \IXR_Error
27
- *
28
- * @wp-f2b-hard XML-RPC multicall authentication failure
29
  */
30
- function xmlrpc_login_error( $error, $user )
31
- {
32
- static $attempts = 0 ;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
34
- if ( ++$attempts > 1 ) {
35
- openlog();
36
- syslog( LOG_NOTICE, 'XML-RPC multicall authentication failure' );
37
- // @codeCoverageIgnoreEnd
38
- bail();
39
- } else {
40
- return $error;
41
  }
42
-
 
 
 
 
 
 
43
  }
44
 
45
- add_action(
46
- 'xmlrpc_login_error',
47
- __NAMESPACE__ . '\\xmlrpc_login_error',
48
- 10,
49
- 2
50
- );
51
  /**
52
- * Catch failed pingbacks
53
- *
54
- * @see \wp_xmlrpc_server::pingback_error()
55
- *
56
- * @since 4.0.0 Return $ixr_error
57
- * @since 3.5.0 Refactored for unit testing
58
- * @since 3.0.0
59
- *
60
- * @param \IXR_Error $ixr_error
61
- *
62
- * @return \IXR_Error
63
- *
64
- * @wp-f2b-hard Pingback error .* generated
65
  */
66
- function xmlrpc_pingback_error( $ixr_error )
67
- {
68
-
69
- if ( 48 !== $ixr_error->code ) {
70
- openlog();
71
- syslog( LOG_NOTICE, 'Pingback error ' . $ixr_error->code . ' generated' );
72
- // @codeCoverageIgnoreEnd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  }
74
 
75
- return $ixr_error;
76
  }
77
 
78
- add_filter( 'xmlrpc_pingback_error', __NAMESPACE__ . '\\xmlrpc_pingback_error', 5 );
79
  /**
80
  * @since 4.0.0 Refactored
81
  * @since 2.2.0
12
  exit;
13
  }
14
  /**
15
+ * @since 4.0.5 Guard
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  */
17
+
18
+ if ( !function_exists( __NAMESPACE__ . '\\xmlrpc_login_error' ) ) {
19
+ /**
20
+ * Catch multiple XML-RPC authentication failures
21
+ *
22
+ * @see \wp_xmlrpc_server::login()
23
+ *
24
+ * @since 4.0.0 Return $error
25
+ * @since 3.5.0 Refactored for unit testing
26
+ * @since 3.0.0
27
+ *
28
+ * @param \IXR_Error $error
29
+ * @param \WP_Error $user
30
+ *
31
+ * @return \IXR_Error
32
+ *
33
+ * @wp-f2b-hard XML-RPC multicall authentication failure
34
+ */
35
+ function xmlrpc_login_error( $error, $user )
36
+ {
37
+ static $attempts = 0 ;
38
+
39
+ if ( ++$attempts > 1 ) {
40
+ openlog();
41
+ syslog( LOG_NOTICE, 'XML-RPC multicall authentication failure' );
42
+ // @codeCoverageIgnoreEnd
43
+ bail();
44
+ } else {
45
+ return $error;
46
+ }
47
 
 
 
 
 
 
 
 
48
  }
49
+
50
+ add_action(
51
+ 'xmlrpc_login_error',
52
+ __NAMESPACE__ . '\\xmlrpc_login_error',
53
+ 10,
54
+ 2
55
+ );
56
  }
57
 
 
 
 
 
 
 
58
  /**
59
+ * @since 4.0.5 Guard
 
 
 
 
 
 
 
 
 
 
 
 
60
  */
61
+
62
+ if ( !function_exists( __NAMESPACE__ . '\\xmlrpc_pingback_error' ) ) {
63
+ /**
64
+ * Catch failed pingbacks
65
+ *
66
+ * @see \wp_xmlrpc_server::pingback_error()
67
+ *
68
+ * @since 4.0.0 Return $ixr_error
69
+ * @since 3.5.0 Refactored for unit testing
70
+ * @since 3.0.0
71
+ *
72
+ * @param \IXR_Error $ixr_error
73
+ *
74
+ * @return \IXR_Error
75
+ *
76
+ * @wp-f2b-hard Pingback error .* generated
77
+ */
78
+ function xmlrpc_pingback_error( $ixr_error )
79
+ {
80
+
81
+ if ( 48 !== $ixr_error->code ) {
82
+ openlog();
83
+ syslog( LOG_NOTICE, 'Pingback error ' . $ixr_error->code . ' generated' );
84
+ // @codeCoverageIgnoreEnd
85
+ }
86
+
87
+ return $ixr_error;
88
  }
89
 
90
+ add_filter( 'xmlrpc_pingback_error', __NAMESPACE__ . '\\xmlrpc_pingback_error', 5 );
91
  }
92
 
 
93
  /**
94
  * @since 4.0.0 Refactored
95
  * @since 2.2.0
feature/xmlrpc/pingback.php CHANGED
@@ -12,22 +12,28 @@ if ( !defined( 'ABSPATH' ) ) {
12
  exit;
13
  }
14
  /**
15
- * Log pingbacks
16
- *
17
- * @since 3.5.0 Refactored for unit testing
18
- * @since 2.2.0
19
- *
20
- * @param string $call
21
  */
22
- function xmlrpc_call( $call )
23
- {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
25
- if ( 'pingback.ping' == $call ) {
26
- openlog( 'WP_FAIL2BAN_PINGBACK_LOG' );
27
- syslog( LOG_INFO, 'Pingback requested' );
28
- // @codeCoverageIgnoreEnd
29
  }
30
-
 
31
  }
32
-
33
- add_action( 'xmlrpc_call', __NAMESPACE__ . '\\xmlrpc_call' );
12
  exit;
13
  }
14
  /**
15
+ * @since 4.0.5 Guard
 
 
 
 
 
16
  */
17
+
18
+ if ( !function_exists( __NAMESPACE__ . '\\xmlrpc_call' ) ) {
19
+ /**
20
+ * Log pingbacks
21
+ *
22
+ * @since 3.5.0 Refactored for unit testing
23
+ * @since 2.2.0
24
+ *
25
+ * @param string $call
26
+ */
27
+ function xmlrpc_call( $call )
28
+ {
29
+
30
+ if ( 'pingback.ping' == $call ) {
31
+ openlog( 'WP_FAIL2BAN_PINGBACK_LOG' );
32
+ syslog( LOG_INFO, 'Pingback requested' );
33
+ // @codeCoverageIgnoreEnd
34
+ }
35
 
 
 
 
 
36
  }
37
+
38
+ add_action( 'xmlrpc_call', __NAMESPACE__ . '\\xmlrpc_call' );
39
  }
 
 
filters.d/wordpress-extra.conf CHANGED
@@ -1,5 +1,5 @@
1
  # Fail2Ban filter for WordPress extra failures
2
- # Auto-generated: 2019-01-24T14:41:41+00:00
3
  #
4
 
5
  [INCLUDES]
1
  # Fail2Ban filter for WordPress extra failures
2
+ # Auto-generated: 2019-03-13T01:12:18+00:00
3
  #
4
 
5
  [INCLUDES]
filters.d/wordpress-hard.conf CHANGED
@@ -1,5 +1,5 @@
1
  # Fail2Ban filter for WordPress hard failures
2
- # Auto-generated: 2019-01-24T14:41:41+00:00
3
  #
4
 
5
  [INCLUDES]
@@ -11,14 +11,13 @@ before = common.conf
11
  _daemon = (?:wordpress|wp)
12
 
13
  failregex = ^%(__prefix_line)sAuthentication attempt for unknown user .* from <HOST>$
 
14
  ^%(__prefix_line)sXML-RPC authentication attempt for unknown user .* from <HOST>$
15
  ^%(__prefix_line)sSpam comment \d+ from <HOST>$
16
  ^%(__prefix_line)sBlocked user enumeration attempt from <HOST>$
17
  ^%(__prefix_line)sBlocked authentication attempt for .* from <HOST>$
18
  ^%(__prefix_line)sXML-RPC multicall authentication failure from <HOST>$
19
  ^%(__prefix_line)sPingback error .* generated from <HOST>$
20
- ^%(__prefix_line)sXML-RPC multicall authentication failure from <HOST>$
21
- ^%(__prefix_line)sPingback error .* generated from <HOST>$
22
 
23
  ignoreregex =
24
 
1
  # Fail2Ban filter for WordPress hard failures
2
+ # Auto-generated: 2019-03-13T01:12:18+00:00
3
  #
4
 
5
  [INCLUDES]
11
  _daemon = (?:wordpress|wp)
12
 
13
  failregex = ^%(__prefix_line)sAuthentication attempt for unknown user .* from <HOST>$
14
+ ^%(__prefix_line)sREST authentication attempt for unknown user .* from <HOST>$
15
  ^%(__prefix_line)sXML-RPC authentication attempt for unknown user .* from <HOST>$
16
  ^%(__prefix_line)sSpam comment \d+ from <HOST>$
17
  ^%(__prefix_line)sBlocked user enumeration attempt from <HOST>$
18
  ^%(__prefix_line)sBlocked authentication attempt for .* from <HOST>$
19
  ^%(__prefix_line)sXML-RPC multicall authentication failure from <HOST>$
20
  ^%(__prefix_line)sPingback error .* generated from <HOST>$
 
 
21
 
22
  ignoreregex =
23
 
filters.d/wordpress-soft.conf CHANGED
@@ -1,5 +1,5 @@
1
  # Fail2Ban filter for WordPress soft failures
2
- # Auto-generated: 2019-01-24T14:41:41+00:00
3
  #
4
 
5
  [INCLUDES]
@@ -11,6 +11,7 @@ before = common.conf
11
  _daemon = (?:wordpress|wp)
12
 
13
  failregex = ^%(__prefix_line)sAuthentication failure for .* from <HOST>$
 
14
  ^%(__prefix_line)sXML-RPC authentication failure for .* from <HOST>$
15
 
16
  ignoreregex =
1
  # Fail2Ban filter for WordPress soft failures
2
+ # Auto-generated: 2019-03-13T01:12:18+00:00
3
  #
4
 
5
  [INCLUDES]
11
  _daemon = (?:wordpress|wp)
12
 
13
  failregex = ^%(__prefix_line)sAuthentication failure for .* from <HOST>$
14
+ ^%(__prefix_line)sREST authentication failure for .* from <HOST>$
15
  ^%(__prefix_line)sXML-RPC authentication failure for .* from <HOST>$
16
 
17
  ignoreregex =
readme.txt CHANGED
@@ -5,8 +5,8 @@ Author URI: https://charles.lecklider.org/
5
  Plugin URI: https://wp-fail2ban.com/
6
  Tags: fail2ban, login, security, syslog
7
  Requires at least: 3.4
8
- Tested up to: 5.0
9
- Stable tag: 4.0.2
10
  Requires PHP: 5.3
11
  License: GPLv2 or later
12
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
@@ -27,48 +27,60 @@ Write a myriad of WordPress events to syslog for integration with fail2ban.
27
  = Features =
28
 
29
  * **CloudFlare and Proxy Servers**
30
- *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.0/defines.html#wp-fail2ban-proxies).
31
 
32
  * **Comments**
33
- *WPf2b* can log comments (see [`WP_FAIL2BAN_LOG_COMMENTS`](https://docs.wp-fail2ban.com/en/4.0/defines.html#wp-fail2ban-log-comments)) and attempted comments (see [`WP_FAIL2BAN_LOG_COMMENTS_EXTRA`](https://docs.wp-fail2ban.com/en/4.0/defines.html#wp-fail2ban-log-comments-extra)).
34
 
35
  * **Pingbacks**
36
- *WPf2b* logs failed pingbacks, and can log all pingbacks. For an overview see [`WP_FAIL2BAN_LOG_PINGBACKS`](https://docs.wp-fail2ban.com/en/4.0/defines.html#wp-fail2ban-log-pingbacks).
37
 
38
  * **Spam**
39
- *WPf2b* can log comments marked as spam. See [`WP_FAIL2BAN_LOG_SPAM`](https://docs.wp-fail2ban.com/en/4.0/defines.html#wp-fail2ban-log-spam).
40
 
41
  * **Block User Enumeration**
42
- *WPf2b* can block user enumeration. See [`WP_FAIL2BAN_BLOCK_USER_ENUMERATION`](https://docs.wp-fail2ban.com/en/4.0/defines.html#wp-fail2ban-block-user-enumeration).
43
 
44
  * **Work-Arounds for Broken syslogd**
45
- *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.0/defines.html#wp-fail2ban-syslog-short-tag) and [`WP_FAIL2BAN_HTTP_HOST`](https://docs.wp-fail2ban.com/en/4.0/defines.html#wp-fail2ban-http-host).
46
 
47
  * **Blocking Users**
48
- *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.0/defines.html#wp-fail2ban-blocked-users).
49
 
50
  * **`mu-plugins` Support**
51
- *WPf2b* can easily be configured as a must-use plugin - see [Configuration](https://docs.wp-fail2ban.com/en/4.0/configuration.html#mu-plugins-support).
52
 
53
  == Installation ==
54
 
55
  1. Install via the Plugin Directory, or upload to your plugins directory.
56
  1. Activate the plugin through the 'Plugins' menu in WordPress.
57
- 1. Edit `wp-config.php` to suit your needs - see [Configuration](https://docs.wp-fail2ban.com/en/4.0/configuration.html).
58
 
59
  == Changelog ==
60
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  = 4.0.2 =
62
  * Fix PHP 5.3 compatibility.
63
- * Bugfix for WP_FAIL2BAN_LOG_COMMENTS_EXTRA.
64
- * Bugfix for WP_FAIL2BAN_REMOTE_ADDR summary.
65
 
66
  = 4.0.1 =
67
  * Add extra features via Freemius. **This is entirely optional.** *WPf2b* works as before, including new features listed here.
68
  * Add settings summary page (Settings -> WP fail2ban).
69
- * Add [`WP_FAIL2BAN_PASSWORD_REQUEST_LOG`](https://docs.wp-fail2ban.com/en/4.0/defines.html#wp-fail2ban-password-request-log).
70
- * Add [`WP_FAIL2BAN_SPAM_LOG`](https://docs.wp-fail2ban.com/en/4.0/defines.html#wp-fail2ban-spam-log).
71
- * Add [`WP_FAIL2BAN_LOG_COMMENTS_EXTRA`](https://docs.wp-fail2ban.com/en/4.0/defines.html#wp-fail2ban-log-comments-extra) - enable logging for attempted comments on posts which are:
72
  * not found,
73
  * closed for commenting,
74
  * in the trash,
@@ -80,70 +92,70 @@ Write a myriad of WordPress events to syslog for integration with fail2ban.
80
  * Not released.
81
 
82
  = 3.6.0 =
83
- * The [filter files](https://docs.wp-fail2ban.com/en/4.0/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.
84
- * Added [PHPUnit tests](https://docs.wp-fail2ban.com/en/4.0/tests.html). Almost 100% code coverage, with the exception of [`WP_FAIL2BAN_PROXIES`](https://docs.wp-fail2ban.com/en/4.0/defines.html#wp-fail2ban-proxies) which is quite hard to test properly.
85
- * Bugfix for [`wordpress-soft.conf`](https://docs.wp-fail2ban.com/en/4.0/filters.html#wordpress-soft-conf).
86
- * Add [`WP_FAIL2BAN_XMLRPC_LOG`](https://docs.wp-fail2ban.com/en/4.0/defines.html#wp-fail2ban-xmlrpc-log).
87
- * Add [`WP_FAIL2BAN_REMOTE_ADDR`](https://docs.wp-fail2ban.com/en/4.0/defines.html#wp-fail2ban-remote-addr).
88
- * [`WP_FAIL2BAN_PROXIES`](https://docs.wp-fail2ban.com/en/4.0/defines.html#wp-fail2ban-proxies) now supports an array of IPs with PHP 7.
89
  * Moved all documentation to [https://docs.wp-fail2ban.com/](https://docs.wp-fail2ban.com/).
90
 
91
  = 3.5.3 =
92
- * Bugfix for [`wordpress-hard.conf`](https://docs.wp-fail2ban.com/en/4.0/filters.html#wordpress-hard-conf).
93
 
94
  = 3.5.1 =
95
- * Bugfix for [`WP_FAIL2BAN_BLOCK_USER_ENUMERATION`](https://docs.wp-fail2ban.com/en/4.0/defines.html#wp-fail2ban-block-user-enumeration).
96
 
97
  = 3.5.0 =
98
- * Add [`WP_FAIL2BAN_OPENLOG_OPTIONS`](https://docs.wp-fail2ban.com/en/4.0/defines.html#wp-fail2ban-openlog-options).
99
- * Add [`WP_FAIL2BAN_LOG_COMMENTS`](https://docs.wp-fail2ban.com/en/4.0/defines.html#wp-fail2ban-log-comments) and [`WP_FAIL2BAN_COMMENT_LOG`](https://docs.wp-fail2ban.com/en/4.0/defines.html#wp-fail2ban-comment-log).
100
- * Add [`WP_FAIL2BAN_LOG_PASSWORD_REQUEST`](https://docs.wp-fail2ban.com/en/4.0/defines.html#wp-fail2ban-log-password-request).
101
- * Add [`WP_FAIL2BAN_LOG_SPAM`](https://docs.wp-fail2ban.com/en/4.0/defines.html#wp-fail2ban-log-spam).
102
- * Add [`WP_FAIL2BAN_TRUNCATE_HOST`](https://docs.wp-fail2ban.com/en/4.0/defines.html#wp-fail2ban-truncate-host).
103
- * [`WP_FAIL2BAN_BLOCKED_USERS`](https://docs.wp-fail2ban.com/en/4.0/defines.html#wp-fail2ban-blocked-users) now supports an array of users with PHP 7.
104
 
105
  = 3.0.3 =
106
- * Fix regex in [`wordpress-hard.conf`](https://docs.wp-fail2ban.com/en/4.0/filters.html#wordpress-hard-conf).
107
 
108
  = 3.0.2 =
109
  * Prevent double logging in WP 4.5.x for XML-RPC authentication failure
110
 
111
  = 3.0.1 =
112
- * Fix regex in [`wordpress-hard.conf`](https://docs.wp-fail2ban.com/en/4.0/filters.html#wordpress-hard-conf).
113
 
114
  = 3.0.0 =
115
- * Add [`WP_FAIL2BAN_SYSLOG_SHORT_TAG`](https://docs.wp-fail2ban.com/en/4.0/defines.html#wp-fail2ban-syslog-short-tag).
116
- * Add [`WP_FAIL2BAN_HTTP_HOST`](https://docs.wp-fail2ban.com/en/4.0/defines.html#wp-fail2ban-http-host).
117
  * Log XML-RPC authentication failure.
118
  * Add better support for MU deployment.
119
 
120
  = 2.3.2 =
121
- * Bugfix [`WP_FAIL2BAN_BLOCKED_USERS`](https://docs.wp-fail2ban.com/en/4.0/defines.html#wp-fail2ban-blocked-users).
122
 
123
  = 2.3.0 =
124
- * Bugfix in *experimental* [`WP_FAIL2BAN_PROXIES`](https://docs.wp-fail2ban.com/en/4.0/defines.html#wp-fail2ban-proxies) code (thanks to KyleCartmell).
125
 
126
  = 2.2.1 =
127
- * Fix stupid mistake with [`WP_FAIL2BAN_BLOCKED_USERS`](https://docs.wp-fail2ban.com/en/4.0/defines.html#wp-fail2ban-blocked-users).
128
 
129
  = 2.2.0 =
130
- * Custom authentication log is now called [`WP_FAIL2BAN_AUTH_LOG`](https://docs.wp-fail2ban.com/en/4.0/defines.html#wp-fail2ban-auth-log).
131
- * Add logging for pingbacks; see [`WP_FAIL2BAN_LOG_PINGBACKS`](https://docs.wp-fail2ban.com/en/4.0/defines.html#wp-fail2ban-log-pingbacks).
132
- * Custom pingback log is called [`WP_FAIL2BAN_PINGBACK_LOG`](https://docs.wp-fail2ban.com/en/4.0/defines.html#wp-fail2ban-pingback-log).
133
 
134
  = 2.1.1 =
135
  * Minor bugfix.
136
 
137
  = 2.1.0 =
138
- * Add support for blocking user enumeration; see [`WP_FAIL2BAN_BLOCK_USER_ENUMERATION`](https://docs.wp-fail2ban.com/en/4.0/defines.html#wp-fail2ban-block-user-enumeration).
139
- * Add support for CIDR notation in [`WP_FAIL2BAN_PROXIES`](https://docs.wp-fail2ban.com/en/4.0/defines.html#wp-fail2ban-proxies).
140
 
141
  = 2.0.1 =
142
- * Bugfix in *experimental* [`WP_FAIL2BAN_PROXIES`](https://docs.wp-fail2ban.com/en/4.0/defines.html#wp-fail2ban-proxies) code.
143
 
144
  = 2.0.0 =
145
- * Add *experimental* support for X-Forwarded-For header; see [`WP_FAIL2BAN_PROXIES`](https://docs.wp-fail2ban.com/en/4.0/defines.html#wp-fail2ban-proxies).
146
- * Add *experimental* support for regex-based login blocking; see [`WP_FAIL2BAN_BLOCKED_USERS`](https://docs.wp-fail2ban.com/en/4.0/defines.html#wp-fail2ban-blocked-users).
147
 
148
  = 1.2.1 =
149
  * Update FAQ.
@@ -159,6 +171,18 @@ Write a myriad of WordPress events to syslog for integration with fail2ban.
159
 
160
  == Upgrade Notice ==
161
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  = 4.0.2 =
163
  This is a bugfix. You do not need to update your filters from 4.0.1.
164
 
@@ -172,7 +196,7 @@ You will need up update your `fail2ban` filters.
172
  You will need up update your `fail2ban` filters.
173
 
174
  = 3.5.1 =
175
- Bugfix: disable [`WP_FAIL2BAN_BLOCK_USER_ENUMERATION`](https://docs.wp-fail2ban.com/en/4.0/defines.html#wp-fail2ban-block-user-enumeration) in admin area....
176
 
177
  = 3.5.0 =
178
  You will need up update your `fail2ban` filters.
@@ -184,13 +208,13 @@ You will need up update your `fail2ban` filters.
184
  BREAKING CHANGE: The `fail2ban` filters have been split into two files. You will need up update your `fail2ban` configuration.
185
 
186
  = 2.3.0 =
187
- Fix for [`WP_FAIL2BAN_PROXIES`](https://docs.wp-fail2ban.com/en/4.0/defines.html#wp-fail2ban-proxies); if you're not using it you can safely skip this release.
188
 
189
  = 2.2.1 =
190
  Bugfix.
191
 
192
  = 2.2.0 =
193
- BREAKING CHANGE: `WP_FAIL2BAN_LOG` has been renamed to [`WP_FAIL2BAN_AUTH_LOG`](https://docs.wp-fail2ban.com/en/4.0/defines.html#wp-fail2ban-auth-log).
194
 
195
  Pingbacks are getting a lot of attention recently, so *WPf2b* can now log them.
196
  The `wordpress.conf` filter has been updated; you will need to update your `fail2ban` configuration.
@@ -202,4 +226,4 @@ The `wordpress.conf` filter has been updated; you will need to update your `fail
202
  Bugfix in experimental code; still an experimental release.
203
 
204
  = 2.0.0 =
205
- 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.
5
  Plugin URI: https://wp-fail2ban.com/
6
  Tags: fail2ban, login, security, syslog
7
  Requires at least: 3.4
8
+ Tested up to: 5.1
9
+ Stable tag: 4.1.0
10
  Requires PHP: 5.3
11
  License: GPLv2 or later
12
  License URI: http://www.gnu.org/licenses/gpl-2.0.html
27
  = Features =
28
 
29
  * **CloudFlare and Proxy Servers**
30
+ *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.1/defines.html#wp-fail2ban-proxies).
31
 
32
  * **Comments**
33
+ *WPf2b* can log comments (see [`WP_FAIL2BAN_LOG_COMMENTS`](https://docs.wp-fail2ban.com/en/4.1/defines.html#wp-fail2ban-log-comments)) and attempted comments (see [`WP_FAIL2BAN_LOG_COMMENTS_EXTRA`](https://docs.wp-fail2ban.com/en/4.1/defines.html#wp-fail2ban-log-comments-extra)).
34
 
35
  * **Pingbacks**
36
+ *WPf2b* logs failed pingbacks, and can log all pingbacks. For an overview see [`WP_FAIL2BAN_LOG_PINGBACKS`](https://docs.wp-fail2ban.com/en/4.1/defines.html#wp-fail2ban-log-pingbacks).
37
 
38
  * **Spam**
39
+ *WPf2b* can log comments marked as spam. See [`WP_FAIL2BAN_LOG_SPAM`](https://docs.wp-fail2ban.com/en/4.1/defines.html#wp-fail2ban-log-spam).
40
 
41
  * **Block User Enumeration**
42
+ *WPf2b* can block user enumeration. See [`WP_FAIL2BAN_BLOCK_USER_ENUMERATION`](https://docs.wp-fail2ban.com/en/4.1/defines.html#wp-fail2ban-block-user-enumeration).
43
 
44
  * **Work-Arounds for Broken syslogd**
45
+ *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.1/defines.html#wp-fail2ban-syslog-short-tag) and [`WP_FAIL2BAN_HTTP_HOST`](https://docs.wp-fail2ban.com/en/4.1/defines.html#wp-fail2ban-http-host).
46
 
47
  * **Blocking Users**
48
+ *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.1/defines.html#wp-fail2ban-blocked-users).
49
 
50
  * **`mu-plugins` Support**
51
+ *WPf2b* can easily be configured as a must-use plugin - see [Configuration](https://docs.wp-fail2ban.com/en/4.1/configuration.html#mu-plugins-support).
52
 
53
  == Installation ==
54
 
55
  1. Install via the Plugin Directory, or upload to your plugins directory.
56
  1. Activate the plugin through the 'Plugins' menu in WordPress.
57
+ 1. Edit `wp-config.php` to suit your needs - see [Configuration](https://docs.wp-fail2ban.com/en/4.1/configuration.html).
58
 
59
  == Changelog ==
60
 
61
+ = 4.1.0 =
62
+ * Add separate logging for REST authentication.
63
+ * 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).
64
+
65
+ = 4.0.5 =
66
+ * Add [`WP_FAIL2BAN_COMMENT_EXTRA_LOG`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_COMMENT_EXTRA_LOG.html).
67
+ * Add [`WP_FAIL2BAN_PINGBACK_ERROR_LOG`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_PINGBACK_ERROR_LOG.html) (future functionality).
68
+ * Change `WP_FAIL2BAN_LOG_SPAM` to use `LOG_NOTICE`.
69
+ * Change `WP_FAIL2BAN_SPAM_LOG` to `LOG_AUTH`.
70
+ * Change `WP_FAIL2BAN_LOG_COMMENTS_EXTRA` events to use `LOG_NOTICE` by default.
71
+ * Fix conflict with 3.x in `mu-plugins`.
72
+
73
  = 4.0.2 =
74
  * Fix PHP 5.3 compatibility.
75
+ * Bugfix for `WP_FAIL2BAN_LOG_COMMENTS_EXTRA`.
76
+ * Bugfix for `WP_FAIL2BAN_REMOTE_ADDR` summary.
77
 
78
  = 4.0.1 =
79
  * Add extra features via Freemius. **This is entirely optional.** *WPf2b* works as before, including new features listed here.
80
  * Add settings summary page (Settings -> WP fail2ban).
81
+ * Add [`WP_FAIL2BAN_PASSWORD_REQUEST_LOG`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_PASSWORD_REQUEST_LOG.html).
82
+ * Add [`WP_FAIL2BAN_SPAM_LOG`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_SPAM_LOG.html).
83
+ * 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:
84
  * not found,
85
  * closed for commenting,
86
  * in the trash,
92
  * Not released.
93
 
94
  = 3.6.0 =
95
+ * 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.
96
+ * 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.
97
+ * Bugfix for [`wordpress-soft.conf`](https://docs.wp-fail2ban.com/en/4.1/filters.html#wordpress-soft-conf).
98
+ * Add [`WP_FAIL2BAN_XMLRPC_LOG`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_XMLRPC_LOG.html).
99
+ * Add [`WP_FAIL2BAN_REMOTE_ADDR`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_REMOTE_ADDR.html).
100
+ * [`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.
101
  * Moved all documentation to [https://docs.wp-fail2ban.com/](https://docs.wp-fail2ban.com/).
102
 
103
  = 3.5.3 =
104
+ * Bugfix for [`wordpress-hard.conf`](https://docs.wp-fail2ban.com/en/4.1/filters.html#wordpress-hard-conf).
105
 
106
  = 3.5.1 =
107
+ * Bugfix for [`WP_FAIL2BAN_BLOCK_USER_ENUMERATION`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_BLOCK_USER_ENUMERATION.html).
108
 
109
  = 3.5.0 =
110
+ * Add [`WP_FAIL2BAN_OPENLOG_OPTIONS`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_OPENLOG_OPTIONS.html).
111
+ * 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).
112
+ * Add [`WP_FAIL2BAN_LOG_PASSWORD_REQUEST`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_LOG_PASSWORD_REQUEST.html).
113
+ * Add [`WP_FAIL2BAN_LOG_SPAM`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_LOG_SPAM.html).
114
+ * Add [`WP_FAIL2BAN_TRUNCATE_HOST`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_TRUNCATE_HOST.html).
115
+ * [`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.
116
 
117
  = 3.0.3 =
118
+ * Fix regex in [`wordpress-hard.conf`](https://docs.wp-fail2ban.com/en/4.1/filters.html#wordpress-hard-conf).
119
 
120
  = 3.0.2 =
121
  * Prevent double logging in WP 4.5.x for XML-RPC authentication failure
122
 
123
  = 3.0.1 =
124
+ * Fix regex in [`wordpress-hard.conf`](https://docs.wp-fail2ban.com/en/4.1/filters.html#wordpress-hard-conf).
125
 
126
  = 3.0.0 =
127
+ * Add [`WP_FAIL2BAN_SYSLOG_SHORT_TAG`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_SYSLOG_SHORT_TAG.html).
128
+ * Add [`WP_FAIL2BAN_HTTP_HOST`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_HTTP_HOST.html).
129
  * Log XML-RPC authentication failure.
130
  * Add better support for MU deployment.
131
 
132
  = 2.3.2 =
133
+ * Bugfix [`WP_FAIL2BAN_BLOCKED_USERS`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_BLOCKED_USERS.html).
134
 
135
  = 2.3.0 =
136
+ * Bugfix in *experimental* [`WP_FAIL2BAN_PROXIES`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_PROXIES.html) code (thanks to KyleCartmell).
137
 
138
  = 2.2.1 =
139
+ * Fix stupid mistake with [`WP_FAIL2BAN_BLOCKED_USERS`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_BLOCKED_USERS.html).
140
 
141
  = 2.2.0 =
142
+ * Custom authentication log is now called [`WP_FAIL2BAN_AUTH_LOG`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_AUTH_LOG.html).
143
+ * Add logging for pingbacks; see [`WP_FAIL2BAN_LOG_PINGBACKS`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_LOG_PINGBACKS.html).
144
+ * Custom pingback log is called [`WP_FAIL2BAN_PINGBACK_LOG`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_PINGBACK_LOG.html).
145
 
146
  = 2.1.1 =
147
  * Minor bugfix.
148
 
149
  = 2.1.0 =
150
+ * 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).
151
+ * Add support for CIDR notation in [`WP_FAIL2BAN_PROXIES`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_PROXIES.html).
152
 
153
  = 2.0.1 =
154
+ * Bugfix in *experimental* [`WP_FAIL2BAN_PROXIES`](https://docs.wp-fail2ban.com/en/4.1/defines/WP_FAIL2BAN_PROXIES.html) code.
155
 
156
  = 2.0.0 =
157
+ * 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).
158
+ * 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).
159
 
160
  = 1.2.1 =
161
  * Update FAQ.
171
 
172
  == Upgrade Notice ==
173
 
174
+ = 4.1.0 =
175
+ To take advantage of the new features you will need up update your `fail2ban` filters; existing filters will continue to work as before.
176
+
177
+ = 4.0.5 =
178
+ This is a security fix: all 4.x users are strongly advised to upgrade immediately. You do not need to update your filters from 4.0.1.
179
+
180
+ = 4.0.4 =
181
+ This is a bugfix. You do not need to update your filters from 4.0.1.
182
+
183
+ = 4.0.3 =
184
+ This is a bugfix. You do not need to update your filters from 4.0.1.
185
+
186
  = 4.0.2 =
187
  This is a bugfix. You do not need to update your filters from 4.0.1.
188
 
196
  You will need up update your `fail2ban` filters.
197
 
198
  = 3.5.1 =
199
+ 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....
200
 
201
  = 3.5.0 =
202
  You will need up update your `fail2ban` filters.
208
  BREAKING CHANGE: The `fail2ban` filters have been split into two files. You will need up update your `fail2ban` configuration.
209
 
210
  = 2.3.0 =
211
+ 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.
212
 
213
  = 2.2.1 =
214
  Bugfix.
215
 
216
  = 2.2.0 =
217
+ 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).
218
 
219
  Pingbacks are getting a lot of attention recently, so *WPf2b* can now log them.
220
  The `wordpress.conf` filter has been updated; you will need to update your `fail2ban` configuration.
226
  Bugfix in experimental code; still an experimental release.
227
 
228
  = 2.0.0 =
229
+ 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.
vendor/freemius/wordpress-sdk/includes/class-freemius.php CHANGED
@@ -2977,6 +2977,10 @@
2977
  * @since 1.1.7.3
2978
  */
2979
  static function _toggle_debug_mode() {
 
 
 
 
2980
  $is_on = fs_request_get( 'is_on', false, 'post' );
2981
 
2982
  if ( fs_request_is_post() && in_array( $is_on, array( 0, 1 ) ) ) {
@@ -3008,8 +3012,16 @@
3008
  * @since 1.2.1.7
3009
  */
3010
  static function _get_db_option() {
 
 
3011
  $option_name = fs_request_get( 'option_name' );
3012
 
 
 
 
 
 
 
3013
  $value = get_option( $option_name );
3014
 
3015
  $result = array(
@@ -3032,7 +3044,16 @@
3032
  * @since 1.2.1.7
3033
  */
3034
  static function _set_db_option() {
3035
- $option_name = fs_request_get( 'option_name' );
 
 
 
 
 
 
 
 
 
3036
  $option_value = fs_request_get( 'option_value' );
3037
 
3038
  if ( ! empty( $option_value ) ) {
2977
  * @since 1.1.7.3
2978
  */
2979
  static function _toggle_debug_mode() {
2980
+ if ( ! is_super_admin() ) {
2981
+ return;
2982
+ }
2983
+
2984
  $is_on = fs_request_get( 'is_on', false, 'post' );
2985
 
2986
  if ( fs_request_is_post() && in_array( $is_on, array( 0, 1 ) ) ) {
3012
  * @since 1.2.1.7
3013
  */
3014
  static function _get_db_option() {
3015
+ check_admin_referer( 'fs_get_db_option' );
3016
+
3017
  $option_name = fs_request_get( 'option_name' );
3018
 
3019
+ if ( ! is_super_admin() ||
3020
+ ! fs_starts_with( $option_name, 'fs_' )
3021
+ ) {
3022
+ self::shoot_ajax_failure();
3023
+ }
3024
+
3025
  $value = get_option( $option_name );
3026
 
3027
  $result = array(
3044
  * @since 1.2.1.7
3045
  */
3046
  static function _set_db_option() {
3047
+ check_admin_referer( 'fs_set_db_option' );
3048
+
3049
+ $option_name = fs_request_get( 'option_name' );
3050
+
3051
+ if ( ! is_super_admin() ||
3052
+ ! fs_starts_with( $option_name, 'fs_' )
3053
+ ) {
3054
+ self::shoot_ajax_failure();
3055
+ }
3056
+
3057
  $option_value = fs_request_get( 'option_value' );
3058
 
3059
  if ( ! empty( $option_value ) ) {
vendor/freemius/wordpress-sdk/start.php CHANGED
@@ -15,7 +15,7 @@
15
  *
16
  * @var string
17
  */
18
- $this_sdk_version = '2.2.3';
19
 
20
  #region SDK Selection Logic --------------------------------------------------------------------
21
 
15
  *
16
  * @var string
17
  */
18
+ $this_sdk_version = '2.2.4';
19
 
20
  #region SDK Selection Logic --------------------------------------------------------------------
21
 
vendor/freemius/wordpress-sdk/templates/debug.php CHANGED
@@ -113,6 +113,7 @@
113
  if (optionName) {
114
  $.post(ajaxurl, {
115
  action : 'fs_get_db_option',
 
116
  option_name: optionName
117
  }, function (response) {
118
  if (response.data.value)
@@ -132,6 +133,7 @@
132
  if (optionValue) {
133
  $.post(ajaxurl, {
134
  action : 'fs_set_db_option',
 
135
  option_name : optionName,
136
  option_value: optionValue
137
  }, function () {
113
  if (optionName) {
114
  $.post(ajaxurl, {
115
  action : 'fs_get_db_option',
116
+ _wpnonce : '<?php echo wp_create_nonce( 'fs_get_db_option' ) ?>',
117
  option_name: optionName
118
  }, function (response) {
119
  if (response.data.value)
133
  if (optionValue) {
134
  $.post(ajaxurl, {
135
  action : 'fs_set_db_option',
136
+ _wpnonce : '<?php echo wp_create_nonce( 'fs_set_db_option' ) ?>',
137
  option_name : optionName,
138
  option_value: optionValue
139
  }, function () {
vendor/freemius/wordpress-sdk/templates/forms/deactivation/form.php CHANGED
@@ -1,497 +1,497 @@
1
- <?php
2
- /**
3
- * @package Freemius
4
- * @copyright Copyright (c) 2015, Freemius, Inc.
5
- * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
6
- * @since 1.1.2
7
- */
8
-
9
- if ( ! defined( 'ABSPATH' ) ) {
10
- exit;
11
- }
12
-
13
- /**
14
- * @var array $VARS
15
- */
16
- $fs = freemius( $VARS['id'] );
17
- $slug = $fs->get_slug();
18
-
19
- $confirmation_message = $fs->apply_filters( 'uninstall_confirmation_message', '' );
20
-
21
- $reasons = $VARS['reasons'];
22
-
23
- $reasons_list_items_html = '';
24
-
25
- foreach ( $reasons as $reason ) {
26
- $list_item_classes = 'reason' . ( ! empty( $reason['input_type'] ) ? ' has-input' : '' );
27
-
28
- if ( isset( $reason['internal_message'] ) && ! empty( $reason['internal_message'] ) ) {
29
- $list_item_classes .= ' has-internal-message';
30
- $reason_internal_message = $reason['internal_message'];
31
- } else {
32
- $reason_internal_message = '';
33
- }
34
-
35
- $reason_input_type = ( ! empty( $reason['input_type'] ) ? $reason['input_type'] : '' );
36
- $reason_input_placeholder = ( ! empty( $reason['input_placeholder'] ) ? $reason['input_placeholder'] : '' );
37
-
38
- $reason_list_item_html = <<< HTML
39
- <li class="{$list_item_classes}"
40
- data-input-type="{$reason_input_type}"
41
- data-input-placeholder="{$reason_input_placeholder}">
42
- <label>
43
- <span>
44
- <input type="radio" name="selected-reason" value="{$reason['id']}"/>
45
- </span>
46
- <span>{$reason['text']}</span>
47
- </label>
48
- <div class="internal-message">{$reason_internal_message}</div>
49
- </li>
50
- HTML;
51
-
52
- $reasons_list_items_html .= $reason_list_item_html;
53
- }
54
-
55
- $is_anonymous = ( ! $fs->is_registered() );
56
- if ( $is_anonymous ) {
57
- $anonymous_feedback_checkbox_html = sprintf(
58
- '<label class="anonymous-feedback-label"><input type="checkbox" class="anonymous-feedback-checkbox"> %s</label>',
59
- fs_esc_html_inline( 'Anonymous feedback', 'anonymous-feedback', $slug )
60
- );
61
- } else {
62
- $anonymous_feedback_checkbox_html = '';
63
- }
64
-
65
- // Aliases.
66
- $deactivate_text = fs_text_inline( 'Deactivate', 'deactivate', $slug );
67
- $theme_text = fs_text_inline( 'Theme', 'theme', $slug );
68
- $activate_x_text = fs_text_inline( 'Activate %s', 'activate-x', $slug );
69
-
70
- fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' );
71
- ?>
72
- <?php $fs->_maybe_add_subscription_cancellation_dialog_box() ?>
73
- <script type="text/javascript">
74
- (function ($) {
75
- var reasonsHtml = <?php echo json_encode( $reasons_list_items_html ) ?>,
76
- modalHtml =
77
- '<div class="fs-modal fs-modal-deactivation-feedback<?php echo empty( $confirmation_message ) ? ' no-confirmation-message' : ''; ?>">'
78
- + ' <div class="fs-modal-dialog">'
79
- + ' <div class="fs-modal-header">'
80
- + ' <h4><?php fs_esc_attr_echo_inline( 'Quick Feedback', 'quick-feedback' , $slug ) ?></h4>'
81
- + ' </div>'
82
- + ' <div class="fs-modal-body">'
83
- + ' <div class="fs-modal-panel" data-panel-id="confirm"><p><?php echo $confirmation_message; ?></p></div>'
84
- + ' <div class="fs-modal-panel active" data-panel-id="reasons"><h3><strong><?php echo esc_js( sprintf( fs_text_inline( 'If you have a moment, please let us know why you are %s', 'deactivation-share-reason' , $slug ), ( $fs->is_plugin() ? fs_text_inline( 'deactivating', 'deactivating', $slug ) : fs_text_inline( 'switching', 'switching', $slug ) ) ) ) ?>:</strong></h3><ul id="reasons-list">' + reasonsHtml + '</ul></div>'
85
- + ' </div>'
86
- + ' <div class="fs-modal-footer">'
87
- + ' <?php echo $anonymous_feedback_checkbox_html ?>'
88
- + ' <a href="#" class="button button-secondary button-deactivate"></a>'
89
- + ' <a href="#" class="button button-primary button-close"><?php fs_esc_js_echo_inline( 'Cancel', 'cancel', $slug ) ?></a>'
90
- + ' </div>'
91
- + ' </div>'
92
- + '</div>',
93
- $modal = $(modalHtml),
94
- $deactivateLink = $('#the-list .deactivate > [data-module-id=<?php echo $fs->get_id() ?>].fs-module-id').prev(),
95
- selectedReasonID = false,
96
- redirectLink = '',
97
- $anonymousFeedback = $modal.find( '.anonymous-feedback-label' ),
98
- isAnonymous = <?php echo ( $is_anonymous ? 'true' : 'false' ); ?>,
99
- otherReasonID = <?php echo Freemius::REASON_OTHER; ?>,
100
- dontShareDataReasonID = <?php echo Freemius::REASON_DONT_LIKE_TO_SHARE_MY_INFORMATION; ?>,
101
- deleteThemeUpdateData = <?php echo $fs->is_theme() && $fs->is_premium() && ! $fs->has_any_active_valid_license() ? 'true' : 'false' ?>,
102
- $subscriptionCancellationModal = $( '.fs-modal-subscription-cancellation-<?php echo $fs->get_id() ?>' );
103
-
104
- $modal.appendTo($('body'));
105
-
106
- if ( 0 !== $subscriptionCancellationModal.length ) {
107
- $subscriptionCancellationModal.on( '<?php echo $fs->get_action_tag( 'subscription_cancellation_action' ) ?>', function( evt, cancelSubscription ) {
108
- if ( false === cancelSubscription ) {
109
- showModal();
110
-
111
- $subscriptionCancellationModal.trigger( 'closeModal' );
112
- } else {
113
- var $errorMessage = $subscriptionCancellationModal.find( '.notice-error' );
114
-
115
- <?php
116
- $subscription_cancellation_context = $fs->is_paid_trial() ?
117
- fs_text_inline( 'trial', 'trial', $slug ) :
118
- fs_text_inline( 'subscription', 'subscription', $slug );
119
- ?>
120
-
121
- $.ajax({
122
- url : ajaxurl,
123
- method : 'POST',
124
- data : {
125
- action : '<?php echo $fs->get_ajax_action( 'cancel_subscription_or_trial' ) ?>',
126
- security : '<?php echo $fs->get_ajax_security( 'cancel_subscription_or_trial' ) ?>',
127
- module_id: '<?php echo $fs->get_id() ?>'
128
- },
129
- beforeSend: function() {
130
- $errorMessage.hide();
131
-
132
- $subscriptionCancellationModal.find( '.fs-modal-footer .button' ).addClass( 'disabled' );
133
- $subscriptionCancellationModal.find( '.fs-modal-footer .button-primary' ).text( '<?php echo esc_js(
134
- sprintf( fs_text_inline( 'Cancelling %s...', 'cancelling-x' , $slug ), $subscription_cancellation_context )
135
- ) ?>' );
136
- },
137
- success: function( result ) {
138
- if ( result.success ) {
139
- $subscriptionCancellationModal.removeClass( 'has-subscription-actions' );
140
- $subscriptionCancellationModal.find( '.fs-modal-footer .button-primary' ).removeClass( 'warn' );
141
-
142
- $subscriptionCancellationModal.remove();
143
- showModal();
144
- } else {
145
- $errorMessage.find( '> p' ).html( result.error );
146
- $errorMessage.show();
147
-
148
- $subscriptionCancellationModal.find( '.fs-modal-footer .button' ).removeClass( 'disabled' );
149
- $subscriptionCancellationModal.find( '.fs-modal-footer .button-primary' ).html( <?php echo json_encode( sprintf(
150
- fs_text_inline( 'Cancel %s & Proceed', 'cancel-x-and-proceed', $slug ),
151
- ucfirst( $subscription_cancellation_context )
152
- ) ) ?> );
153
- }
154
- }
155
- });
156
- }
157
- });
158
- }
159
-
160
- registerEventHandlers();
161
-
162
- function registerEventHandlers() {
163
- $deactivateLink.click(function (evt) {
164
- evt.preventDefault();
165
-
166
- redirectLink = $(this).attr('href');
167
-
168
- if ( 0 == $subscriptionCancellationModal.length ) {
169
- showModal();
170
- } else {
171
- $subscriptionCancellationModal.trigger( 'showModal' );
172
- }
173
- });
174
-
175
- <?php
176
- if ( ! $fs->is_plugin() ) {
177
- /**
178
- * For "theme" module type, the modal is shown when the current user clicks on
179
- * the "Activate" button of any other theme. The "Activate" button is actually
180
- * a link to the "Themes" page (/wp-admin/themes.php) containing query params
181
- * that tell WordPress to deactivate the current theme and activate a different theme.
182
- *
183
- * @author Leo Fajardo (@leorw)
184
- * @since 1.2.2
185
- *
186
- * @since 1.2.2.7 Don't trigger the deactivation feedback form if activating the premium version of the theme.
187
- */
188
- ?>
189
- $('body').on('click', '.theme-browser .theme:not([data-slug=<?php echo $fs->get_premium_slug() ?>]) .theme-actions .button.activate', function (evt) {
190
- evt.preventDefault();
191
-
192
- redirectLink = $(this).attr('href');
193
-
194
- showModal();
195
- });
196
- <?php
197
- } ?>
198
-
199
- $modal.on('input propertychange', '.reason-input input', function () {
200
- if (!isOtherReasonSelected()) {
201
- return;
202
- }
203
-
204
- var reason = $(this).val().trim();
205
-
206
- /**
207
- * If reason is not empty, remove the error-message class of the message container
208
- * to change the message color back to default.
209
- */
210
- if (reason.length > 0) {
211
- $('.message').removeClass('error-message');
212
- enableDeactivateButton();
213
- }
214
- });
215
-
216
- $modal.on('blur', '.reason-input input', function () {
217
- var $userReason = $(this);
218
-
219
- setTimeout(function () {
220
- if (!isOtherReasonSelected()) {
221
- return;
222
- }
223
-
224
- /**
225
- * If reason is empty, add the error-message class to the message container
226
- * to change the message color to red.
227
- */
228
- if (0 === $userReason.val().trim().length) {
229
- $('.message').addClass('error-message');
230
- disableDeactivateButton();
231
- }
232
- }, 150);
233
- });
234
-
235
- $modal.on('click', '.fs-modal-footer .button', function (evt) {
236
- evt.preventDefault();
237
-
238
- if ($(this).hasClass('disabled')) {
239
- return;
240
- }
241
-
242
- var _parent = $(this).parents('.fs-modal:first');
243
- var _this = $(this);
244
-
245
- if (_this.hasClass('allow-deactivate')) {
246
- var $radio = $('input[type="radio"]:checked');
247
-
248
- if (0 === $radio.length) {
249
- if ( ! deleteThemeUpdateData ) {
250
- // If no selected reason, just deactivate the plugin.
251
- window.location.href = redirectLink;
252
- } else {
253
- $.ajax({
254
- url : ajaxurl,
255
- method : 'POST',
256
- data : {
257
- action : '<?php echo $fs->get_ajax_action( 'delete_theme_update_data' ) ?>',
258
- security : '<?php echo $fs->get_ajax_security( 'delete_theme_update_data' ) ?>',
259
- module_id: '<?php echo $fs->get_id() ?>'
260
- },
261
- beforeSend: function() {
262
- _parent.find( '.fs-modal-footer .button' ).addClass( 'disabled' );
263
- _parent.find( '.fs-modal-footer .button-secondary' ).text( 'Processing...' );
264
- },
265
- complete : function() {
266
- window.location.href = redirectLink;
267
- }
268
- });
269
- }
270
-
271
- return;
272
- }
273
-
274
- var $selected_reason = $radio.parents('li:first'),
275
- $input = $selected_reason.find('textarea, input[type="text"]'),
276
- userReason = ( 0 !== $input.length ) ? $input.val().trim() : '';
277
-
278
- if (isOtherReasonSelected() && ( '' === userReason )) {
279
- return;
280
- }
281
-
282
- $.ajax({
283
- url : ajaxurl,
284
- method : 'POST',
285
- data : {
286
- action : '<?php echo $fs->get_ajax_action( 'submit_uninstall_reason' ) ?>',
287
- security : '<?php echo $fs->get_ajax_security( 'submit_uninstall_reason' ) ?>',
288
- module_id : '<?php echo $fs->get_id() ?>',
289
- reason_id : $radio.val(),
290
- reason_info : userReason,
291
- is_anonymous: isAnonymousFeedback()
292
- },
293
- beforeSend: function () {
294
- _parent.find('.fs-modal-footer .button').addClass('disabled');
295
- _parent.find('.fs-modal-footer .button-secondary').text('Processing...');
296
- },
297
- complete : function () {
298
- // Do not show the dialog box, deactivate the plugin.
299
- window.location.href = redirectLink;
300
- }
301
- });
302
- } else if (_this.hasClass('button-deactivate')) {
303
- // Change the Deactivate button's text and show the reasons panel.
304
- _parent.find('.button-deactivate').addClass('allow-deactivate');
305
-
306
- showPanel('reasons');
307
- }
308
- });
309
-
310
- $modal.on('click', 'input[type="radio"]', function () {
311
- var $selectedReasonOption = $( this );
312
-
313
- // If the selection has not changed, do not proceed.
314
- if (selectedReasonID === $selectedReasonOption.val())
315
- return;
316
-
317
- selectedReasonID = $selectedReasonOption.val();
318
-
319
- if ( isAnonymous ) {
320
- if ( isReasonSelected( dontShareDataReasonID ) ) {
321
- $anonymousFeedback.hide();
322
- } else {
323
- $anonymousFeedback.show();
324
- }
325
- }
326
-
327
- var _parent = $(this).parents('li:first');
328
-
329
- $modal.find('.reason-input').remove();
330
- $modal.find( '.internal-message' ).hide();
331
- $modal.find('.button-deactivate').html('<?php echo esc_js( sprintf(
332
- fs_text_inline( 'Submit & %s', 'deactivation-modal-button-submit' , $slug ),
333
- $fs->is_plugin() ?
334
- $deactivate_text :
335
- sprintf( $activate_x_text, $theme_text )
336
- ) ) ?>');
337
-
338
- enableDeactivateButton();
339
-
340
- if ( _parent.hasClass( 'has-internal-message' ) ) {
341
- _parent.find( '.internal-message' ).show();
342
- }
343
-
344
- if (_parent.hasClass('has-input')) {
345
- var inputType = _parent.data('input-type'),
346
- inputPlaceholder = _parent.data('input-placeholder'),
347
- reasonInputHtml = '<div class="reason-input"><span class="message"></span>' + ( ( 'textfield' === inputType ) ? '<input type="text" />' : '<textarea rows="5"></textarea>' ) + '</div>';
348
-
349
- _parent.append($(reasonInputHtml));
350
- _parent.find('input, textarea').attr('placeholder', inputPlaceholder).focus();
351
-
352
- if (isOtherReasonSelected()) {
353
- showMessage('<?php echo esc_js( fs_text_inline( 'Kindly tell us the reason so we can improve.', 'ask-for-reason-message' , $slug ) ); ?>');
354
- disableDeactivateButton();
355
- }
356
- }
357
- });
358
-
359
- // If the user has clicked outside the window, cancel it.
360
- $modal.on('click', function (evt) {
361
- var $target = $(evt.target);
362
-
363
- // If the user has clicked anywhere in the modal dialog, just return.
364
- if ($target.hasClass('fs-modal-body') || $target.hasClass('fs-modal-footer')) {
365
- return;
366
- }
367
-
368
- // If the user has not clicked the close button and the clicked element is inside the modal dialog, just return.
369
- if (
370
- ! $target.hasClass( 'button-close' ) &&
371
- ( $target.parents( '.fs-modal-body' ).length > 0 || $target.parents( '.fs-modal-footer' ).length > 0 )
372
- ) {
373
- return;
374
- }
375
-
376
- closeModal();
377
-
378
- return false;
379
- });
380
- }
381
-
382
- function isAnonymousFeedback() {
383
- if ( ! isAnonymous ) {
384
- return false;
385
- }
386
-
387
- return ( isReasonSelected( dontShareDataReasonID ) || $anonymousFeedback.find( 'input' ).prop( 'checked' ) );
388
- }
389
-
390
- function isReasonSelected( reasonID ) {
391
- // Get the selected radio input element.
392
- var $selectedReasonOption = $modal.find('input[type="radio"]:checked');
393
-
394
- return ( reasonID == $selectedReasonOption.val() );
395
- }
396
-
397
- function isOtherReasonSelected() {
398
- return isReasonSelected( otherReasonID );
399
- }
400
-
401
- function showModal() {
402
- resetModal();
403
-
404
- // Display the dialog box.
405
- $modal.addClass('active');
406
-
407
- $('body').addClass('has-fs-modal');
408
- }
409
-
410
- function closeModal() {
411
- $modal.removeClass('active');
412
-
413
- $('body').removeClass('has-fs-modal');
414
- }
415
-
416
- function resetModal() {
417
- selectedReasonID = false;
418
-
419
- enableDeactivateButton();
420
-
421
- // Uncheck all radio buttons.
422
- $modal.find('input[type="radio"]').prop('checked', false);
423
-
424
- // Remove all input fields ( textfield, textarea ).
425
- $modal.find('.reason-input').remove();
426
-
427
- $modal.find('.message').hide();
428
-
429
- if ( isAnonymous ) {
430
- $anonymousFeedback.find( 'input' ).prop( 'checked', false );
431
-
432
- // Hide, since by default there is no selected reason.
433
- $anonymousFeedback.hide();
434
- }
435
-
436
- var $deactivateButton = $modal.find('.button-deactivate');
437
-
438
- /*
439
- * If the modal dialog has no confirmation message, that is, it has only one panel, then ensure
440
- * that clicking the deactivate button will actually deactivate the plugin.
441
- */
442
- if ( $modal.hasClass( 'no-confirmation-message' ) ) {
443
- $deactivateButton.addClass( 'allow-deactivate' );
444
-
445
- showPanel( 'reasons' );
446
- } else {
447
- $deactivateButton.removeClass( 'allow-deactivate' );
448
-
449
- showPanel( 'confirm' );
450
- }
451
- }
452
-
453
- function showMessage(message) {
454
- $modal.find('.message').text(message).show();
455
- }
456
-
457
- function enableDeactivateButton() {
458
- $modal.find('.button-deactivate').removeClass('disabled');
459
- }
460
-
461
- function disableDeactivateButton() {
462
- $modal.find('.button-deactivate').addClass('disabled');
463
- }
464
-
465
- function showPanel(panelType) {
466
- $modal.find( '.fs-modal-panel' ).removeClass( 'active' );
467
- $modal.find( '[data-panel-id="' + panelType + '"]' ).addClass( 'active' );
468
-
469
- updateButtonLabels();
470
- }
471
-
472
- function updateButtonLabels() {
473
- var $deactivateButton = $modal.find( '.button-deactivate' );
474
-
475
- // Reset the deactivate button's text.
476
- if ( 'confirm' === getCurrentPanel() ) {
477
- $deactivateButton.text( <?php echo json_encode( sprintf(
478
- fs_text_inline( 'Yes - %s', 'deactivation-modal-button-confirm', $slug ),
479
- $fs->is_plugin() ?
480
- $deactivate_text :
481
- sprintf( $activate_x_text, $theme_text )
482
- ) ) ?> );
483
- } else {
484
- $deactivateButton.html( <?php echo json_encode( sprintf(
485
- fs_text_inline('Skip & %s', 'skip-and-x', $slug ),
486
- $fs->is_plugin() ?
487
- $deactivate_text :
488
- sprintf( $activate_x_text, $theme_text )
489
- ) ) ?> );
490
- }
491
- }
492
-
493
- function getCurrentPanel() {
494
- return $modal.find('.fs-modal-panel.active').attr('data-panel-id');
495
- }
496
- })(jQuery);
497
- </script>
1
+ <?php
2
+ /**
3
+ * @package Freemius
4
+ * @copyright Copyright (c) 2015, Freemius, Inc.
5
+ * @license https://www.gnu.org/licenses/gpl-3.0.html GNU General Public License Version 3
6
+ * @since 1.1.2
7
+ */
8
+
9
+ if ( ! defined( 'ABSPATH' ) ) {
10
+ exit;
11
+ }
12
+
13
+ /**
14
+ * @var array $VARS
15
+ */
16
+ $fs = freemius( $VARS['id'] );
17
+ $slug = $fs->get_slug();
18
+
19
+ $confirmation_message = $fs->apply_filters( 'uninstall_confirmation_message', '' );
20
+
21
+ $reasons = $VARS['reasons'];
22
+
23
+ $reasons_list_items_html = '';
24
+
25
+ foreach ( $reasons as $reason ) {
26
+ $list_item_classes = 'reason' . ( ! empty( $reason['input_type'] ) ? ' has-input' : '' );
27
+
28
+ if ( isset( $reason['internal_message'] ) && ! empty( $reason['internal_message'] ) ) {
29
+ $list_item_classes .= ' has-internal-message';
30
+ $reason_internal_message = $reason['internal_message'];
31
+ } else {
32
+ $reason_internal_message = '';
33
+ }
34
+
35
+ $reason_input_type = ( ! empty( $reason['input_type'] ) ? $reason['input_type'] : '' );
36
+ $reason_input_placeholder = ( ! empty( $reason['input_placeholder'] ) ? $reason['input_placeholder'] : '' );
37
+
38
+ $reason_list_item_html = <<< HTML
39
+ <li class="{$list_item_classes}"
40
+ data-input-type="{$reason_input_type}"
41
+ data-input-placeholder="{$reason_input_placeholder}">
42
+ <label>
43
+ <span>
44
+ <input type="radio" name="selected-reason" value="{$reason['id']}"/>
45
+ </span>
46
+ <span>{$reason['text']}</span>
47
+ </label>
48
+ <div class="internal-message">{$reason_internal_message}</div>
49
+ </li>
50
+ HTML;
51
+
52
+ $reasons_list_items_html .= $reason_list_item_html;
53
+ }
54
+
55
+ $is_anonymous = ( ! $fs->is_registered() );
56
+ if ( $is_anonymous ) {
57
+ $anonymous_feedback_checkbox_html = sprintf(
58
+ '<label class="anonymous-feedback-label"><input type="checkbox" class="anonymous-feedback-checkbox"> %s</label>',
59
+ fs_esc_html_inline( 'Anonymous feedback', 'anonymous-feedback', $slug )
60
+ );
61
+ } else {
62
+ $anonymous_feedback_checkbox_html = '';
63
+ }
64
+
65
+ // Aliases.
66
+ $deactivate_text = fs_text_inline( 'Deactivate', 'deactivate', $slug );
67
+ $theme_text = fs_text_inline( 'Theme', 'theme', $slug );
68
+ $activate_x_text = fs_text_inline( 'Activate %s', 'activate-x', $slug );
69
+
70
+ fs_enqueue_local_style( 'fs_dialog_boxes', '/admin/dialog-boxes.css' );
71
+ ?>
72
+ <?php $fs->_maybe_add_subscription_cancellation_dialog_box() ?>
73
+ <script type="text/javascript">
74
+ (function ($) {
75
+ var reasonsHtml = <?php echo json_encode( $reasons_list_items_html ) ?>,
76
+ modalHtml =
77
+ '<div class="fs-modal fs-modal-deactivation-feedback<?php echo empty( $confirmation_message ) ? ' no-confirmation-message' : ''; ?>">'
78
+ + ' <div class="fs-modal-dialog">'
79
+ + ' <div class="fs-modal-header">'
80
+ + ' <h4><?php fs_esc_attr_echo_inline( 'Quick Feedback', 'quick-feedback' , $slug ) ?></h4>'
81
+ + ' </div>'
82
+ + ' <div class="fs-modal-body">'
83
+ + ' <div class="fs-modal-panel" data-panel-id="confirm"><p><?php echo $confirmation_message; ?></p></div>'
84
+ + ' <div class="fs-modal-panel active" data-panel-id="reasons"><h3><strong><?php echo esc_js( sprintf( fs_text_inline( 'If you have a moment, please let us know why you are %s', 'deactivation-share-reason' , $slug ), ( $fs->is_plugin() ? fs_text_inline( 'deactivating', 'deactivating', $slug ) : fs_text_inline( 'switching', 'switching', $slug ) ) ) ) ?>:</strong></h3><ul id="reasons-list">' + reasonsHtml + '</ul></div>'
85
+ + ' </div>'
86
+ + ' <div class="fs-modal-footer">'
87
+ + ' <?php echo $anonymous_feedback_checkbox_html ?>'
88
+ + ' <a href="#" class="button button-secondary button-deactivate"></a>'
89
+ + ' <a href="#" class="button button-primary button-close"><?php fs_esc_js_echo_inline( 'Cancel', 'cancel', $slug ) ?></a>'
90
+ + ' </div>'
91
+ + ' </div>'
92
+ + '</div>',
93
+ $modal = $(modalHtml),
94
+ $deactivateLink = $('#the-list .deactivate > [data-module-id=<?php echo $fs->get_id() ?>].fs-module-id').prev(),
95
+ selectedReasonID = false,
96
+ redirectLink = '',
97
+ $anonymousFeedback = $modal.find( '.anonymous-feedback-label' ),
98
+ isAnonymous = <?php echo ( $is_anonymous ? 'true' : 'false' ); ?>,
99
+ otherReasonID = <?php echo Freemius::REASON_OTHER; ?>,
100
+ dontShareDataReasonID = <?php echo Freemius::REASON_DONT_LIKE_TO_SHARE_MY_INFORMATION; ?>,
101
+ deleteThemeUpdateData = <?php echo $fs->is_theme() && $fs->is_premium() && ! $fs->has_any_active_valid_license() ? 'true' : 'false' ?>,
102
+ $subscriptionCancellationModal = $( '.fs-modal-subscription-cancellation-<?php echo $fs->get_id() ?>' );
103
+
104
+ $modal.appendTo($('body'));
105
+
106
+ if ( 0 !== $subscriptionCancellationModal.length ) {
107
+ $subscriptionCancellationModal.on( '<?php echo $fs->get_action_tag( 'subscription_cancellation_action' ) ?>', function( evt, cancelSubscription ) {
108
+ if ( false === cancelSubscription ) {
109
+ showModal();
110
+
111
+ $subscriptionCancellationModal.trigger( 'closeModal' );
112
+ } else {
113
+ var $errorMessage = $subscriptionCancellationModal.find( '.notice-error' );
114
+
115
+ <?php
116
+ $subscription_cancellation_context = $fs->is_paid_trial() ?
117
+ fs_text_inline( 'trial', 'trial', $slug ) :
118
+ fs_text_inline( 'subscription', 'subscription', $slug );
119
+ ?>
120
+
121
+ $.ajax({
122
+ url : ajaxurl,
123
+ method : 'POST',
124
+ data : {
125
+ action : '<?php echo $fs->get_ajax_action( 'cancel_subscription_or_trial' ) ?>',
126
+ security : '<?php echo $fs->get_ajax_security( 'cancel_subscription_or_trial' ) ?>',
127
+ module_id: '<?php echo $fs->get_id() ?>'
128
+ },
129
+ beforeSend: function() {
130
+ $errorMessage.hide();
131
+
132
+ $subscriptionCancellationModal.find( '.fs-modal-footer .button' ).addClass( 'disabled' );
133
+ $subscriptionCancellationModal.find( '.fs-modal-footer .button-primary' ).text( '<?php echo esc_js(
134
+ sprintf( fs_text_inline( 'Cancelling %s...', 'cancelling-x' , $slug ), $subscription_cancellation_context )
135
+ ) ?>' );
136
+ },
137
+ success: function( result ) {
138
+ if ( result.success ) {
139
+ $subscriptionCancellationModal.removeClass( 'has-subscription-actions' );
140
+ $subscriptionCancellationModal.find( '.fs-modal-footer .button-primary' ).removeClass( 'warn' );
141
+
142
+ $subscriptionCancellationModal.remove();
143
+ showModal();
144
+ } else {
145
+ $errorMessage.find( '> p' ).html( result.error );
146
+ $errorMessage.show();
147
+
148
+ $subscriptionCancellationModal.find( '.fs-modal-footer .button' ).removeClass( 'disabled' );
149
+ $subscriptionCancellationModal.find( '.fs-modal-footer .button-primary' ).html( <?php echo json_encode( sprintf(
150
+ fs_text_inline( 'Cancel %s & Proceed', 'cancel-x-and-proceed', $slug ),
151
+ ucfirst( $subscription_cancellation_context )
152
+ ) ) ?> );
153
+ }
154
+ }
155
+ });
156
+ }
157
+ });
158
+ }
159
+
160
+ registerEventHandlers();
161
+
162
+ function registerEventHandlers() {
163
+ $deactivateLink.click(function (evt) {
164
+ evt.preventDefault();
165
+
166
+ redirectLink = $(this).attr('href');
167
+
168
+ if ( 0 == $subscriptionCancellationModal.length ) {
169
+ showModal();
170
+ } else {
171
+ $subscriptionCancellationModal.trigger( 'showModal' );
172
+ }
173
+ });
174
+
175
+ <?php
176
+ if ( ! $fs->is_plugin() ) {
177
+ /**
178
+ * For "theme" module type, the modal is shown when the current user clicks on
179
+ * the "Activate" button of any other theme. The "Activate" button is actually
180
+ * a link to the "Themes" page (/wp-admin/themes.php) containing query params
181
+ * that tell WordPress to deactivate the current theme and activate a different theme.
182
+ *
183
+ * @author Leo Fajardo (@leorw)
184
+ * @since 1.2.2
185
+ *
186
+ * @since 1.2.2.7 Don't trigger the deactivation feedback form if activating the premium version of the theme.
187
+ */
188
+ ?>
189
+ $('body').on('click', '.theme-browser .theme:not([data-slug=<?php echo $fs->get_premium_slug() ?>]) .theme-actions .button.activate', function (evt) {
190
+ evt.preventDefault();
191
+
192
+ redirectLink = $(this).attr('href');
193
+
194
+ showModal();
195
+ });
196
+ <?php
197
+ } ?>
198
+
199
+ $modal.on('input propertychange', '.reason-input input', function () {
200
+ if (!isOtherReasonSelected()) {
201
+ return;
202
+ }
203
+
204
+ var reason = $(this).val().trim();
205
+
206
+ /**
207
+ * If reason is not empty, remove the error-message class of the message container
208
+ * to change the message color back to default.
209
+ */
210
+ if (reason.length > 0) {
211
+ $('.message').removeClass('error-message');
212
+ enableDeactivateButton();
213
+ }
214
+ });
215
+
216
+ $modal.on('blur', '.reason-input input', function () {
217
+ var $userReason = $(this);
218
+
219
+ setTimeout(function () {
220
+ if (!isOtherReasonSelected()) {
221
+ return;
222
+ }
223
+
224
+ /**
225
+ * If reason is empty, add the error-message class to the message container
226
+ * to change the message color to red.
227
+ */
228
+ if (0 === $userReason.val().trim().length) {
229
+ $('.message').addClass('error-message');
230
+ disableDeactivateButton();
231
+ }
232
+ }, 150);
233
+ });
234
+
235
+ $modal.on('click', '.fs-modal-footer .button', function (evt) {
236
+ evt.preventDefault();
237
+
238
+ if ($(this).hasClass('disabled')) {
239
+ return;
240
+ }
241
+
242
+ var _parent = $(this).parents('.fs-modal:first');
243
+ var _this = $(this);
244
+
245
+ if (_this.hasClass('allow-deactivate')) {
246
+ var $radio = $('input[type="radio"]:checked');
247
+
248
+ if (0 === $radio.length) {
249
+ if ( ! deleteThemeUpdateData ) {
250
+ // If no selected reason, just deactivate the plugin.
251
+ window.location.href = redirectLink;
252
+ } else {
253
+ $.ajax({
254
+ url : ajaxurl,
255
+ method : 'POST',
256
+ data : {
257
+ action : '<?php echo $fs->get_ajax_action( 'delete_theme_update_data' ) ?>',
258
+ security : '<?php echo $fs->get_ajax_security( 'delete_theme_update_data' ) ?>',
259
+ module_id: '<?php echo $fs->get_id() ?>'
260
+ },
261
+ beforeSend: function() {
262
+ _parent.find( '.fs-modal-footer .button' ).addClass( 'disabled' );
263
+ _parent.find( '.fs-modal-footer .button-secondary' ).text( 'Processing...' );
264
+ },
265
+ complete : function() {
266
+ window.location.href = redirectLink;
267
+ }
268
+ });
269
+ }
270
+
271
+ return;
272
+ }
273
+
274
+ var $selected_reason = $radio.parents('li:first'),
275
+ $input = $selected_reason.find('textarea, input[type="text"]'),
276
+ userReason = ( 0 !== $input.length ) ? $input.val().trim() : '';
277
+
278
+ if (isOtherReasonSelected() && ( '' === userReason )) {
279
+ return;
280
+ }
281
+
282
+ $.ajax({
283
+ url : ajaxurl,
284
+ method : 'POST',
285
+ data : {
286
+ action : '<?php echo $fs->get_ajax_action( 'submit_uninstall_reason' ) ?>',
287
+ security : '<?php echo $fs->get_ajax_security( 'submit_uninstall_reason' ) ?>',
288
+ module_id : '<?php echo $fs->get_id() ?>',
289
+ reason_id : $radio.val(),
290
+ reason_info : userReason,
291
+ is_anonymous: isAnonymousFeedback()
292
+ },
293
+ beforeSend: function () {
294
+ _parent.find('.fs-modal-footer .button').addClass('disabled');
295
+ _parent.find('.fs-modal-footer .button-secondary').text('Processing...');
296
+ },
297
+ complete : function () {
298
+ // Do not show the dialog box, deactivate the plugin.
299
+ window.location.href = redirectLink;
300
+ }
301
+ });
302
+ } else if (_this.hasClass('button-deactivate')) {
303
+ // Change the Deactivate button's text and show the reasons panel.
304
+ _parent.find('.button-deactivate').addClass('allow-deactivate');
305
+
306
+ showPanel('reasons');
307
+ }
308
+ });
309
+
310
+ $modal.on('click', 'input[type="radio"]', function () {
311
+ var $selectedReasonOption = $( this );
312
+
313
+ // If the selection has not changed, do not proceed.
314
+ if (selectedReasonID === $selectedReasonOption.val())
315
+ return;
316
+
317
+ selectedReasonID = $selectedReasonOption.val();
318
+
319
+ if ( isAnonymous ) {
320
+ if ( isReasonSelected( dontShareDataReasonID ) ) {
321
+ $anonymousFeedback.hide();
322
+ } else {
323
+ $anonymousFeedback.show();
324
+ }
325
+ }
326
+
327
+ var _parent = $(this).parents('li:first');
328
+
329
+ $modal.find('.reason-input').remove();
330
+ $modal.find( '.internal-message' ).hide();
331
+ $modal.find('.button-deactivate').html('<?php echo esc_js( sprintf(
332
+ fs_text_inline( 'Submit & %s', 'deactivation-modal-button-submit' , $slug ),
333
+ $fs->is_plugin() ?
334
+ $deactivate_text :
335
+ sprintf( $activate_x_text, $theme_text )
336
+ ) ) ?>');
337
+
338
+ enableDeactivateButton();
339
+
340
+ if ( _parent.hasClass( 'has-internal-message' ) ) {
341
+ _parent.find( '.internal-message' ).show();
342
+ }
343
+
344
+ if (_parent.hasClass('has-input')) {
345
+ var inputType = _parent.data('input-type'),
346
+ inputPlaceholder = _parent.data('input-placeholder'),
347
+ reasonInputHtml = '<div class="reason-input"><span class="message"></span>' + ( ( 'textfield' === inputType ) ? '<input type="text" />' : '<textarea rows="5"></textarea>' ) + '</div>';
348
+
349
+ _parent.append($(reasonInputHtml));
350
+ _parent.find('input, textarea').attr('placeholder', inputPlaceholder).focus();
351
+
352
+ if (isOtherReasonSelected()) {
353
+ showMessage('<?php echo esc_js( fs_text_inline( 'Kindly tell us the reason so we can improve.', 'ask-for-reason-message' , $slug ) ); ?>');
354
+ disableDeactivateButton();
355
+ }
356
+ }
357
+ });
358
+
359
+ // If the user has clicked outside the window, cancel it.
360
+ $modal.on('click', function (evt) {
361
+ var $target = $(evt.target);
362
+
363
+ // If the user has clicked anywhere in the modal dialog, just return.
364
+ if ($target.hasClass('fs-modal-body') || $target.hasClass('fs-modal-footer')) {
365
+ return;
366
+ }
367
+
368
+ // If the user has not clicked the close button and the clicked element is inside the modal dialog, just return.
369
+ if (
370
+ ! $target.hasClass( 'button-close' ) &&
371
+ ( $target.parents( '.fs-modal-body' ).length > 0 || $target.parents( '.fs-modal-footer' ).length > 0 )
372
+ ) {
373
+ return;
374
+ }
375
+
376
+ closeModal();
377
+
378
+ return false;
379
+ });
380
+ }
381
+
382
+ function isAnonymousFeedback() {
383
+ if ( ! isAnonymous ) {
384
+ return false;
385
+ }
386
+
387
+ return ( isReasonSelected( dontShareDataReasonID ) || $anonymousFeedback.find( 'input' ).prop( 'checked' ) );
388
+ }
389
+
390
+ function isReasonSelected( reasonID ) {
391
+ // Get the selected radio input element.
392
+ var $selectedReasonOption = $modal.find('input[type="radio"]:checked');
393
+
394
+ return ( reasonID == $selectedReasonOption.val() );
395
+ }
396
+
397
+ function isOtherReasonSelected() {
398
+ return isReasonSelected( otherReasonID );
399
+ }
400
+
401
+ function showModal() {
402
+ resetModal();
403
+
404
+ // Display the dialog box.
405
+ $modal.addClass('active');
406
+
407
+ $('body').addClass('has-fs-modal');
408
+ }
409
+
410
+ function closeModal() {
411
+ $modal.removeClass('active');
412
+
413
+ $('body').removeClass('has-fs-modal');
414
+ }
415
+
416
+ function resetModal() {
417
+ selectedReasonID = false;
418
+
419
+ enableDeactivateButton();
420
+
421
+ // Uncheck all radio buttons.
422
+ $modal.find('input[type="radio"]').prop('checked', false);
423
+
424
+ // Remove all input fields ( textfield, textarea ).
425
+ $modal.find('.reason-input').remove();
426
+
427
+ $modal.find('.message').hide();
428
+
429
+ if ( isAnonymous ) {
430
+ $anonymousFeedback.find( 'input' ).prop( 'checked', false );
431
+
432
+ // Hide, since by default there is no selected reason.
433
+ $anonymousFeedback.hide();
434
+ }
435
+
436
+ var $deactivateButton = $modal.find('.button-deactivate');
437
+
438
+ /*
439
+ * If the modal dialog has no confirmation message, that is, it has only one panel, then ensure
440
+ * that clicking the deactivate button will actually deactivate the plugin.
441
+ */
442
+ if ( $modal.hasClass( 'no-confirmation-message' ) ) {
443
+ $deactivateButton.addClass( 'allow-deactivate' );
444
+
445
+ showPanel( 'reasons' );
446
+ } else {
447
+ $deactivateButton.removeClass( 'allow-deactivate' );
448
+
449
+ showPanel( 'confirm' );
450
+ }
451
+ }
452
+
453
+ function showMessage(message) {
454
+ $modal.find('.message').text(message).show();
455
+ }
456
+
457
+ function enableDeactivateButton() {
458
+ $modal.find('.button-deactivate').removeClass('disabled');
459
+ }
460
+
461
+ function disableDeactivateButton() {
462
+ $modal.find('.button-deactivate').addClass('disabled');
463
+ }
464
+
465
+ function showPanel(panelType) {
466
+ $modal.find( '.fs-modal-panel' ).removeClass( 'active' );
467
+ $modal.find( '[data-panel-id="' + panelType + '"]' ).addClass( 'active' );
468
+
469
+ updateButtonLabels();
470
+ }
471
+
472
+ function updateButtonLabels() {
473
+ var $deactivateButton = $modal.find( '.button-deactivate' );
474
+
475
+ // Reset the deactivate button's text.
476
+ if ( 'confirm' === getCurrentPanel() ) {
477
+ $deactivateButton.text( <?php echo json_encode( sprintf(
478
+ fs_text_inline( 'Yes - %s', 'deactivation-modal-button-confirm', $slug ),
479
+ $fs->is_plugin() ?
480
+ $deactivate_text :
481
+ sprintf( $activate_x_text, $theme_text )
482
+ ) ) ?> );
483
+ } else {
484
+ $deactivateButton.html( <?php echo json_encode( sprintf(
485
+ fs_text_inline('Skip & %s', 'skip-and-x', $slug ),
486
+ $fs->is_plugin() ?
487
+ $deactivate_text :
488
+ sprintf( $activate_x_text, $theme_text )
489
+ ) ) ?> );
490
+ }
491
+ }
492
+
493
+ function getCurrentPanel() {
494
+ return $modal.find('.fs-modal-panel.active').attr('data-panel-id');
495
+ }
496
+ })(jQuery);
497
+ </script>
wp-fail2ban-main.php CHANGED
@@ -11,6 +11,7 @@ namespace org\lecklider\charles\wordpress\wp_fail2ban;
11
  if ( !defined( 'ABSPATH' ) ) {
12
  exit;
13
  }
 
14
  /**
15
  * Defaults
16
  *
@@ -21,7 +22,75 @@ define( 'DEFAULT_WP_FAIL2BAN_AUTH_LOG', LOG_AUTH );
21
  define( 'DEFAULT_WP_FAIL2BAN_COMMENT_LOG', LOG_USER );
22
  define( 'DEFAULT_WP_FAIL2BAN_PINGBACK_LOG', LOG_USER );
23
  define( 'DEFAULT_WP_FAIL2BAN_PASSWORD_REQUEST_LOG', LOG_USER );
24
- define( 'DEFAULT_WP_FAIL2BAN_SPAM_LOG', LOG_USER );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  /**
26
  * Allow custom openlog options.
27
  * e.g. you may not want the PID if logging remotely.
@@ -54,52 +123,100 @@ if ( !defined( 'WP_FAIL2BAN_PASSWORD_REQUEST_LOG' ) ) {
54
  if ( !defined( 'WP_FAIL2BAN_SPAM_LOG' ) ) {
55
  define( 'WP_FAIL2BAN_SPAM_LOG', DEFAULT_WP_FAIL2BAN_SPAM_LOG );
56
  }
 
 
 
 
 
 
 
 
 
57
 
58
  if ( is_admin() ) {
59
  require 'admin/summary.php';
60
  } else {
61
  require dirname( __FILE__ ) . '/feature/lib.php';
62
  /**
63
- * @since 3.5.0 Refactored for unit testing
64
- * @since 1.0.0
65
- *
66
- * @param string $user_login
67
  */
68
- function wp_login( $user_login, $user )
69
- {
70
- openlog();
71
- syslog( LOG_INFO, "Accepted password for {$user_login}" );
72
- // @codeCoverageIgnoreEnd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  }
74
 
75
- add_action(
76
- 'wp_login',
77
- __NAMESPACE__ . '\\wp_login',
78
- 10,
79
- 2
80
- );
81
  /**
82
- * @since 3.5.0 Refactored for unit testing
83
- * @since 1.0.0
84
- *
85
- * @param string $username
86
- *
87
- * @wp-f2b-hard Authentication attempt for unknown user .*
88
- * @wp-f2b-hard XML-RPC authentication attempt for unknown user .*
89
- * @wp-f2b-soft Authentication failure for .*
90
- * @wp-f2b-soft XML-RPC authentication failure for .*
91
  */
92
- function wp_login_failed( $username )
93
- {
94
- global $wp_xmlrpc_server ;
95
- $msg = ( $wp_xmlrpc_server ? 'XML-RPC a' : 'A' );
96
- $msg .= ( wp_cache_get( $username, 'userlogins' ) ? "uthentication failure for {$username}" : "uthentication attempt for unknown user {$username}" );
97
- openlog();
98
- syslog( LOG_NOTICE, $msg );
99
- // @codeCoverageIgnoreEnd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  }
101
 
102
- add_action( 'wp_login_failed', __NAMESPACE__ . '\\wp_login_failed' );
103
  /**
104
  * Comments
105
  *
11
  if ( !defined( 'ABSPATH' ) ) {
12
  exit;
13
  }
14
+ // phpcs:disable Generic.Functions.FunctionCallArgumentSpacing
15
  /**
16
  * Defaults
17
  *
22
  define( 'DEFAULT_WP_FAIL2BAN_COMMENT_LOG', LOG_USER );
23
  define( 'DEFAULT_WP_FAIL2BAN_PINGBACK_LOG', LOG_USER );
24
  define( 'DEFAULT_WP_FAIL2BAN_PASSWORD_REQUEST_LOG', LOG_USER );
25
+ define( 'DEFAULT_WP_FAIL2BAN_SPAM_LOG', LOG_AUTH );
26
+ /**
27
+ * @since 4.0.5
28
+ */
29
+ define( 'DEFAULT_WP_FAIL2BAN_COMMENT_EXTRA_LOG', LOG_AUTH );
30
+ define( 'DEFAULT_WP_FAIL2BAN_PINGBACK_ERROR_LOG', LOG_AUTH );
31
+ register_activation_hook( WP_FAIL2BAN_FILE, function () {
32
+ foreach ( get_mu_plugins() as $plugin => $data ) {
33
+
34
+ if ( 'WP fail2ban' == substr( $data['Name'], 0, 11 ) ) {
35
+ $wp_f2b_ver = substr( WP_FAIL2BAN_VER, 0, strrpos( WP_FAIL2BAN_VER, '.' ) );
36
+ $wpf2b = 'WP fail2ban';
37
+ $error_msg = "<h1>Cannot activate {$wpf2b}</h1>";
38
+ $mu_file = WPMU_PLUGIN_DIR . '/' . $plugin;
39
+
40
+ if ( is_link( $mu_file ) ) {
41
+
42
+ if ( false === ($link = readlink( $mu_file )) || false === ($path = realpath( $mu_file )) ) {
43
+ $error_msg .= <<<__ERROR__
44
+ <h3>A broken symbolic link was found in <tt>mu-plugins</tt>:</h3>
45
+ <p><tt>{$mu_file}</tt></p>
46
+ __ERROR__;
47
+ } elseif ( WP_FAIL2BAN_FILE == $path ) {
48
+ // OK, we're linking to ourself
49
+ } else {
50
+ $mu_file = str_replace( '/', '/<wbr>', $mu_file );
51
+ $mu_file = substr( $mu_file, strlen( WPMU_PLUGIN_DIR ) - 1 );
52
+ $error_msg .= <<<__ERROR__
53
+ <h3>A conflicting symbolic link was found in <tt>mu-plugins</tt>:</h3>
54
+ <style>
55
+ table { text-align: center; }
56
+ td { width: 50%; }
57
+ th { font-size: 200%; }
58
+ td, th { font-family: monospace; }
59
+ span.tt { font-weight: bold; }
60
+ </style>
61
+ <table>
62
+ <tr>
63
+ <td>{$mu_file}</td>
64
+ <th>&DoubleRightArrow;</th>
65
+ <td>{$link}</td>
66
+ </tr>
67
+ <tr>
68
+ <td colspan="3"><span class="tt">&equiv;</span> <span>{$path}</span></td>
69
+ </tr>
70
+ <tr>
71
+ <td colspan="3"></td>
72
+ </tr>
73
+ </table>
74
+ __ERROR__;
75
+ }
76
+
77
+ } else {
78
+ $mu_file = str_replace( '/', '/<wbr>', $mu_file );
79
+ $mu_file = substr( $mu_file, strlen( WPMU_PLUGIN_DIR ) - 1 );
80
+ $error_msg .= <<<__ERROR__
81
+ <h3>A conflicting file was found in <tt>mu-plugins</tt>:</h3>
82
+ <p><tt>{$mu_file}</tt></p>
83
+ __ERROR__;
84
+ }
85
+
86
+ $error_msg .= "<p>Please see the <a href=\"https://docs.wp-fail2ban.com/en/{$wp_f2b_ver}/configuration.html#mu-plugins-support\" target=\"_blank\">documentation</a> for how to configure {$wpf2b} for <tt>mu-plugins</tt>.</p>";
87
+ $error_msg .= '<p>Click <a href="?">here</a> to return to the plugins page.</p>';
88
+ deactivate_plugins( plugin_basename( WP_FAIL2BAN_FILE ) );
89
+ wp_die( $error_msg );
90
+ }
91
+
92
+ }
93
+ } );
94
  /**
95
  * Allow custom openlog options.
96
  * e.g. you may not want the PID if logging remotely.
123
  if ( !defined( 'WP_FAIL2BAN_SPAM_LOG' ) ) {
124
  define( 'WP_FAIL2BAN_SPAM_LOG', DEFAULT_WP_FAIL2BAN_SPAM_LOG );
125
  }
126
+ /**
127
+ * @since 4.0.5
128
+ */
129
+ if ( !defined( 'WP_FAIL2BAN_COMMENT_EXTRA_LOG' ) ) {
130
+ define( 'WP_FAIL2BAN_COMMENT_EXTRA_LOG', DEFAULT_WP_FAIL2BAN_COMMENT_EXTRA_LOG );
131
+ }
132
+ if ( !defined( 'WP_FAIL2BAN_PINGBACK_ERROR_LOG' ) ) {
133
+ define( 'WP_FAIL2BAN_PINGBACK_ERROR_LOG', DEFAULT_WP_FAIL2BAN_PINGBACK_ERROR_LOG );
134
+ }
135
 
136
  if ( is_admin() ) {
137
  require 'admin/summary.php';
138
  } else {
139
  require dirname( __FILE__ ) . '/feature/lib.php';
140
  /**
141
+ * @since 4.0.5
 
 
 
142
  */
143
+
144
+ if ( !function_exists( __NAMESPACE__ . '\\wp_login' ) ) {
145
+ /**
146
+ * @since 4.1.0 Add REST support
147
+ * @since 3.5.0 Refactored for unit testing
148
+ * @since 1.0.0
149
+ *
150
+ * @param string $user_login
151
+ */
152
+ function wp_login( $user_login, $user )
153
+ {
154
+ global $wp_xmlrpc_server ;
155
+
156
+ if ( defined( 'REST_REQUEST' ) ) {
157
+ $action = 'WPF2B_ACTION_REST_AUTH_OK';
158
+ } elseif ( $wp_xmlrpc_server ) {
159
+ $action = 'WPF2B_ACTION_XMLRPC_AUTH_OK';
160
+ } else {
161
+ $action = 'WPF2B_ACTION_AUTH_OK';
162
+ }
163
+
164
+ openlog();
165
+ syslog( LOG_INFO, "Accepted password for {$user_login}" );
166
+ // @codeCoverageIgnoreEnd
167
+ }
168
+
169
+ add_action(
170
+ 'wp_login',
171
+ __NAMESPACE__ . '\\wp_login',
172
+ 10,
173
+ 2
174
+ );
175
  }
176
 
 
 
 
 
 
 
177
  /**
178
+ * @since 4.0.5
 
 
 
 
 
 
 
 
179
  */
180
+
181
+ if ( !function_exists( __NAMESPACE__ . '\\wp_login_failed' ) ) {
182
+ /**
183
+ * @since 4.1.0 Add REST support
184
+ * @since 3.5.0 Refactored for unit testing
185
+ * @since 1.0.0
186
+ *
187
+ * @param string $username
188
+ *
189
+ * @wp-f2b-hard Authentication attempt for unknown user .*
190
+ * @wp-f2b-hard REST authentication attempt for unknown user .*
191
+ * @wp-f2b-hard XML-RPC authentication attempt for unknown user .*
192
+ * @wp-f2b-soft Authentication failure for .*
193
+ * @wp-f2b-soft REST authentication failure for .*
194
+ * @wp-f2b-soft XML-RPC authentication failure for .*
195
+ */
196
+ function wp_login_failed( $username )
197
+ {
198
+ global $wp_xmlrpc_server ;
199
+
200
+ if ( defined( 'REST_REQUEST' ) ) {
201
+ $action = 'WPF2B_ACTION_REST_AUTH_FAIL';
202
+ $msg = 'REST a';
203
+ } elseif ( $wp_xmlrpc_server ) {
204
+ $action = 'WPF2B_ACTION_XMLRPC_AUTH_FAIL';
205
+ $msg = 'XML-RPC a';
206
+ } else {
207
+ $action = 'WPF2B_ACTION_AUTH_FAIL';
208
+ $msg = 'A';
209
+ }
210
+
211
+ $msg .= ( wp_cache_get( $username, 'userlogins' ) ? "uthentication failure for {$username}" : "uthentication attempt for unknown user {$username}" );
212
+ openlog();
213
+ syslog( LOG_NOTICE, $msg );
214
+ // @codeCoverageIgnoreEnd
215
+ }
216
+
217
+ add_action( 'wp_login_failed', __NAMESPACE__ . '\\wp_login_failed' );
218
  }
219
 
 
220
  /**
221
  * Comments
222
  *
wp-fail2ban.php CHANGED
@@ -5,14 +5,13 @@
5
  * Plugin URI: https://wp-fail2ban.com/
6
  * Description: Write a myriad of WordPress events to syslog for integration with fail2ban.
7
  * Text Domain: wp-fail2ban
8
- * Version: 4.0.2
9
  * Author: Charles Lecklider
10
  * Author URI: https://charles.lecklider.org/
11
  * License: GPLv2
12
  * SPDX-License-Identifier: GPL-2.0
13
  * Requires PHP: 5.3
14
  *
15
- * @fs_premium_only /admin/premium/, composer.json, composer.lock, /premium/, /vendor/autoload.php, /vendor/composer/, /vendor/geoip2/, /vendor/maxmind/, /vendor/maxmind-db/
16
  */
17
  /*
18
  * Copyright 2012-19 Charles Lecklider (email : wordpress@charles.lecklider.org)
@@ -36,15 +35,10 @@
36
  namespace org\lecklider\charles\wordpress\wp_fail2ban;
37
 
38
  /**
39
- * Guard for MU
40
- *
41
- * @since 4.0.0 Leave here for easy version bump
42
  */
43
- global $wp_fail2ban ;
44
- if ( empty($wp_fail2ban) && defined( 'WP_FAIL2BAN' ) ) {
45
- return;
46
- }
47
- define( 'WP_FAIL2BAN', '4.0.2' );
48
  /**
49
  * Freemius integration
50
  *
5
  * Plugin URI: https://wp-fail2ban.com/
6
  * Description: Write a myriad of WordPress events to syslog for integration with fail2ban.
7
  * Text Domain: wp-fail2ban
8
+ * Version: 4.1.0
9
  * Author: Charles Lecklider
10
  * Author URI: https://charles.lecklider.org/
11
  * License: GPLv2
12
  * SPDX-License-Identifier: GPL-2.0
13
  * Requires PHP: 5.3
14
  *
 
15
  */
16
  /*
17
  * Copyright 2012-19 Charles Lecklider (email : wordpress@charles.lecklider.org)
35
  namespace org\lecklider\charles\wordpress\wp_fail2ban;
36
 
37
  /**
38
+ * @since 4.0.5
 
 
39
  */
40
+ define( 'WP_FAIL2BAN_VER', '4.1.0' );
41
+ define( 'WP_FAIL2BAN_FILE', __FILE__ );
 
 
 
42
  /**
43
  * Freemius integration
44
  *