Limit Login Attempts Reloaded - Version 2.7.2

Version Description

  • Settings are moved to a separate page.
  • Fixed: login error message. https://wordpress.org/support/topic/how-to-change-login-error-message/
Download this release

Release Info

Developer wpchefgadget
Plugin Icon 128x128 Limit Login Attempts Reloaded
Version 2.7.2
Comparing to
See all releases

Code changes from version 2.7.1 to 2.7.2

assets/css/limit-login-attempts.css CHANGED
@@ -1,9 +1,12 @@
1
- .limit-login-page-settings .field-col {
2
- display: inline-block;
3
- margin-right: 20px; }
4
- .limit-login-page-settings .limit-login-log table {
5
- background-color: #fff; }
6
- .limit-login-page-settings .limit-login-log table th, .limit-login-page-settings .limit-login-log table td {
7
- padding: 10px; }
8
- .limit-login-page-settings .limit-login-log table tr:nth-child(even) {
9
- background-color: rgba(0, 0, 0, 0.09); }
 
 
 
1
+ .limit-login-page-settings .field-col {
2
+ display: inline-block;
3
+ margin-right: 20px; }
4
+ .limit-login-page-settings .limit-login-log table {
5
+ background-color: #fff; }
6
+ .limit-login-page-settings .limit-login-log table th, .limit-login-page-settings .limit-login-log table td {
7
+ padding: 10px; }
8
+ .limit-login-page-settings .limit-login-log table tr:nth-child(even) {
9
+ background-color: rgba(0, 0, 0, 0.09); }
10
+
11
+ #toplevel_page_limit-login-attempts .wp-menu-image img {
12
+ max-width: 22px; }
assets/img/icon-menu2.svg ADDED
@@ -0,0 +1 @@
 
1
+ <?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'><svg enable-background="new -0.6 -1 27 31" height="31px" version="1.1" viewBox="-0.6 -1 27 31" width="27px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs/><path clip-rule="evenodd" d="M13.1,29.8L13.1,29.8c-14.8-3.2-13-25-13-25 C7.9,5.7,12.4,0.8,13,0.1V0c0,0,0,0,0,0c0,0,0,0,0,0v0.1c0.6,0.7,5.2,5.6,12.9,4.7C26,4.8,27.9,26.6,13.1,29.8z M23.3,6.5 c-6.2,0.7-9.8-3-10.2-3.6V2.8c0,0,0,0,0,0c0,0,0,0,0,0v0.1C12.5,3.5,8.9,7.2,2.8,6.5c0,0-1.4,18.2,10.3,20.7h0 C24.8,24.7,23.3,6.5,23.3,6.5z M13.1,25.3L13.1,25.3c-0.3-0.1-0.5-0.1-0.7-0.2l8.8-9.7C20.2,19.5,18.1,24.2,13.1,25.3z M8.6,22.9 l13-14.1c0,0.7,0,1.8-0.1,3.1L10.4,24.2C9.7,23.9,9.1,23.4,8.6,22.9z M4.5,8.1c5.1,0.6,8.1-2.5,8.5-3V5c0,0,0,0,0,0c0,0,0,0,0,0v0.1 c0.3,0.4,2.6,2.8,6.5,3.1L7.3,21.3C3.9,16.1,4.5,8.1,4.5,8.1z" fill="#fff" fill-rule="evenodd"/></svg>
assets/js/limit-login-attempts.js CHANGED
@@ -1,8 +1,8 @@
1
- ;(function($){
2
- "use strict";
3
-
4
- $(document).ready(function(){
5
-
6
- });
7
-
8
  })(jQuery);
1
+ ;(function($){
2
+ "use strict";
3
+
4
+ $(document).ready(function(){
5
+
6
+ });
7
+
8
  })(jQuery);
assets/sass/limit-login-attempts.scss CHANGED
@@ -1,17 +1,25 @@
1
- .limit-login-page-settings {
2
- .field-col {
3
- display: inline-block;
4
- margin-right: 20px;
5
- }
6
- .limit-login-log {
7
- table {
8
- background-color: #fff;
9
- th, td {
10
- padding: 10px;
11
- }
12
- tr:nth-child(even) {
13
- background-color: rgba(#000, .09);
14
- }
15
- }
16
- }
 
 
 
 
 
 
 
 
17
  }
1
+ .limit-login-page-settings {
2
+ .field-col {
3
+ display: inline-block;
4
+ margin-right: 20px;
5
+ }
6
+ .limit-login-log {
7
+ table {
8
+ background-color: #fff;
9
+ th, td {
10
+ padding: 10px;
11
+ }
12
+ tr:nth-child(even) {
13
+ background-color: rgba(#000, .09);
14
+ }
15
+ }
16
+ }
17
+ }
18
+
19
+ #toplevel_page_limit-login-attempts {
20
+ .wp-menu-image {
21
+ img {
22
+ max-width: 22px;
23
+ }
24
+ }
25
  }
core/Helpers.php CHANGED
@@ -1,63 +1,63 @@
1
- <?php
2
-
3
- /**
4
- * Class LLA_Helpers
5
- */
6
- class LLA_Helpers {
7
-
8
- /**
9
- * @param string $msg
10
- */
11
- public static function show_error( $msg = '' ) {
12
- if( empty( $msg ) ) {
13
- return;
14
- }
15
-
16
- echo '<div id="message" class="updated fade"><p>' . $msg . '</p></div>';
17
- }
18
-
19
- /**
20
- * @param $log
21
- *
22
- * @return array
23
- */
24
- public static function sorted_log_by_date( $log ) {
25
- $new_log = array();
26
-
27
- if ( ! is_array( $log ) || empty( $log ) ) {
28
- return $new_log;
29
- }
30
-
31
- foreach ( $log as $ip => $users ) {
32
-
33
- if ( ! empty( $users ) ) {
34
- foreach ( $users as $user_name => $info ) {
35
-
36
- if ( is_array( $info ) ) { // For new plugin version
37
- $new_log[ $info['date'] ] = array(
38
- 'ip' => $ip,
39
- 'username' => $user_name,
40
- 'counter' => $info['counter'],
41
- 'gateway' => ( isset( $info['gateway'] ) ) ? $info['gateway'] : '-',
42
- 'unlocked' => !empty( $info['unlocked'] ),
43
- );
44
- } else { // For old plugin version
45
- $new_log[0] = array(
46
- 'ip' => $ip,
47
- 'username' => $user_name,
48
- 'counter' => $info,
49
- 'gateway' => '-',
50
- 'unlocked' => false,
51
- );
52
- }
53
-
54
- }
55
- }
56
-
57
- }
58
-
59
- krsort( $new_log );
60
-
61
- return $new_log;
62
- }
63
  }
1
+ <?php
2
+
3
+ /**
4
+ * Class LLA_Helpers
5
+ */
6
+ class LLA_Helpers {
7
+
8
+ /**
9
+ * @param string $msg
10
+ */
11
+ public static function show_error( $msg = '' ) {
12
+ if( empty( $msg ) ) {
13
+ return;
14
+ }
15
+
16
+ echo '<div id="message" class="updated fade"><p>' . $msg . '</p></div>';
17
+ }
18
+
19
+ /**
20
+ * @param $log
21
+ *
22
+ * @return array
23
+ */
24
+ public static function sorted_log_by_date( $log ) {
25
+ $new_log = array();
26
+
27
+ if ( ! is_array( $log ) || empty( $log ) ) {
28
+ return $new_log;
29
+ }
30
+
31
+ foreach ( $log as $ip => $users ) {
32
+
33
+ if ( ! empty( $users ) ) {
34
+ foreach ( $users as $user_name => $info ) {
35
+
36
+ if ( is_array( $info ) ) { // For new plugin version
37
+ $new_log[ $info['date'] ] = array(
38
+ 'ip' => $ip,
39
+ 'username' => $user_name,
40
+ 'counter' => $info['counter'],
41
+ 'gateway' => ( isset( $info['gateway'] ) ) ? $info['gateway'] : '-',
42
+ 'unlocked' => !empty( $info['unlocked'] ),
43
+ );
44
+ } else { // For old plugin version
45
+ $new_log[0] = array(
46
+ 'ip' => $ip,
47
+ 'username' => $user_name,
48
+ 'counter' => $info,
49
+ 'gateway' => '-',
50
+ 'unlocked' => false,
51
+ );
52
+ }
53
+
54
+ }
55
+ }
56
+
57
+ }
58
+
59
+ krsort( $new_log );
60
+
61
+ return $new_log;
62
+ }
63
  }
core/LimitLoginAttempts.php CHANGED
@@ -1,1285 +1,1290 @@
1
- <?php
2
-
3
- /**
4
- * Class Limit_Login_Attempts
5
- */
6
- class Limit_Login_Attempts
7
- {
8
- public $default_options = array(
9
- 'gdpr' => 0,
10
-
11
- /* Are we behind a proxy? */
12
- 'client_type' => LLA_DIRECT_ADDR,
13
-
14
- /* Lock out after this many tries */
15
- 'allowed_retries' => 4,
16
-
17
- /* Lock out for this many seconds */
18
- 'lockout_duration' => 1200, // 20 minutes
19
-
20
- /* Long lock out after this many lockouts */
21
- 'allowed_lockouts' => 4,
22
-
23
- /* Long lock out for this many seconds */
24
- 'long_duration' => 86400, // 24 hours,
25
-
26
- /* Reset failed attempts after this many seconds */
27
- 'valid_duration' => 43200, // 12 hours
28
-
29
- /* Also limit malformed/forged cookies? */
30
- 'cookies' => true,
31
-
32
- /* Notify on lockout. Values: '', 'log', 'email', 'log,email' */
33
- 'lockout_notify' => 'log',
34
-
35
- /* If notify by email, do so after this number of lockouts */
36
- 'notify_email_after' => 4,
37
-
38
- 'whitelist' => array(),
39
- 'whitelist_usernames' => array(),
40
- 'blacklist' => array(),
41
- 'blacklist_usernames' => array(),
42
- );
43
- /**
44
- * Admin options page slug
45
- * @var string
46
- */
47
- private $_options_page_slug = 'limit-login-attempts';
48
-
49
- /**
50
- * Errors messages
51
- *
52
- * @var array
53
- */
54
- public $_errors = array();
55
-
56
- public function __construct() {
57
- $this->hooks_init();
58
- }
59
-
60
- /**
61
- * Register wp hooks and filters
62
- */
63
- public function hooks_init() {
64
- add_action( 'plugins_loaded', array( $this, 'setup' ), 9999 );
65
- add_action( 'admin_enqueue_scripts', array( $this, 'enqueue' ) );
66
- add_filter( 'limit_login_whitelist_ip', array( $this, 'check_whitelist_ips' ), 10, 2 );
67
- add_filter( 'limit_login_whitelist_usernames', array( $this, 'check_whitelist_usernames' ), 10, 2 );
68
- add_filter( 'limit_login_blacklist_ip', array( $this, 'check_blacklist_ips' ), 10, 2 );
69
- add_filter( 'limit_login_blacklist_usernames', array( $this, 'check_blacklist_usernames' ), 10, 2 );
70
- }
71
-
72
- /**
73
- * Hook 'plugins_loaded'
74
- */
75
- public function setup() {
76
-
77
- // Load languages files
78
- load_plugin_textdomain( 'limit-login-attempts-reloaded', false, plugin_basename( dirname( __FILE__ ) ) . '/../languages' );
79
-
80
- // Check if installed old plugin
81
- $this->check_original_installed();
82
-
83
- if ( is_multisite() )
84
- require_once ABSPATH.'wp-admin/includes/plugin.php';
85
-
86
- $this->network_mode = is_multisite() && is_plugin_active_for_network('limit-login-attempts-reloaded/limit-login-attempts-reloaded.php');
87
-
88
-
89
- if ( $this->network_mode )
90
- {
91
- $this->allow_local_options = get_site_option( 'limit_login_allow_local_options', false );
92
- $this->use_local_options = $this->allow_local_options && get_option( 'limit_login_use_local_options', false );
93
- }
94
- else
95
- {
96
- $this->allow_local_options = true;
97
- $this->use_local_options = true;
98
- }
99
-
100
-
101
- // Setup default plugin options
102
- //$this->sanitize_options();
103
-
104
- add_action( 'wp_login_failed', array( $this, 'limit_login_failed' ) );
105
- add_filter( 'wp_authenticate_user', array( $this, 'wp_authenticate_user' ), 99999, 2 );
106
-
107
- add_filter( 'shake_error_codes', array( $this, 'failure_shake' ) );
108
- add_action( 'login_head', array( $this, 'add_error_message' ) );
109
- add_action( 'login_errors', array( $this, 'fixup_error_messages' ) );
110
-
111
- if ( $this->network_mode )
112
- add_action( 'network_admin_menu', array( $this, 'network_admin_menu' ) );
113
-
114
- if ( $this->allow_local_options )
115
- add_action( 'admin_menu', array( $this, 'admin_menu' ) );
116
-
117
- // Add notices for XMLRPC request
118
- add_filter( 'xmlrpc_login_error', array( $this, 'xmlrpc_error_messages' ) );
119
-
120
- // Add notices to woocommerce login page
121
- add_action( 'wp_head', array( $this, 'add_wc_notices' ) );
122
-
123
- /*
124
- * This action should really be changed to the 'authenticate' filter as
125
- * it will probably be deprecated. That is however only available in
126
- * later versions of WP.
127
- */
128
- add_action( 'wp_authenticate', array( $this, 'track_credentials' ), 10, 2 );
129
- add_action( 'authenticate', array( $this, 'authenticate_filter' ), 5, 3 );
130
-
131
- if ( defined('XMLRPC_REQUEST') && XMLRPC_REQUEST )
132
- add_action( 'init', array( $this, 'check_xmlrpc_lock' ) );
133
-
134
- add_action('wp_ajax_limit-login-unlock', array( $this, 'ajax_unlock' ) );
135
- }
136
-
137
- public function check_xmlrpc_lock()
138
- {
139
- if ( is_user_logged_in() || $this->is_ip_whitelisted() )
140
- return;
141
-
142
- if ( $this->is_ip_blacklisted() || !$this->is_limit_login_ok() )
143
- {
144
- header('HTTP/1.0 403 Forbidden');
145
- exit;
146
- }
147
- }
148
-
149
- public function check_whitelist_ips( $allow, $ip ) {
150
- return $this->ip_in_range( $ip, (array) $this->get_option( 'whitelist' ) );
151
- }
152
-
153
- public function check_whitelist_usernames( $allow, $username ) {
154
- return in_array( $username, (array) $this->get_option( 'whitelist_usernames' ) );
155
- }
156
-
157
- public function check_blacklist_ips( $allow, $ip ) {
158
- return $this->ip_in_range( $ip, (array) $this->get_option( 'blacklist' ) );
159
- }
160
-
161
- public function check_blacklist_usernames( $allow, $username ) {
162
- return in_array( $username, (array) $this->get_option( 'blacklist_usernames' ) );
163
- }
164
-
165
- public function ip_in_range( $ip, $list )
166
- {
167
- foreach ( $list as $range )
168
- {
169
- $range = array_map('trim', explode('-', $range) );
170
- if ( count( $range ) == 1 )
171
- {
172
- if ( (string)$ip === (string)$range[0] )
173
- return true;
174
- }
175
- else
176
- {
177
- $low = ip2long( $range[0] );
178
- $high = ip2long( $range[1] );
179
- $needle = ip2long( $ip );
180
-
181
- if ( $low === false || $high === false || $needle === false )
182
- continue;
183
-
184
- $low = (float)sprintf("%u",$low);
185
- $high = (float)sprintf("%u",$high);
186
- $needle = (float)sprintf("%u",$needle);
187
-
188
- if ( $needle >= $low && $needle <= $high )
189
- return true;
190
- }
191
- }
192
-
193
- return false;
194
- }
195
-
196
- /**
197
- * @param $error IXR_Error
198
- *
199
- * @return IXR_Error
200
- */
201
- public function xmlrpc_error_messages( $error ) {
202
-
203
- if ( ! class_exists( 'IXR_Error' ) ) {
204
- return $error;
205
- }
206
-
207
- if ( ! $this->is_limit_login_ok() ) {
208
- return new IXR_Error( 403, $this->error_msg() );
209
- }
210
-
211
- $ip = $this->get_address();
212
- $retries = $this->get_option( 'retries' );
213
- $valid = $this->get_option( 'retries_valid' );
214
-
215
- /* Should we show retries remaining? */
216
-
217
- if ( ! is_array( $retries ) || ! is_array( $valid ) ) {
218
- /* no retries at all */
219
- return $error;
220
- }
221
- if (
222
- (! isset( $retries[ $ip ] ) && ! isset( $retries[ $this->getHash($ip) ] )) ||
223
- (! isset( $valid[ $ip ] ) && ! isset( $valid[ $this->getHash($ip) ] )) ||
224
- (time() > $valid[ $ip ] && time() > $valid[ $this->getHash($ip) ])
225
-
226
- ) {
227
- /* no: no valid retries */
228
- return $error;
229
- }
230
- if (
231
- ( ((isset($retries[ $ip ]) ? $retries[ $ip ] : 0) + (isset($retries[ $this->getHash($ip) ]) ? $retries[ $this->getHash($ip) ] : 0)) % $this->get_option( 'allowed_retries' ) ) == 0
232
- ) {
233
- //* no: already been locked out for these retries */
234
- return $error;
235
- }
236
-
237
- $remaining = max( ( $this->get_option( 'allowed_retries' ) - ( ((isset($retries[ $ip ]) ? $retries[ $ip ] : 0) + (isset($retries[ $this->getHash($ip) ]) ? $retries[ $this->getHash($ip) ] : 0)) % $this->get_option( 'allowed_retries' ) ) ), 0 );
238
-
239
- return new IXR_Error( 403, sprintf( _n( "<strong>%d</strong> attempt remaining.", "<strong>%d</strong> attempts remaining.", $remaining, 'limit-login-attempts-reloaded' ), $remaining ) );
240
- }
241
-
242
- /**
243
- * Errors on WooCommerce account page
244
- */
245
- public function add_wc_notices() {
246
-
247
- global $limit_login_just_lockedout, $limit_login_nonempty_credentials, $limit_login_my_error_shown;
248
-
249
- if ( ! function_exists( 'is_account_page' ) || ! function_exists( 'wc_add_notice' ) ) {
250
- return;
251
- }
252
-
253
- /*
254
- * During lockout we do not want to show any other error messages (like
255
- * unknown user or empty password).
256
- */
257
- if ( empty( $_POST ) && ! $this->is_limit_login_ok() && ! $limit_login_just_lockedout ) {
258
- if ( is_account_page() ) {
259
- wc_add_notice( $this->error_msg(), 'error' );
260
- }
261
- }
262
-
263
- }
264
-
265
- /**
266
- * @param $user
267
- * @param $username
268
- * @param $password
269
- *
270
- * @return WP_Error | WP_User
271
- */
272
- public function authenticate_filter( $user, $username, $password ) {
273
-
274
- if ( ! empty( $username ) && ! empty( $password ) ) {
275
-
276
- $ip = $this->get_address();
277
-
278
- // Check if username is blacklisted
279
- if ( ! $this->is_username_whitelisted( $username ) && ! $this->is_ip_whitelisted( $ip ) &&
280
- ( $this->is_username_blacklisted( $username ) || $this->is_ip_blacklisted( $ip ) )
281
- ) {
282
-
283
- remove_filter( 'login_errors', array( $this, 'fixup_error_messages' ) );
284
- remove_filter( 'login_head', array( $this, 'add_error_message' ) );
285
- remove_filter( 'wp_login_failed', array( $this, 'limit_login_failed' ) );
286
- remove_filter( 'wp_authenticate_user', array( $this, 'wp_authenticate_user' ), 99999 );
287
- remove_filter( 'login_head', array( $this, 'add_error_message' ) );
288
- remove_filter( 'login_errors', array( $this, 'fixup_error_messages' ) );
289
-
290
- remove_filter( 'authenticate', 'wp_authenticate_username_password', 20 );
291
- remove_filter( 'authenticate', 'wp_authenticate_email_password', 20 );
292
-
293
- $user = new WP_Error();
294
- $user->add( 'username_blacklisted', "<strong>ERROR:</strong> Too many failed login attempts." );
295
-
296
- } elseif ( $this->is_username_whitelisted( $username ) || $this->is_ip_whitelisted( $ip ) ) {
297
-
298
- remove_filter( 'wp_login_failed', array( $this, 'limit_login_failed' ) );
299
- remove_filter( 'wp_authenticate_user', array( $this, 'wp_authenticate_user' ), 99999 );
300
- remove_filter( 'login_head', array( $this, 'add_error_message' ) );
301
- remove_filter( 'login_errors', array( $this, 'fixup_error_messages' ) );
302
-
303
- }
304
-
305
- }
306
-
307
- return $user;
308
- }
309
-
310
- /**
311
- * Check if the original plugin is installed
312
- */
313
- private function check_original_installed()
314
- {
315
- require_once( ABSPATH . '/wp-admin/includes/plugin.php' );
316
- if ( is_plugin_active('limit-login-attempts/limit-login-attempts.php') )
317
- {
318
- deactivate_plugins( 'limit-login-attempts/limit-login-attempts.php', true );
319
- //add_action('plugins_loaded', 'limit_login_setup', 99999);
320
- remove_action( 'plugins_loaded', 'limit_login_setup', 99999 );
321
- }
322
- }
323
-
324
- /**
325
- * Enqueue js and css
326
- */
327
- public function enqueue() {
328
- wp_enqueue_style( 'lla-main', LLA_PLUGIN_URL . '/assets/css/limit-login-attempts.css' );
329
- }
330
-
331
- /**
332
- * Add admin options page
333
- */
334
- public function network_admin_menu()
335
- {
336
- add_submenu_page( 'settings.php', 'Limit Login Attempts', 'Limit Login Attempts', 'manage_options', $this->_options_page_slug, array( $this, 'options_page' ) );
337
- }
338
-
339
- public function admin_menu()
340
- {
341
- add_options_page( 'Limit Login Attempts', 'Limit Login Attempts', 'manage_options', $this->_options_page_slug, array( $this, 'options_page' ) );
342
- }
343
-
344
- /**
345
- * Get the correct options page URI
346
- *
347
- * @return mixed
348
- */
349
- public function get_options_page_uri()
350
- {
351
- if ( is_network_admin() )
352
- return network_admin_url( 'settings.php?page=limit-login-attempts' );
353
-
354
- return menu_page_url( $this->_options_page_slug, false );
355
- }
356
-
357
- /**
358
- * Get option by name
359
- *
360
- * @param $option_name
361
- *
362
- * @return null
363
- */
364
- public function get_option( $option_name, $local = null )
365
- {
366
- if ( is_null( $local ) )
367
- $local = $this->use_local_options;
368
-
369
- $option = 'limit_login_'.$option_name;
370
-
371
- $func = $local ? 'get_option' : 'get_site_option';
372
- $value = $func( $option, null );
373
-
374
- if ( is_null( $value ) && isset( $this->default_options[ $option_name ] ) )
375
- $value = $this->default_options[ $option_name ];
376
-
377
- return $value;
378
- }
379
-
380
- public function update_option( $option_name, $value, $local = null )
381
- {
382
- if ( is_null( $local ) )
383
- $local = $this->use_local_options;
384
-
385
- $option = 'limit_login_'.$option_name;
386
-
387
- $func = $local ? 'update_option' : 'update_site_option';
388
-
389
- return $func( $option, $value );
390
- }
391
-
392
- public function add_option( $option_name, $value, $local=null )
393
- {
394
- if ( is_null( $local ) )
395
- $local = $this->use_local_options;
396
-
397
- $option = 'limit_login_'.$option_name;
398
-
399
- $func = $local ? 'add_option' : 'add_site_option';
400
-
401
- return $func( $option, $value, '', 'no' );
402
- }
403
-
404
- /**
405
- * Setup main options
406
- */
407
- public function sanitize_options()
408
- {
409
- $simple_int_options = array( 'allowed_retries', 'lockout_duration', 'valid_duration', 'allowed_lockouts', 'long_duration', 'notify_email_after');
410
- foreach ( $simple_int_options as $option )
411
- {
412
- $val = $this->get_option( $option );
413
- if ( (int)$val != $val || (int)$val <= 0 )
414
- $this->update_option( $option, 1 );
415
- }
416
- if ( $this->get_option('notify_email_after') > $this->get_option( 'allowed_lockouts' ) )
417
- $this->update_option( 'notify_email_after', $this->get_option( 'allowed_lockouts' ) );
418
-
419
- $args = explode( ',', $this->get_option( 'lockout_notify' ) );
420
- $args_allowed = explode( ',', LLA_LOCKOUT_NOTIFY_ALLOWED );
421
- $new_args = array_intersect( $args, $args_allowed );
422
-
423
- $this->update_option( 'lockout_notify', implode( ',', $new_args ) );
424
-
425
- $ctype = $this->get_option( 'client_type' );
426
- if ( $ctype != LLA_DIRECT_ADDR && $ctype != LLA_PROXY_ADDR )
427
- $this->update_option( 'client_type', LLA_DIRECT_ADDR );
428
- }
429
-
430
- /**
431
- * Check if it is ok to login
432
- *
433
- * @return bool
434
- */
435
- public function is_limit_login_ok() {
436
-
437
- $ip = $this->get_address();
438
-
439
- /* Check external whitelist filter */
440
- if ( $this->is_ip_whitelisted( $ip ) ) {
441
- return true;
442
- }
443
-
444
- /* lockout active? */
445
- $lockouts = $this->get_option( 'lockouts' );
446
-
447
- $a = $this->checkKey($lockouts, $ip);
448
- $b = $this->checkKey($lockouts, $this->getHash($ip));
449
- return (
450
- ! is_array( $lockouts ) ||
451
- (! isset( $lockouts[ $ip ] ) && ! isset( $lockouts[ $this->getHash($ip) ] )) ||
452
- (time() >= $a && time() >= $b ));
453
- }
454
-
455
- /**
456
- * Action when login attempt failed
457
- *
458
- * Increase nr of retries (if necessary). Reset valid value. Setup
459
- * lockout if nr of retries are above threshold. And more!
460
- *
461
- * A note on external whitelist: retries and statistics are still counted and
462
- * notifications done as usual, but no lockout is done.
463
- *
464
- * @param $username
465
- */
466
- public function limit_login_failed( $username ) {
467
-
468
- $ip = $this->get_address();
469
- $ipHash = $this->getHash($this->get_address());
470
-
471
- /* if currently locked-out, do not add to retries */
472
- $lockouts = $this->get_option( 'lockouts' );
473
-
474
- if ( ! is_array( $lockouts ) ) {
475
- $lockouts = array();
476
- }
477
-
478
- if ( (isset( $lockouts[ $ip ] ) && time() < $lockouts[ $ip ]) || (isset( $lockouts[ $ipHash ] ) && time() < $lockouts[ $ipHash ] )) {
479
- return;
480
- }
481
-
482
- /* Get the arrays with retries and retries-valid information */
483
- $retries = $this->get_option( 'retries' );
484
- $valid = $this->get_option( 'retries_valid' );
485
-
486
- if ( ! is_array( $retries ) ) {
487
- $retries = array();
488
- $this->add_option( 'retries', $retries );
489
- }
490
-
491
- if ( ! is_array( $valid ) ) {
492
- $valid = array();
493
- $this->add_option( 'retries_valid', $valid );
494
- }
495
-
496
- $gdpr = $this->get_option('gdpr');
497
- $ip = ($gdpr ? $ipHash : $ip);
498
- /* Check validity and add one to retries */
499
- if ( isset( $retries[ $ip ] ) && isset( $valid[ $ip ] ) && time() < $valid[ $ip ]) {
500
- $retries[ $ip ] ++;
501
- } else {
502
- $retries[ $ip ] = 1;
503
- }
504
- $valid[ $ip ] = time() + $this->get_option( 'valid_duration' );
505
-
506
- /* lockout? */
507
- if ( $retries[ $ip ] % $this->get_option( 'allowed_retries' ) != 0 ) {
508
- /*
509
- * Not lockout (yet!)
510
- * Do housecleaning (which also saves retry/valid values).
511
- */
512
- $this->cleanup( $retries, null, $valid );
513
-
514
- return;
515
- }
516
-
517
- /* lockout! */
518
- $whitelisted = $this->is_ip_whitelisted( $ip );
519
- $retries_long = $this->get_option( 'allowed_retries' ) * $this->get_option( 'allowed_lockouts' );
520
-
521
- /*
522
- * Note that retries and statistics are still counted and notifications
523
- * done as usual for whitelisted ips , but no lockout is done.
524
- */
525
- if ( $whitelisted ) {
526
- if ( $retries[ $ip ] >= $retries_long ) {
527
- unset( $retries[ $ip ] );
528
- unset( $valid[ $ip ] );
529
- }
530
- } else {
531
- global $limit_login_just_lockedout;
532
- $limit_login_just_lockedout = true;
533
- $gdpr = $this->get_option('gdpr');
534
- $index = ($gdpr ? $ipHash : $ip);
535
-
536
- /* setup lockout, reset retries as needed */
537
- if ( (isset($retries[ $ip ]) ? $retries[ $ip ] : 0) >= $retries_long || (isset($retries[ $ipHash ]) ? $retries[ $ipHash ] : 0) >= $retries_long ) {
538
- /* long lockout */
539
- $lockouts[ $index ] = time() + $this->get_option( 'long_duration' );
540
- unset( $retries[ $index ] );
541
- unset( $valid[ $index ] );
542
- } else {
543
- /* normal lockout */
544
- $lockouts[ $index ] = time() + $this->get_option( 'lockout_duration' );
545
- }
546
- }
547
-
548
- /* do housecleaning and save values */
549
- $this->cleanup( $retries, $lockouts, $valid );
550
-
551
- /* do any notification */
552
- $this->notify( $username );
553
-
554
- /* increase statistics */
555
- $total = $this->get_option( 'lockouts_total' );
556
- if ( $total === false || ! is_numeric( $total ) ) {
557
- $this->add_option( 'lockouts_total', 1 );
558
- } else {
559
- $this->update_option( 'lockouts_total', $total + 1 );
560
- }
561
- }
562
-
563
- /**
564
- * Handle notification in event of lockout
565
- *
566
- * @param $user
567
- */
568
- public function notify( $user ) {
569
- $args = explode( ',', $this->get_option( 'lockout_notify' ) );
570
-
571
- if ( empty( $args ) ) {
572
- return;
573
- }
574
-
575
- foreach ( $args as $mode ) {
576
- switch ( trim( $mode ) ) {
577
- case 'email':
578
- $this->notify_email( $user );
579
- break;
580
- case 'log':
581
- $this->notify_log( $user );
582
- break;
583
- }
584
- }
585
- }
586
-
587
- /**
588
- * Email notification of lockout to admin (if configured)
589
- *
590
- * @param $user
591
- */
592
- public function notify_email( $user ) {
593
- $ip = $this->get_address();
594
- $whitelisted = $this->is_ip_whitelisted( $ip );
595
-
596
- $retries = $this->get_option( 'retries' );
597
- if ( ! is_array( $retries ) ) {
598
- $retries = array();
599
- }
600
-
601
- /* check if we are at the right nr to do notification */
602
- if (
603
- (isset( $retries[ $ip ] ) || isset( $retries[ $this->getHash($ip) ] ))
604
- &&
605
- ( ( intval($retries[ $ip ] + $retries[ $this->getHash($ip) ]) / $this->get_option( 'allowed_retries' ) ) % $this->get_option( 'notify_email_after' ) ) != 0 ) {
606
- return;
607
- }
608
-
609
- /* Format message. First current lockout duration */
610
- if ( !isset( $retries[ $ip ] ) && !isset( $retries[ $this->getHash($ip) ] ) ) {
611
- /* longer lockout */
612
- $count = $this->get_option( 'allowed_retries' )
613
- * $this->get_option( 'allowed_lockouts' );
614
- $lockouts = $this->get_option( 'allowed_lockouts' );
615
- $time = round( $this->get_option( 'long_duration' ) / 3600 );
616
- $when = sprintf( _n( '%d hour', '%d hours', $time, 'limit-login-attempts-reloaded' ), $time );
617
- } else {
618
- /* normal lockout */
619
- $count = $retries[ $ip ] + $retries[ $this->getHash($ip) ];
620
- $lockouts = floor( ($count) / $this->get_option( 'allowed_retries' ) );
621
- $time = round( $this->get_option( 'lockout_duration' ) / 60 );
622
- $when = sprintf( _n( '%d minute', '%d minutes', $time, 'limit-login-attempts-reloaded' ), $time );
623
- }
624
-
625
- $blogname = $this->use_local_options ? get_option( 'blogname' ) : get_site_option( 'site_name' );
626
- $blogname = htmlspecialchars_decode( $blogname, ENT_QUOTES );
627
-
628
- if ( $whitelisted ) {
629
- $subject = sprintf( __( "[%s] Failed login attempts from whitelisted IP"
630
- , 'limit-login-attempts-reloaded' )
631
- , $blogname );
632
- } else {
633
- $subject = sprintf( __( "[%s] Too many failed login attempts"
634
- , 'limit-login-attempts-reloaded' )
635
- , $blogname );
636
- }
637
-
638
- $message = sprintf( __( "%d failed login attempts (%d lockout(s)) from IP: %s"
639
- , 'limit-login-attempts-reloaded' ) . "\r\n\r\n"
640
- , $count, $lockouts, $ip );
641
- if ( $user != '' ) {
642
- $message .= sprintf( __( "Last user attempted: %s", 'limit-login-attempts-reloaded' )
643
- . "\r\n\r\n", $user );
644
- }
645
- if ( $whitelisted ) {
646
- $message .= __( "IP was NOT blocked because of external whitelist.", 'limit-login-attempts-reloaded' );
647
- } else {
648
- $message .= sprintf( __( "IP was blocked for %s", 'limit-login-attempts-reloaded' ), $when );
649
- }
650
-
651
- $admin_email = $this->use_local_options ? get_option( 'admin_email' ) : get_site_option( 'admin_email' );
652
-
653
- @wp_mail( $admin_email, $subject, $message );
654
- }
655
-
656
- /**
657
- * Logging of lockout (if configured)
658
- *
659
- * @param $user_login
660
- *
661
- * @internal param $user
662
- */
663
- public function notify_log( $user_login ) {
664
-
665
- if ( ! $user_login ) {
666
- return;
667
- }
668
-
669
- $log = $option = $this->get_option( 'logged' );
670
- if ( ! is_array( $log ) ) {
671
- $log = array();
672
- }
673
- $ip = $this->get_address();
674
-
675
- $index = ($this->get_option('gdpr') ? $this->getHash($ip) : $ip );
676
- /* can be written much simpler, if you do not mind php warnings */
677
- if ( !isset( $log[ $index ] ) )
678
- $log[ $index ] = array();
679
-
680
- if ( !isset( $log[ $index ][ $user_login ] ) )
681
- $log[ $index ][ $user_login ] = array( 'counter' => 0 );
682
-
683
- elseif ( !is_array( $log[ $index ][ $user_login ] ) )
684
- $log[ $index ][ $user_login ] = array(
685
- 'counter' => $log[ $index ][ $user_login ],
686
- );
687
-
688
- $log[ $index ][ $user_login ]['counter']++;
689
- $log[ $index ][ $user_login ]['date'] = time();
690
-
691
- if ( isset( $_POST['woocommerce-login-nonce'] ) ) {
692
- $gateway = 'WooCommerce';
693
- } elseif ( isset( $GLOBALS['wp_xmlrpc_server'] ) && is_object( $GLOBALS['wp_xmlrpc_server'] ) ) {
694
- $gateway = 'XMLRPC';
695
- } else {
696
- $gateway = 'WP Login';
697
- }
698
-
699
- $log[ $index ][ $user_login ]['gateway'] = $gateway;
700
-
701
- if ( $option === false ) {
702
- $this->add_option( 'logged', $log );
703
- } else {
704
- $this->update_option( 'logged', $log );
705
- }
706
- }
707
-
708
- /**
709
- * Check if IP is whitelisted.
710
- *
711
- * This function allow external ip whitelisting using a filter. Note that it can
712
- * be called multiple times during the login process.
713
- *
714
- * Note that retries and statistics are still counted and notifications
715
- * done as usual for whitelisted ips , but no lockout is done.
716
- *
717
- * Example:
718
- * function my_ip_whitelist($allow, $ip) {
719
- * return ($ip == 'my-ip') ? true : $allow;
720
- * }
721
- * add_filter('limit_login_whitelist_ip', 'my_ip_whitelist', 10, 2);
722
- *
723
- * @param null $ip
724
- *
725
- * @return bool
726
- */
727
- public function is_ip_whitelisted( $ip = null ) {
728
-
729
- if ( is_null( $ip ) ) {
730
- $ip = $this->get_address();
731
- }
732
-
733
- $whitelisted = apply_filters( 'limit_login_whitelist_ip', false, $ip );
734
-
735
- return ( $whitelisted === true );
736
- }
737
-
738
- public function is_username_whitelisted( $username ) {
739
-
740
- if ( empty( $username ) ) {
741
- return false;
742
- }
743
-
744
- $whitelisted = apply_filters( 'limit_login_whitelist_usernames', false, $username );
745
-
746
- return ( $whitelisted === true );
747
- }
748
-
749
- public function is_ip_blacklisted( $ip = null ) {
750
-
751
- if ( is_null( $ip ) ) {
752
- $ip = $this->get_address();
753
- }
754
-
755
- $whitelisted = apply_filters( 'limit_login_blacklist_ip', false, $ip );
756
-
757
- return ( $whitelisted === true );
758
- }
759
-
760
- public function is_username_blacklisted( $username ) {
761
-
762
- if ( empty( $username ) ) {
763
- return false;
764
- }
765
-
766
- $whitelisted = apply_filters( 'limit_login_blacklist_usernames', false, $username );
767
-
768
- return ( $whitelisted === true );
769
- }
770
-
771
- /**
772
- * Filter: allow login attempt? (called from wp_authenticate())
773
- *
774
- * @param $user WP_User
775
- * @param $password
776
- *
777
- * @return \WP_Error
778
- */
779
- public function wp_authenticate_user( $user, $password ) {
780
-
781
- if ( is_wp_error( $user ) ||
782
- $this->check_whitelist_ips( false, $this->get_address() ) ||
783
- $this->check_whitelist_usernames( false, $user->user_login ) ||
784
- $this->is_limit_login_ok()
785
- ) {
786
-
787
- return $user;
788
- }
789
-
790
- $error = new WP_Error();
791
-
792
- global $limit_login_my_error_shown;
793
- $limit_login_my_error_shown = true;
794
-
795
- if ( $this->is_username_blacklisted( $user->user_login ) || $this->is_ip_blacklisted( $this->get_address() ) ) {
796
- $error->add( 'username_blacklisted', "<strong>ERROR:</strong> Too many failed login attempts." );
797
- } else {
798
- // This error should be the same as in "shake it" filter below
799
- $error->add( 'too_many_retries', $this->error_msg() );
800
- }
801
-
802
- return $error;
803
- }
804
-
805
- /**
806
- * Filter: add this failure to login page "Shake it!"
807
- *
808
- * @param $error_codes
809
- *
810
- * @return array
811
- */
812
- public function failure_shake( $error_codes ) {
813
- $error_codes[] = 'too_many_retries';
814
- $error_codes[] = 'username_blacklisted';
815
-
816
- return $error_codes;
817
- }
818
-
819
- /**
820
- * Keep track of if user or password are empty, to filter errors correctly
821
- *
822
- * @param $user
823
- * @param $password
824
- */
825
- public function track_credentials( $user, $password ) {
826
- global $limit_login_nonempty_credentials;
827
-
828
- $limit_login_nonempty_credentials = ( ! empty( $user ) && ! empty( $password ) );
829
- }
830
-
831
- /**
832
- * Should we show errors and messages on this page?
833
- *
834
- * @return bool
835
- */
836
- public function login_show_msg() {
837
- if ( isset( $_GET['key'] ) ) {
838
- /* reset password */
839
- return false;
840
- }
841
-
842
- $action = isset( $_REQUEST['action'] ) ? $_REQUEST['action'] : '';
843
-
844
- return ( $action != 'lostpassword' && $action != 'retrievepassword'
845
- && $action != 'resetpass' && $action != 'rp'
846
- && $action != 'register' );
847
- }
848
-
849
- /**
850
- * Construct informative error message
851
- *
852
- * @return string
853
- */
854
- public function error_msg() {
855
- $ip = $this->get_address();
856
- $lockouts = $this->get_option( 'lockouts' );
857
- $a = $this->checkKey($lockouts, $ip);
858
- $b = $this->checkKey($lockouts, $this->getHash($ip));
859
-
860
- $msg = __( '<strong>ERROR</strong>: Too many failed login attempts.', 'limit-login-attempts-reloaded' ) . ' ';
861
-
862
- if (
863
- ! is_array( $lockouts ) ||
864
- ( ! isset( $lockouts[ $ip ] ) && ! isset( $lockouts[$this->getHash($ip)]) ) ||
865
- (time() >= $a && time() >= $b)
866
- ){
867
- /* Huh? No timeout active? */
868
- $msg .= __( 'Please try again later.', 'limit-login-attempts-reloaded' );
869
-
870
- return $msg;
871
- }
872
-
873
- $when = ceil( ( ($a > $b ? $a : $b) - time() ) / 60 );
874
- if ( $when > 60 ) {
875
- $when = ceil( $when / 60 );
876
- $msg .= sprintf( _n( 'Please try again in %d hour.', 'Please try again in %d hours.', $when, 'limit-login-attempts-reloaded' ), $when );
877
- } else {
878
- $msg .= sprintf( _n( 'Please try again in %d minute.', 'Please try again in %d minutes.', $when, 'limit-login-attempts-reloaded' ), $when );
879
- }
880
-
881
- return $msg;
882
- }
883
-
884
- /**
885
- * Add a message to login page when necessary
886
- */
887
- public function add_error_message() {
888
- global $error, $limit_login_my_error_shown;
889
-
890
- if ( ! $this->login_show_msg() || $limit_login_my_error_shown ) {
891
- return;
892
- }
893
-
894
- $msg = $this->get_message();
895
-
896
- if ( $msg != '' ) {
897
- $limit_login_my_error_shown = true;
898
- $error .= $msg;
899
- }
900
-
901
- return;
902
- }
903
-
904
- /**
905
- * Fix up the error message before showing it
906
- *
907
- * @param $content
908
- *
909
- * @return string
910
- */
911
- public function fixup_error_messages( $content ) {
912
- global $limit_login_just_lockedout, $limit_login_nonempty_credentials, $limit_login_my_error_shown;
913
-
914
- if ( ! $this->login_show_msg() ) {
915
- return $content;
916
- }
917
-
918
- /*
919
- * During lockout we do not want to show any other error messages (like
920
- * unknown user or empty password).
921
- */
922
- if ( ! $this->is_limit_login_ok() && ! $limit_login_just_lockedout ) {
923
- return $this->error_msg();
924
- }
925
-
926
- /*
927
- * We want to filter the messages 'Invalid username' and
928
- * 'Invalid password' as that is an information leak regarding user
929
- * account names (prior to WP 2.9?).
930
- *
931
- * Also, if more than one error message, put an extra <br /> tag between
932
- * them.
933
- */
934
- $msgs = explode( "<br />\n", $content );
935
-
936
- if ( strlen( end( $msgs ) ) == 0 ) {
937
- /* remove last entry empty string */
938
- array_pop( $msgs );
939
- }
940
-
941
- $count = count( $msgs );
942
- $my_warn_count = $limit_login_my_error_shown ? 1 : 0;
943
-
944
- if ( $limit_login_nonempty_credentials && $count > $my_warn_count ) {
945
- /* Replace error message, including ours if necessary */
946
- $content = __( '<strong>ERROR</strong>: Incorrect username or password.', 'limit-login-attempts-reloaded' ) . "<br />\n";
947
-
948
- if ( $limit_login_my_error_shown || $this->get_message() ) {
949
- $content .= "<br />\n" . $this->get_message() . "<br />\n";
950
- }
951
-
952
- return $content;
953
- } elseif ( $count <= 1 ) {
954
- return $content;
955
- }
956
-
957
- $new = '';
958
- while ( $count -- > 0 ) {
959
- $new .= array_shift( $msgs ) . "<br />\n";
960
- if ( $count > 0 ) {
961
- $new .= "<br />\n";
962
- }
963
- }
964
-
965
- return $new;
966
- }
967
-
968
- public function fixup_error_messages_wc( \WP_Error $error ) {
969
- $error->add( 1, __( 'WC Error' ) );
970
- }
971
-
972
- /**
973
- * Return current (error) message to show, if any
974
- *
975
- * @return string
976
- */
977
- public function get_message() {
978
- /* Check external whitelist */
979
- if ( $this->is_ip_whitelisted() ) {
980
- return '';
981
- }
982
-
983
- /* Is lockout in effect? */
984
- if ( ! $this->is_limit_login_ok() ) {
985
- return $this->error_msg();
986
- }
987
-
988
- return $this->retries_remaining_msg();
989
- }
990
-
991
- /**
992
- * Construct retries remaining message
993
- *
994
- * @return string
995
- */
996
- public function retries_remaining_msg() {
997
- $ip = $this->get_address();
998
- $retries = $this->get_option( 'retries' );
999
- $valid = $this->get_option( 'retries_valid' );
1000
- $a = $this->checkKey($retries, $ip);
1001
- $b = $this->checkKey($retries, $this->getHash($ip));
1002
- $c = $this->checkKey($valid, $ip);
1003
- $d = $this->checkKey($valid, $this->getHash($ip));
1004
-
1005
- /* Should we show retries remaining? */
1006
- if ( ! is_array( $retries ) || ! is_array( $valid ) ) {
1007
- /* no retries at all */
1008
- return '';
1009
- }
1010
- if (
1011
- (! isset( $retries[ $ip ] ) && ! isset( $retries[ $this->getHash($ip) ] )) ||
1012
- (! isset( $valid[ $ip ] ) && ! isset( $valid[ $this->getHash($ip) ] )) ||
1013
- ( time() > $c && time() > $d )
1014
- ) {
1015
- /* no: no valid retries */
1016
- return '';
1017
- }
1018
- if (
1019
- ( $a % $this->get_option( 'allowed_retries' ) ) == 0 &&
1020
- ( $b % $this->get_option( 'allowed_retries' ) ) == 0
1021
- ) {
1022
- /* no: already been locked out for these retries */
1023
- return '';
1024
- }
1025
-
1026
- $remaining = max( ( $this->get_option( 'allowed_retries' ) - ( ($a + $b) % $this->get_option( 'allowed_retries' ) ) ), 0 );
1027
-
1028
- return sprintf( _n( "<strong>%d</strong> attempt remaining.", "<strong>%d</strong> attempts remaining.", $remaining, 'limit-login-attempts-reloaded' ), $remaining );
1029
- }
1030
-
1031
- /**
1032
- * Get correct remote address
1033
- *
1034
- * @param string $type_name
1035
- *
1036
- * @return string
1037
- */
1038
- public function get_address() {
1039
-
1040
- if ( !empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) && filter_var( $_SERVER['HTTP_X_FORWARDED_FOR'], FILTER_VALIDATE_IP ) )
1041
- $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
1042
-
1043
- elseif ( !empty( $_SERVER['HTTP_X_SUCURI_CLIENTIP'] ) && filter_var( $_SERVER['HTTP_X_SUCURI_CLIENTIP'], FILTER_VALIDATE_IP ) )
1044
- $ip = $_SERVER['HTTP_X_SUCURI_CLIENTIP'];
1045
-
1046
- elseif ( isset( $_SERVER['REMOTE_ADDR'] ) )
1047
- $ip = $_SERVER['REMOTE_ADDR'];
1048
-
1049
- else
1050
- $ip = '';
1051
-
1052
- $ip = preg_replace('/^(\d+\.\d+\.\d+\.\d+):\d+$/', '\1', $ip);
1053
-
1054
- return $ip;
1055
- }
1056
-
1057
- /**
1058
- * Clean up old lockouts and retries, and save supplied arrays
1059
- *
1060
- * @param null $retries
1061
- * @param null $lockouts
1062
- * @param null $valid
1063
- */
1064
- public function cleanup( $retries = null, $lockouts = null, $valid = null ) {
1065
- $now = time();
1066
- $lockouts = ! is_null( $lockouts ) ? $lockouts : $this->get_option( 'lockouts' );
1067
-
1068
- /* remove old lockouts */
1069
- if ( is_array( $lockouts ) ) {
1070
- foreach ( $lockouts as $ip => $lockout ) {
1071
- if ( $lockout < $now ) {
1072
- unset( $lockouts[ $ip ] );
1073
- }
1074
- }
1075
- $this->update_option( 'lockouts', $lockouts );
1076
- }
1077
-
1078
- /* remove retries that are no longer valid */
1079
- $valid = ! is_null( $valid ) ? $valid : $this->get_option( 'retries_valid' );
1080
- $retries = ! is_null( $retries ) ? $retries : $this->get_option( 'retries' );
1081
- if ( ! is_array( $valid ) || ! is_array( $retries ) ) {
1082
- return;
1083
- }
1084
-
1085
- foreach ( $valid as $ip => $lockout ) {
1086
- if ( $lockout < $now ) {
1087
- unset( $valid[ $ip ] );
1088
- unset( $retries[ $ip ] );
1089
- }
1090
- }
1091
-
1092
- /* go through retries directly, if for some reason they've gone out of sync */
1093
- foreach ( $retries as $ip => $retry ) {
1094
- if ( ! isset( $valid[ $ip ] ) ) {
1095
- unset( $retries[ $ip ] );
1096
- }
1097
- }
1098
-
1099
- $this->update_option( 'retries', $retries );
1100
- $this->update_option( 'retries_valid', $valid );
1101
- }
1102
-
1103
- /**
1104
- * Render admin options page
1105
- */
1106
- public function options_page() {
1107
- $this->use_local_options = !is_network_admin();
1108
- $this->cleanup();
1109
-
1110
- if( !empty( $_POST ) )
1111
- {
1112
- check_admin_referer( 'limit-login-attempts-options' );
1113
-
1114
- if ( is_network_admin() )
1115
- $this->update_option( 'allow_local_options', !empty($_POST['allow_local_options']) );
1116
-
1117
- elseif ( $this->network_mode )
1118
- $this->update_option( 'use_local_options', empty($_POST['use_global_options']) );
1119
-
1120
- /* Should we support GDPR */
1121
- if( isset( $_POST[ 'gdpr' ] ) )
1122
- {
1123
- $this->update_option( 'gdpr', 1 );
1124
- }
1125
- else {
1126
- $this->update_option( 'gdpr', 0 );
1127
- }
1128
-
1129
- /* Should we clear log? */
1130
- if( isset( $_POST[ 'clear_log' ] ) )
1131
- {
1132
- $this->update_option( 'logged', '' );
1133
- $this->show_error( __( 'Cleared IP log', 'limit-login-attempts-reloaded' ) );
1134
- }
1135
-
1136
- /* Should we reset counter? */
1137
- if( isset( $_POST[ 'reset_total' ] ) )
1138
- {
1139
- $this->update_option( 'lockouts_total', 0 );
1140
- $this->show_error( __( 'Reset lockout count', 'limit-login-attempts-reloaded' ) );
1141
- }
1142
-
1143
- /* Should we restore current lockouts? */
1144
- if( isset( $_POST[ 'reset_current' ] ) )
1145
- {
1146
- $this->update_option( 'lockouts', array() );
1147
- $this->show_error( __( 'Cleared current lockouts', 'limit-login-attempts-reloaded' ) );
1148
- }
1149
-
1150
- /* Should we update options? */
1151
- if( isset( $_POST[ 'update_options' ] ) )
1152
- {
1153
- $this->update_option('allowed_retries', (int)$_POST['allowed_retries'] );
1154
- $this->update_option('lockout_duration', (int)$_POST['lockout_duration'] * 60 );
1155
- $this->update_option('valid_duration', (int)$_POST['valid_duration'] * 3600 );
1156
- $this->update_option('allowed_lockouts', (int)$_POST['allowed_lockouts'] );
1157
- $this->update_option('long_duration', (int)$_POST['long_duration'] * 3600 );
1158
- $this->update_option('notify_email_after', (int)$_POST['email_after'] );
1159
-
1160
- $white_list_ips = ( !empty( $_POST['lla_whitelist_ips'] ) ) ? explode("\n", str_replace("\r", "", stripslashes($_POST['lla_whitelist_ips']) ) ) : array();
1161
-
1162
- if( !empty( $white_list_ips ) ) {
1163
- foreach( $white_list_ips as $key => $ip ) {
1164
- if( '' == $ip ) {
1165
- unset( $white_list_ips[ $key ] );
1166
- }
1167
- }
1168
- }
1169
- $this->update_option('whitelist', $white_list_ips );
1170
-
1171
- $white_list_usernames = ( !empty( $_POST['lla_whitelist_usernames'] ) ) ? explode("\n", str_replace("\r", "", stripslashes($_POST['lla_whitelist_usernames']) ) ) : array();
1172
-
1173
- if( !empty( $white_list_usernames ) ) {
1174
- foreach( $white_list_usernames as $key => $ip ) {
1175
- if( '' == $ip ) {
1176
- unset( $white_list_usernames[ $key ] );
1177
- }
1178
- }
1179
- }
1180
- $this->update_option('whitelist_usernames', $white_list_usernames );
1181
-
1182
- $black_list_ips = ( !empty( $_POST['lla_blacklist_ips'] ) ) ? explode("\n", str_replace("\r", "", stripslashes($_POST['lla_blacklist_ips']) ) ) : array();
1183
-
1184
- if( !empty( $black_list_ips ) ) {
1185
- foreach( $black_list_ips as $key => $ip ) {
1186
- $range = array_map('trim', explode('-', $ip) );
1187
- if ( count( $range ) > 1 && (float)sprintf("%u",ip2long($range[0])) > (float)sprintf("%u",ip2long($range[1]))) {
1188
- $this->show_error( __( 'The "'. $ip .'" IP range is invalid', 'limit-login-attempts-reloaded' ) );
1189
- }
1190
- if( '' == $ip ) {
1191
- unset( $black_list_ips[ $key ] );
1192
- }
1193
- }
1194
- }
1195
- $this->update_option('blacklist', $black_list_ips );
1196
-
1197
- $black_list_usernames = ( !empty( $_POST['lla_blacklist_usernames'] ) ) ? explode("\n", str_replace("\r", "", stripslashes($_POST['lla_blacklist_usernames']) ) ) : array();
1198
-
1199
- if( !empty( $black_list_usernames ) ) {
1200
- foreach( $black_list_usernames as $key => $ip ) {
1201
- if( '' == $ip ) {
1202
- unset( $black_list_usernames[ $key ] );
1203
- }
1204
- }
1205
- }
1206
- $this->update_option('blacklist_usernames', $black_list_usernames );
1207
-
1208
- $notify_methods = array();
1209
- if( isset( $_POST[ 'lockout_notify_log' ] ) ) {
1210
- $notify_methods[] = 'log';
1211
- }
1212
- if( isset( $_POST[ 'lockout_notify_email' ] ) ) {
1213
- $notify_methods[] = 'email';
1214
- }
1215
- $this->update_option('lockout_notify', implode( ',', $notify_methods ) );
1216
-
1217
- $this->sanitize_options();
1218
-
1219
- $this->show_error( __( 'Options saved.', 'limit-login-attempts-reloaded' ) );
1220
- }
1221
- }
1222
-
1223
- include_once( LLA_PLUGIN_DIR . '/views/options-page.php' );
1224
- }
1225
-
1226
- public function ajax_unlock()
1227
- {
1228
- check_ajax_referer('limit-login-unlock', 'sec');
1229
- $ip = (string)@$_POST['ip'];
1230
-
1231
- $lockouts = (array)$this->get_option('lockouts');
1232
-
1233
- if ( isset( $lockouts[ $ip ] ) )
1234
- {
1235
- unset( $lockouts[ $ip ] );
1236
- $this->update_option( 'lockouts', $lockouts );
1237
- }
1238
-
1239
- //save to log
1240
- $user_login = @(string)$_POST['username'];
1241
- $log = $this->get_option( 'logged' );
1242
-
1243
- if ( @$log[ $ip ][ $user_login ] )
1244
- {
1245
- if ( !is_array( $log[ $ip ][ $user_login ] ) )
1246
- $log[ $ip ][ $user_login ] = array(
1247
- 'counter' => $log[ $ip ][ $user_login ],
1248
- );
1249
- $log[ $ip ][ $user_login ]['unlocked'] = true;
1250
-
1251
- $this->update_option( 'logged', $log );
1252
- }
1253
-
1254
- header('Content-Type: application/json');
1255
- echo 'true';
1256
- exit;
1257
- }
1258
-
1259
- /**
1260
- * Show error message
1261
- *
1262
- * @param $msg
1263
- */
1264
- public function show_error( $msg ) {
1265
- LLA_Helpers::show_error( $msg );
1266
- }
1267
-
1268
- /**
1269
- * returns IP with its md5 value
1270
- */
1271
- private function getHash($str)
1272
- {
1273
- return md5($str);
1274
- }
1275
-
1276
- /**
1277
- * @param $arr - array
1278
- * @param $k - key
1279
- * @return int array value at given index or zero
1280
- */
1281
- private function checkKey($arr, $k)
1282
- {
1283
- return isset($arr[$k]) ? $arr[$k] : 0;
1284
- }
 
 
 
 
 
1285
  }
1
+ <?php
2
+
3
+ /**
4
+ * Class Limit_Login_Attempts
5
+ */
6
+ class Limit_Login_Attempts
7
+ {
8
+ public $default_options = array(
9
+ 'gdpr' => 0,
10
+
11
+ /* Are we behind a proxy? */
12
+ 'client_type' => LLA_DIRECT_ADDR,
13
+
14
+ /* Lock out after this many tries */
15
+ 'allowed_retries' => 4,
16
+
17
+ /* Lock out for this many seconds */
18
+ 'lockout_duration' => 1200, // 20 minutes
19
+
20
+ /* Long lock out after this many lockouts */
21
+ 'allowed_lockouts' => 4,
22
+
23
+ /* Long lock out for this many seconds */
24
+ 'long_duration' => 86400, // 24 hours,
25
+
26
+ /* Reset failed attempts after this many seconds */
27
+ 'valid_duration' => 43200, // 12 hours
28
+
29
+ /* Also limit malformed/forged cookies? */
30
+ 'cookies' => true,
31
+
32
+ /* Notify on lockout. Values: '', 'log', 'email', 'log,email' */
33
+ 'lockout_notify' => 'log',
34
+
35
+ /* If notify by email, do so after this number of lockouts */
36
+ 'notify_email_after' => 4,
37
+
38
+ 'whitelist' => array(),
39
+ 'whitelist_usernames' => array(),
40
+ 'blacklist' => array(),
41
+ 'blacklist_usernames' => array(),
42
+ );
43
+ /**
44
+ * Admin options page slug
45
+ * @var string
46
+ */
47
+ private $_options_page_slug = 'limit-login-attempts';
48
+
49
+ /**
50
+ * Errors messages
51
+ *
52
+ * @var array
53
+ */
54
+ public $_errors = array();
55
+
56
+ public function __construct() {
57
+ $this->hooks_init();
58
+ }
59
+
60
+ /**
61
+ * Register wp hooks and filters
62
+ */
63
+ public function hooks_init() {
64
+ add_action( 'plugins_loaded', array( $this, 'setup' ), 9999 );
65
+ add_action( 'admin_enqueue_scripts', array( $this, 'enqueue' ) );
66
+ add_filter( 'limit_login_whitelist_ip', array( $this, 'check_whitelist_ips' ), 10, 2 );
67
+ add_filter( 'limit_login_whitelist_usernames', array( $this, 'check_whitelist_usernames' ), 10, 2 );
68
+ add_filter( 'limit_login_blacklist_ip', array( $this, 'check_blacklist_ips' ), 10, 2 );
69
+ add_filter( 'limit_login_blacklist_usernames', array( $this, 'check_blacklist_usernames' ), 10, 2 );
70
+ }
71
+
72
+ /**
73
+ * Hook 'plugins_loaded'
74
+ */
75
+ public function setup() {
76
+
77
+ // Load languages files
78
+ load_plugin_textdomain( 'limit-login-attempts-reloaded', false, plugin_basename( dirname( __FILE__ ) ) . '/../languages' );
79
+
80
+ // Check if installed old plugin
81
+ $this->check_original_installed();
82
+
83
+ if ( is_multisite() )
84
+ require_once ABSPATH.'wp-admin/includes/plugin.php';
85
+
86
+ $this->network_mode = is_multisite() && is_plugin_active_for_network('limit-login-attempts-reloaded/limit-login-attempts-reloaded.php');
87
+
88
+
89
+ if ( $this->network_mode )
90
+ {
91
+ $this->allow_local_options = get_site_option( 'limit_login_allow_local_options', false );
92
+ $this->use_local_options = $this->allow_local_options && get_option( 'limit_login_use_local_options', false );
93
+ }
94
+ else
95
+ {
96
+ $this->allow_local_options = true;
97
+ $this->use_local_options = true;
98
+ }
99
+
100
+
101
+ // Setup default plugin options
102
+ //$this->sanitize_options();
103
+
104
+ add_action( 'wp_login_failed', array( $this, 'limit_login_failed' ) );
105
+ add_filter( 'wp_authenticate_user', array( $this, 'wp_authenticate_user' ), 99999, 2 );
106
+
107
+ add_filter( 'shake_error_codes', array( $this, 'failure_shake' ) );
108
+ add_action( 'login_head', array( $this, 'add_error_message' ) );
109
+ add_action( 'login_errors', array( $this, 'fixup_error_messages' ) );
110
+
111
+ if ( $this->network_mode )
112
+ add_action( 'network_admin_menu', array( $this, 'network_admin_menu' ) );
113
+
114
+ if ( $this->allow_local_options )
115
+ add_action( 'admin_menu', array( $this, 'admin_menu' ) );
116
+
117
+ // Add notices for XMLRPC request
118
+ add_filter( 'xmlrpc_login_error', array( $this, 'xmlrpc_error_messages' ) );
119
+
120
+ // Add notices to woocommerce login page
121
+ add_action( 'wp_head', array( $this, 'add_wc_notices' ) );
122
+
123
+ /*
124
+ * This action should really be changed to the 'authenticate' filter as
125
+ * it will probably be deprecated. That is however only available in
126
+ * later versions of WP.
127
+ */
128
+ add_action( 'wp_authenticate', array( $this, 'track_credentials' ), 10, 2 );
129
+ add_action( 'authenticate', array( $this, 'authenticate_filter' ), 5, 3 );
130
+
131
+ if ( defined('XMLRPC_REQUEST') && XMLRPC_REQUEST )
132
+ add_action( 'init', array( $this, 'check_xmlrpc_lock' ) );
133
+
134
+ add_action('wp_ajax_limit-login-unlock', array( $this, 'ajax_unlock' ) );
135
+ }
136
+
137
+ public function check_xmlrpc_lock()
138
+ {
139
+ if ( is_user_logged_in() || $this->is_ip_whitelisted() )
140
+ return;
141
+
142
+ if ( $this->is_ip_blacklisted() || !$this->is_limit_login_ok() )
143
+ {
144
+ header('HTTP/1.0 403 Forbidden');
145
+ exit;
146
+ }
147
+ }
148
+
149
+ public function check_whitelist_ips( $allow, $ip ) {
150
+ return $this->ip_in_range( $ip, (array) $this->get_option( 'whitelist' ) );
151
+ }
152
+
153
+ public function check_whitelist_usernames( $allow, $username ) {
154
+ return in_array( $username, (array) $this->get_option( 'whitelist_usernames' ) );
155
+ }
156
+
157
+ public function check_blacklist_ips( $allow, $ip ) {
158
+ return $this->ip_in_range( $ip, (array) $this->get_option( 'blacklist' ) );
159
+ }
160
+
161
+ public function check_blacklist_usernames( $allow, $username ) {
162
+ return in_array( $username, (array) $this->get_option( 'blacklist_usernames' ) );
163
+ }
164
+
165
+ public function ip_in_range( $ip, $list )
166
+ {
167
+ foreach ( $list as $range )
168
+ {
169
+ $range = array_map('trim', explode('-', $range) );
170
+ if ( count( $range ) == 1 )
171
+ {
172
+ if ( (string)$ip === (string)$range[0] )
173
+ return true;
174
+ }
175
+ else
176
+ {
177
+ $low = ip2long( $range[0] );
178
+ $high = ip2long( $range[1] );
179
+ $needle = ip2long( $ip );
180
+
181
+ if ( $low === false || $high === false || $needle === false )
182
+ continue;
183
+
184
+ $low = (float)sprintf("%u",$low);
185
+ $high = (float)sprintf("%u",$high);
186
+ $needle = (float)sprintf("%u",$needle);
187
+
188
+ if ( $needle >= $low && $needle <= $high )
189
+ return true;
190
+ }
191
+ }
192
+
193
+ return false;
194
+ }
195
+
196
+ /**
197
+ * @param $error IXR_Error
198
+ *
199
+ * @return IXR_Error
200
+ */
201
+ public function xmlrpc_error_messages( $error ) {
202
+
203
+ if ( ! class_exists( 'IXR_Error' ) ) {
204
+ return $error;
205
+ }
206
+
207
+ if ( ! $this->is_limit_login_ok() ) {
208
+ return new IXR_Error( 403, $this->error_msg() );
209
+ }
210
+
211
+ $ip = $this->get_address();
212
+ $retries = $this->get_option( 'retries' );
213
+ $valid = $this->get_option( 'retries_valid' );
214
+
215
+ /* Should we show retries remaining? */
216
+
217
+ if ( ! is_array( $retries ) || ! is_array( $valid ) ) {
218
+ /* no retries at all */
219
+ return $error;
220
+ }
221
+ if (
222
+ (! isset( $retries[ $ip ] ) && ! isset( $retries[ $this->getHash($ip) ] )) ||
223
+ (! isset( $valid[ $ip ] ) && ! isset( $valid[ $this->getHash($ip) ] )) ||
224
+ (time() > $valid[ $ip ] && time() > $valid[ $this->getHash($ip) ])
225
+
226
+ ) {
227
+ /* no: no valid retries */
228
+ return $error;
229
+ }
230
+ if (
231
+ ( ((isset($retries[ $ip ]) ? $retries[ $ip ] : 0) + (isset($retries[ $this->getHash($ip) ]) ? $retries[ $this->getHash($ip) ] : 0)) % $this->get_option( 'allowed_retries' ) ) == 0
232
+ ) {
233
+ //* no: already been locked out for these retries */
234
+ return $error;
235
+ }
236
+
237
+ $remaining = max( ( $this->get_option( 'allowed_retries' ) - ( ((isset($retries[ $ip ]) ? $retries[ $ip ] : 0) + (isset($retries[ $this->getHash($ip) ]) ? $retries[ $this->getHash($ip) ] : 0)) % $this->get_option( 'allowed_retries' ) ) ), 0 );
238
+
239
+ return new IXR_Error( 403, sprintf( _n( "<strong>%d</strong> attempt remaining.", "<strong>%d</strong> attempts remaining.", $remaining, 'limit-login-attempts-reloaded' ), $remaining ) );
240
+ }
241
+
242
+ /**
243
+ * Errors on WooCommerce account page
244
+ */
245
+ public function add_wc_notices() {
246
+
247
+ global $limit_login_just_lockedout, $limit_login_nonempty_credentials, $limit_login_my_error_shown;
248
+
249
+ if ( ! function_exists( 'is_account_page' ) || ! function_exists( 'wc_add_notice' ) ) {
250
+ return;
251
+ }
252
+
253
+ /*
254
+ * During lockout we do not want to show any other error messages (like
255
+ * unknown user or empty password).
256
+ */
257
+ if ( empty( $_POST ) && ! $this->is_limit_login_ok() && ! $limit_login_just_lockedout ) {
258
+ if ( is_account_page() ) {
259
+ wc_add_notice( $this->error_msg(), 'error' );
260
+ }
261
+ }
262
+
263
+ }
264
+
265
+ /**
266
+ * @param $user
267
+ * @param $username
268
+ * @param $password
269
+ *
270
+ * @return WP_Error | WP_User
271
+ */
272
+ public function authenticate_filter( $user, $username, $password ) {
273
+
274
+ if ( ! empty( $username ) && ! empty( $password ) ) {
275
+
276
+ $ip = $this->get_address();
277
+
278
+ // Check if username is blacklisted
279
+ if ( ! $this->is_username_whitelisted( $username ) && ! $this->is_ip_whitelisted( $ip ) &&
280
+ ( $this->is_username_blacklisted( $username ) || $this->is_ip_blacklisted( $ip ) )
281
+ ) {
282
+
283
+ remove_filter( 'login_errors', array( $this, 'fixup_error_messages' ) );
284
+ remove_filter( 'login_head', array( $this, 'add_error_message' ) );
285
+ remove_filter( 'wp_login_failed', array( $this, 'limit_login_failed' ) );
286
+ remove_filter( 'wp_authenticate_user', array( $this, 'wp_authenticate_user' ), 99999 );
287
+ remove_filter( 'login_head', array( $this, 'add_error_message' ) );
288
+ remove_filter( 'login_errors', array( $this, 'fixup_error_messages' ) );
289
+
290
+ remove_filter( 'authenticate', 'wp_authenticate_username_password', 20 );
291
+ remove_filter( 'authenticate', 'wp_authenticate_email_password', 20 );
292
+
293
+ $user = new WP_Error();
294
+ $user->add( 'username_blacklisted', "<strong>ERROR:</strong> Too many failed login attempts." );
295
+
296
+ } elseif ( $this->is_username_whitelisted( $username ) || $this->is_ip_whitelisted( $ip ) ) {
297
+
298
+ remove_filter( 'wp_login_failed', array( $this, 'limit_login_failed' ) );
299
+ remove_filter( 'wp_authenticate_user', array( $this, 'wp_authenticate_user' ), 99999 );
300
+ remove_filter( 'login_head', array( $this, 'add_error_message' ) );
301
+ remove_filter( 'login_errors', array( $this, 'fixup_error_messages' ) );
302
+
303
+ }
304
+
305
+ }
306
+
307
+ return $user;
308
+ }
309
+
310
+ /**
311
+ * Check if the original plugin is installed
312
+ */
313
+ private function check_original_installed()
314
+ {
315
+ require_once( ABSPATH . '/wp-admin/includes/plugin.php' );
316
+ if ( is_plugin_active('limit-login-attempts/limit-login-attempts.php') )
317
+ {
318
+ deactivate_plugins( 'limit-login-attempts/limit-login-attempts.php', true );
319
+ //add_action('plugins_loaded', 'limit_login_setup', 99999);
320
+ remove_action( 'plugins_loaded', 'limit_login_setup', 99999 );
321
+ }
322
+ }
323
+
324
+ /**
325
+ * Enqueue js and css
326
+ */
327
+ public function enqueue() {
328
+ wp_enqueue_style( 'lla-main', LLA_PLUGIN_URL . '/assets/css/limit-login-attempts.css' );
329
+ }
330
+
331
+ /**
332
+ * Add admin options page
333
+ */
334
+ public function network_admin_menu()
335
+ {
336
+ add_menu_page( 'Limit Login Attempts', 'Limit Login Attempts', 'manage_options', $this->_options_page_slug, array( $this, 'options_page' ), LLA_PLUGIN_URL . '/assets/img/icon-menu2.svg' );
337
+ }
338
+
339
+ public function admin_menu()
340
+ {
341
+ add_menu_page( 'Limit Login Attempts', 'Limit Login Attempts', 'manage_options', $this->_options_page_slug, array( $this, 'options_page' ), LLA_PLUGIN_URL . '/assets/img/icon-menu2.svg' );
342
+ }
343
+
344
+ /**
345
+ * Get the correct options page URI
346
+ *
347
+ * @return mixed
348
+ */
349
+ public function get_options_page_uri()
350
+ {
351
+ if ( is_network_admin() )
352
+ return network_admin_url( 'settings.php?page=limit-login-attempts' );
353
+
354
+ return menu_page_url( $this->_options_page_slug, false );
355
+ }
356
+
357
+ /**
358
+ * Get option by name
359
+ *
360
+ * @param $option_name
361
+ *
362
+ * @return null
363
+ */
364
+ public function get_option( $option_name, $local = null )
365
+ {
366
+ if ( is_null( $local ) )
367
+ $local = $this->use_local_options;
368
+
369
+ $option = 'limit_login_'.$option_name;
370
+
371
+ $func = $local ? 'get_option' : 'get_site_option';
372
+ $value = $func( $option, null );
373
+
374
+ if ( is_null( $value ) && isset( $this->default_options[ $option_name ] ) )
375
+ $value = $this->default_options[ $option_name ];
376
+
377
+ return $value;
378
+ }
379
+
380
+ public function update_option( $option_name, $value, $local = null )
381
+ {
382
+ if ( is_null( $local ) )
383
+ $local = $this->use_local_options;
384
+
385
+ $option = 'limit_login_'.$option_name;
386
+
387
+ $func = $local ? 'update_option' : 'update_site_option';
388
+
389
+ return $func( $option, $value );
390
+ }
391
+
392
+ public function add_option( $option_name, $value, $local=null )
393
+ {
394
+ if ( is_null( $local ) )
395
+ $local = $this->use_local_options;
396
+
397
+ $option = 'limit_login_'.$option_name;
398
+
399
+ $func = $local ? 'add_option' : 'add_site_option';
400
+
401
+ return $func( $option, $value, '', 'no' );
402
+ }
403
+
404
+ /**
405
+ * Setup main options
406
+ */
407
+ public function sanitize_options()
408
+ {
409
+ $simple_int_options = array( 'allowed_retries', 'lockout_duration', 'valid_duration', 'allowed_lockouts', 'long_duration', 'notify_email_after');
410
+ foreach ( $simple_int_options as $option )
411
+ {
412
+ $val = $this->get_option( $option );
413
+ if ( (int)$val != $val || (int)$val <= 0 )
414
+ $this->update_option( $option, 1 );
415
+ }
416
+ if ( $this->get_option('notify_email_after') > $this->get_option( 'allowed_lockouts' ) )
417
+ $this->update_option( 'notify_email_after', $this->get_option( 'allowed_lockouts' ) );
418
+
419
+ $args = explode( ',', $this->get_option( 'lockout_notify' ) );
420
+ $args_allowed = explode( ',', LLA_LOCKOUT_NOTIFY_ALLOWED );
421
+ $new_args = array_intersect( $args, $args_allowed );
422
+
423
+ $this->update_option( 'lockout_notify', implode( ',', $new_args ) );
424
+
425
+ $ctype = $this->get_option( 'client_type' );
426
+ if ( $ctype != LLA_DIRECT_ADDR && $ctype != LLA_PROXY_ADDR )
427
+ $this->update_option( 'client_type', LLA_DIRECT_ADDR );
428
+ }
429
+
430
+ /**
431
+ * Check if it is ok to login
432
+ *
433
+ * @return bool
434
+ */
435
+ public function is_limit_login_ok() {
436
+
437
+ $ip = $this->get_address();
438
+
439
+ /* Check external whitelist filter */
440
+ if ( $this->is_ip_whitelisted( $ip ) ) {
441
+ return true;
442
+ }
443
+
444
+ /* lockout active? */
445
+ $lockouts = $this->get_option( 'lockouts' );
446
+
447
+ $a = $this->checkKey($lockouts, $ip);
448
+ $b = $this->checkKey($lockouts, $this->getHash($ip));
449
+ return (
450
+ ! is_array( $lockouts ) ||
451
+ (! isset( $lockouts[ $ip ] ) && ! isset( $lockouts[ $this->getHash($ip) ] )) ||
452
+ (time() >= $a && time() >= $b ));
453
+ }
454
+
455
+ /**
456
+ * Action when login attempt failed
457
+ *
458
+ * Increase nr of retries (if necessary). Reset valid value. Setup
459
+ * lockout if nr of retries are above threshold. And more!
460
+ *
461
+ * A note on external whitelist: retries and statistics are still counted and
462
+ * notifications done as usual, but no lockout is done.
463
+ *
464
+ * @param $username
465
+ */
466
+ public function limit_login_failed( $username ) {
467
+
468
+ $ip = $this->get_address();
469
+ $ipHash = $this->getHash($this->get_address());
470
+
471
+ /* if currently locked-out, do not add to retries */
472
+ $lockouts = $this->get_option( 'lockouts' );
473
+
474
+ if ( ! is_array( $lockouts ) ) {
475
+ $lockouts = array();
476
+ }
477
+
478
+ if ( (isset( $lockouts[ $ip ] ) && time() < $lockouts[ $ip ]) || (isset( $lockouts[ $ipHash ] ) && time() < $lockouts[ $ipHash ] )) {
479
+ return;
480
+ }
481
+
482
+ /* Get the arrays with retries and retries-valid information */
483
+ $retries = $this->get_option( 'retries' );
484
+ $valid = $this->get_option( 'retries_valid' );
485
+
486
+ if ( ! is_array( $retries ) ) {
487
+ $retries = array();
488
+ $this->add_option( 'retries', $retries );
489
+ }
490
+
491
+ if ( ! is_array( $valid ) ) {
492
+ $valid = array();
493
+ $this->add_option( 'retries_valid', $valid );
494
+ }
495
+
496
+ $gdpr = $this->get_option('gdpr');
497
+ $ip = ($gdpr ? $ipHash : $ip);
498
+ /* Check validity and add one to retries */
499
+ if ( isset( $retries[ $ip ] ) && isset( $valid[ $ip ] ) && time() < $valid[ $ip ]) {
500
+ $retries[ $ip ] ++;
501
+ } else {
502
+ $retries[ $ip ] = 1;
503
+ }
504
+ $valid[ $ip ] = time() + $this->get_option( 'valid_duration' );
505
+
506
+ /* lockout? */
507
+ if ( $retries[ $ip ] % $this->get_option( 'allowed_retries' ) != 0 ) {
508
+ /*
509
+ * Not lockout (yet!)
510
+ * Do housecleaning (which also saves retry/valid values).
511
+ */
512
+ $this->cleanup( $retries, null, $valid );
513
+
514
+ return;
515
+ }
516
+
517
+ /* lockout! */
518
+ $whitelisted = $this->is_ip_whitelisted( $ip );
519
+ $retries_long = $this->get_option( 'allowed_retries' ) * $this->get_option( 'allowed_lockouts' );
520
+
521
+ /*
522
+ * Note that retries and statistics are still counted and notifications
523
+ * done as usual for whitelisted ips , but no lockout is done.
524
+ */
525
+ if ( $whitelisted ) {
526
+ if ( $retries[ $ip ] >= $retries_long ) {
527
+ unset( $retries[ $ip ] );
528
+ unset( $valid[ $ip ] );
529
+ }
530
+ } else {
531
+ global $limit_login_just_lockedout;
532
+ $limit_login_just_lockedout = true;
533
+ $gdpr = $this->get_option('gdpr');
534
+ $index = ($gdpr ? $ipHash : $ip);
535
+
536
+ /* setup lockout, reset retries as needed */
537
+ if ( (isset($retries[ $ip ]) ? $retries[ $ip ] : 0) >= $retries_long || (isset($retries[ $ipHash ]) ? $retries[ $ipHash ] : 0) >= $retries_long ) {
538
+ /* long lockout */
539
+ $lockouts[ $index ] = time() + $this->get_option( 'long_duration' );
540
+ unset( $retries[ $index ] );
541
+ unset( $valid[ $index ] );
542
+ } else {
543
+ /* normal lockout */
544
+ $lockouts[ $index ] = time() + $this->get_option( 'lockout_duration' );
545
+ }
546
+ }
547
+
548
+ /* do housecleaning and save values */
549
+ $this->cleanup( $retries, $lockouts, $valid );
550
+
551
+ /* do any notification */
552
+ $this->notify( $username );
553
+
554
+ /* increase statistics */
555
+ $total = $this->get_option( 'lockouts_total' );
556
+ if ( $total === false || ! is_numeric( $total ) ) {
557
+ $this->add_option( 'lockouts_total', 1 );
558
+ } else {
559
+ $this->update_option( 'lockouts_total', $total + 1 );
560
+ }
561
+ }
562
+
563
+ /**
564
+ * Handle notification in event of lockout
565
+ *
566
+ * @param $user
567
+ */
568
+ public function notify( $user ) {
569
+ $args = explode( ',', $this->get_option( 'lockout_notify' ) );
570
+
571
+ if ( empty( $args ) ) {
572
+ return;
573
+ }
574
+
575
+ foreach ( $args as $mode ) {
576
+ switch ( trim( $mode ) ) {
577
+ case 'email':
578
+ $this->notify_email( $user );
579
+ break;
580
+ case 'log':
581
+ $this->notify_log( $user );
582
+ break;
583
+ }
584
+ }
585
+ }
586
+
587
+ /**
588
+ * Email notification of lockout to admin (if configured)
589
+ *
590
+ * @param $user
591
+ */
592
+ public function notify_email( $user ) {
593
+ $ip = $this->get_address();
594
+ $whitelisted = $this->is_ip_whitelisted( $ip );
595
+
596
+ $retries = $this->get_option( 'retries' );
597
+ if ( ! is_array( $retries ) ) {
598
+ $retries = array();
599
+ }
600
+
601
+ /* check if we are at the right nr to do notification */
602
+ if (
603
+ (isset( $retries[ $ip ] ) || isset( $retries[ $this->getHash($ip) ] ))
604
+ &&
605
+ ( ( intval($retries[ $ip ] + $retries[ $this->getHash($ip) ]) / $this->get_option( 'allowed_retries' ) ) % $this->get_option( 'notify_email_after' ) ) != 0 ) {
606
+ return;
607
+ }
608
+
609
+ /* Format message. First current lockout duration */
610
+ if ( !isset( $retries[ $ip ] ) && !isset( $retries[ $this->getHash($ip) ] ) ) {
611
+ /* longer lockout */
612
+ $count = $this->get_option( 'allowed_retries' )
613
+ * $this->get_option( 'allowed_lockouts' );
614
+ $lockouts = $this->get_option( 'allowed_lockouts' );
615
+ $time = round( $this->get_option( 'long_duration' ) / 3600 );
616
+ $when = sprintf( _n( '%d hour', '%d hours', $time, 'limit-login-attempts-reloaded' ), $time );
617
+ } else {
618
+ /* normal lockout */
619
+ $count = $retries[ $ip ] + $retries[ $this->getHash($ip) ];
620
+ $lockouts = floor( ($count) / $this->get_option( 'allowed_retries' ) );
621
+ $time = round( $this->get_option( 'lockout_duration' ) / 60 );
622
+ $when = sprintf( _n( '%d minute', '%d minutes', $time, 'limit-login-attempts-reloaded' ), $time );
623
+ }
624
+
625
+ $blogname = $this->use_local_options ? get_option( 'blogname' ) : get_site_option( 'site_name' );
626
+ $blogname = htmlspecialchars_decode( $blogname, ENT_QUOTES );
627
+
628
+ if ( $whitelisted ) {
629
+ $subject = sprintf( __( "[%s] Failed login attempts from whitelisted IP"
630
+ , 'limit-login-attempts-reloaded' )
631
+ , $blogname );
632
+ } else {
633
+ $subject = sprintf( __( "[%s] Too many failed login attempts"
634
+ , 'limit-login-attempts-reloaded' )
635
+ , $blogname );
636
+ }
637
+
638
+ $message = sprintf( __( "%d failed login attempts (%d lockout(s)) from IP: %s"
639
+ , 'limit-login-attempts-reloaded' ) . "\r\n\r\n"
640
+ , $count, $lockouts, $ip );
641
+ if ( $user != '' ) {
642
+ $message .= sprintf( __( "Last user attempted: %s", 'limit-login-attempts-reloaded' )
643
+ . "\r\n\r\n", $user );
644
+ }
645
+ if ( $whitelisted ) {
646
+ $message .= __( "IP was NOT blocked because of external whitelist.", 'limit-login-attempts-reloaded' );
647
+ } else {
648
+ $message .= sprintf( __( "IP was blocked for %s", 'limit-login-attempts-reloaded' ), $when );
649
+ }
650
+
651
+ $admin_email = $this->use_local_options ? get_option( 'admin_email' ) : get_site_option( 'admin_email' );
652
+
653
+ @wp_mail( $admin_email, $subject, $message );
654
+ }
655
+
656
+ /**
657
+ * Logging of lockout (if configured)
658
+ *
659
+ * @param $user_login
660
+ *
661
+ * @internal param $user
662
+ */
663
+ public function notify_log( $user_login ) {
664
+
665
+ if ( ! $user_login ) {
666
+ return;
667
+ }
668
+
669
+ $log = $option = $this->get_option( 'logged' );
670
+ if ( ! is_array( $log ) ) {
671
+ $log = array();
672
+ }
673
+ $ip = $this->get_address();
674
+
675
+ $index = ($this->get_option('gdpr') ? $this->getHash($ip) : $ip );
676
+ /* can be written much simpler, if you do not mind php warnings */
677
+ if ( !isset( $log[ $index ] ) )
678
+ $log[ $index ] = array();
679
+
680
+ if ( !isset( $log[ $index ][ $user_login ] ) )
681
+ $log[ $index ][ $user_login ] = array( 'counter' => 0 );
682
+
683
+ elseif ( !is_array( $log[ $index ][ $user_login ] ) )
684
+ $log[ $index ][ $user_login ] = array(
685
+ 'counter' => $log[ $index ][ $user_login ],
686
+ );
687
+
688
+ $log[ $index ][ $user_login ]['counter']++;
689
+ $log[ $index ][ $user_login ]['date'] = time();
690
+
691
+ if ( isset( $_POST['woocommerce-login-nonce'] ) ) {
692
+ $gateway = 'WooCommerce';
693
+ } elseif ( isset( $GLOBALS['wp_xmlrpc_server'] ) && is_object( $GLOBALS['wp_xmlrpc_server'] ) ) {
694
+ $gateway = 'XMLRPC';
695
+ } else {
696
+ $gateway = 'WP Login';
697
+ }
698
+
699
+ $log[ $index ][ $user_login ]['gateway'] = $gateway;
700
+
701
+ if ( $option === false ) {
702
+ $this->add_option( 'logged', $log );
703
+ } else {
704
+ $this->update_option( 'logged', $log );
705
+ }
706
+ }
707
+
708
+ /**
709
+ * Check if IP is whitelisted.
710
+ *
711
+ * This function allow external ip whitelisting using a filter. Note that it can
712
+ * be called multiple times during the login process.
713
+ *
714
+ * Note that retries and statistics are still counted and notifications
715
+ * done as usual for whitelisted ips , but no lockout is done.
716
+ *
717
+ * Example:
718
+ * function my_ip_whitelist($allow, $ip) {
719
+ * return ($ip == 'my-ip') ? true : $allow;
720
+ * }
721
+ * add_filter('limit_login_whitelist_ip', 'my_ip_whitelist', 10, 2);
722
+ *
723
+ * @param null $ip
724
+ *
725
+ * @return bool
726
+ */
727
+ public function is_ip_whitelisted( $ip = null ) {
728
+
729
+ if ( is_null( $ip ) ) {
730
+ $ip = $this->get_address();
731
+ }
732
+
733
+ $whitelisted = apply_filters( 'limit_login_whitelist_ip', false, $ip );
734
+
735
+ return ( $whitelisted === true );
736
+ }
737
+
738
+ public function is_username_whitelisted( $username ) {
739
+
740
+ if ( empty( $username ) ) {
741
+ return false;
742
+ }
743
+
744
+ $whitelisted = apply_filters( 'limit_login_whitelist_usernames', false, $username );
745
+
746
+ return ( $whitelisted === true );
747
+ }
748
+
749
+ public function is_ip_blacklisted( $ip = null ) {
750
+
751
+ if ( is_null( $ip ) ) {
752
+ $ip = $this->get_address();
753
+ }
754
+
755
+ $whitelisted = apply_filters( 'limit_login_blacklist_ip', false, $ip );
756
+
757
+ return ( $whitelisted === true );
758
+ }
759
+
760
+ public function is_username_blacklisted( $username ) {
761
+
762
+ if ( empty( $username ) ) {
763
+ return false;
764
+ }
765
+
766
+ $whitelisted = apply_filters( 'limit_login_blacklist_usernames', false, $username );
767
+
768
+ return ( $whitelisted === true );
769
+ }
770
+
771
+ /**
772
+ * Filter: allow login attempt? (called from wp_authenticate())
773
+ *
774
+ * @param $user WP_User
775
+ * @param $password
776
+ *
777
+ * @return \WP_Error
778
+ */
779
+ public function wp_authenticate_user( $user, $password ) {
780
+
781
+ if ( is_wp_error( $user ) ||
782
+ $this->check_whitelist_ips( false, $this->get_address() ) ||
783
+ $this->check_whitelist_usernames( false, $user->user_login ) ||
784
+ $this->is_limit_login_ok()
785
+ ) {
786
+
787
+ return $user;
788
+ }
789
+
790
+ $error = new WP_Error();
791
+
792
+ global $limit_login_my_error_shown;
793
+ $limit_login_my_error_shown = true;
794
+
795
+ if ( $this->is_username_blacklisted( $user->user_login ) || $this->is_ip_blacklisted( $this->get_address() ) ) {
796
+ $error->add( 'username_blacklisted', "<strong>ERROR:</strong> Too many failed login attempts." );
797
+ } else {
798
+ // This error should be the same as in "shake it" filter below
799
+ $error->add( 'too_many_retries', $this->error_msg() );
800
+ }
801
+
802
+ return $error;
803
+ }
804
+
805
+ /**
806
+ * Filter: add this failure to login page "Shake it!"
807
+ *
808
+ * @param $error_codes
809
+ *
810
+ * @return array
811
+ */
812
+ public function failure_shake( $error_codes ) {
813
+ $error_codes[] = 'too_many_retries';
814
+ $error_codes[] = 'username_blacklisted';
815
+
816
+ return $error_codes;
817
+ }
818
+
819
+ /**
820
+ * Keep track of if user or password are empty, to filter errors correctly
821
+ *
822
+ * @param $user
823
+ * @param $password
824
+ */
825
+ public function track_credentials( $user, $password ) {
826
+ global $limit_login_nonempty_credentials;
827
+
828
+ $limit_login_nonempty_credentials = ( ! empty( $user ) && ! empty( $password ) );
829
+ }
830
+
831
+ /**
832
+ * Should we show errors and messages on this page?
833
+ *
834
+ * @return bool
835
+ */
836
+ public function login_show_msg() {
837
+ if ( isset( $_GET['key'] ) ) {
838
+ /* reset password */
839
+ return false;
840
+ }
841
+
842
+ $action = isset( $_REQUEST['action'] ) ? $_REQUEST['action'] : '';
843
+
844
+ return ( $action != 'lostpassword' && $action != 'retrievepassword'
845
+ && $action != 'resetpass' && $action != 'rp'
846
+ && $action != 'register' );
847
+ }
848
+
849
+ /**
850
+ * Construct informative error message
851
+ *
852
+ * @return string
853
+ */
854
+ public function error_msg() {
855
+ $ip = $this->get_address();
856
+ $lockouts = $this->get_option( 'lockouts' );
857
+ $a = $this->checkKey($lockouts, $ip);
858
+ $b = $this->checkKey($lockouts, $this->getHash($ip));
859
+
860
+ $msg = __( '<strong>ERROR</strong>: Too many failed login attempts.', 'limit-login-attempts-reloaded' ) . ' ';
861
+
862
+ if (
863
+ ! is_array( $lockouts ) ||
864
+ ( ! isset( $lockouts[ $ip ] ) && ! isset( $lockouts[$this->getHash($ip)]) ) ||
865
+ (time() >= $a && time() >= $b)
866
+ ){
867
+ /* Huh? No timeout active? */
868
+ $msg .= __( 'Please try again later.', 'limit-login-attempts-reloaded' );
869
+
870
+ return $msg;
871
+ }
872
+
873
+ $when = ceil( ( ($a > $b ? $a : $b) - time() ) / 60 );
874
+ if ( $when > 60 ) {
875
+ $when = ceil( $when / 60 );
876
+ $msg .= sprintf( _n( 'Please try again in %d hour.', 'Please try again in %d hours.', $when, 'limit-login-attempts-reloaded' ), $when );
877
+ } else {
878
+ $msg .= sprintf( _n( 'Please try again in %d minute.', 'Please try again in %d minutes.', $when, 'limit-login-attempts-reloaded' ), $when );
879
+ }
880
+
881
+ return $msg;
882
+ }
883
+
884
+ /**
885
+ * Add a message to login page when necessary
886
+ */
887
+ public function add_error_message() {
888
+ global $error, $limit_login_my_error_shown;
889
+
890
+ if ( ! $this->login_show_msg() || $limit_login_my_error_shown ) {
891
+ return;
892
+ }
893
+
894
+ $msg = $this->get_message();
895
+
896
+ if ( $msg != '' ) {
897
+ $limit_login_my_error_shown = true;
898
+ $error .= $msg;
899
+ }
900
+
901
+ return;
902
+ }
903
+
904
+ /**
905
+ * Fix up the error message before showing it
906
+ *
907
+ * @param $content
908
+ *
909
+ * @return string
910
+ */
911
+ public function fixup_error_messages( $content ) {
912
+ global $limit_login_just_lockedout, $limit_login_nonempty_credentials, $limit_login_my_error_shown;
913
+
914
+ if ( ! $this->login_show_msg() ) {
915
+ return $content;
916
+ }
917
+
918
+ /*
919
+ * During lockout we do not want to show any other error messages (like
920
+ * unknown user or empty password).
921
+ */
922
+ if ( ! $this->is_limit_login_ok() && ! $limit_login_just_lockedout ) {
923
+ return $this->error_msg();
924
+ }
925
+
926
+ /*
927
+ * We want to filter the messages 'Invalid username' and
928
+ * 'Invalid password' as that is an information leak regarding user
929
+ * account names (prior to WP 2.9?).
930
+ *
931
+ * Also, if more than one error message, put an extra <br /> tag between
932
+ * them.
933
+ */
934
+ $msgs = explode( "<br />\n", $content );
935
+
936
+ if ( strlen( end( $msgs ) ) == 0 ) {
937
+ /* remove last entry empty string */
938
+ array_pop( $msgs );
939
+ }
940
+
941
+ $count = count( $msgs );
942
+ $my_warn_count = $limit_login_my_error_shown ? 1 : 0;
943
+
944
+ if ( $limit_login_nonempty_credentials && $count > $my_warn_count ) {
945
+
946
+ /* Replace error message, including ours if necessary */
947
+ if( !empty( $_REQUEST['log'] ) && is_email( $_REQUEST['log'] ) ) {
948
+ $content = __( '<strong>ERROR</strong>: Incorrect email address or password.', 'limit-login-attempts-reloaded' ) . "<br />\n";
949
+ } else{
950
+ $content = __( '<strong>ERROR</strong>: Incorrect username or password.', 'limit-login-attempts-reloaded' ) . "<br />\n";
951
+ }
952
+
953
+ if ( $limit_login_my_error_shown || $this->get_message() ) {
954
+ $content .= "<br />\n" . $this->get_message() . "<br />\n";
955
+ }
956
+
957
+ return $content;
958
+ } elseif ( $count <= 1 ) {
959
+ return $content;
960
+ }
961
+
962
+ $new = '';
963
+ while ( $count -- > 0 ) {
964
+ $new .= array_shift( $msgs ) . "<br />\n";
965
+ if ( $count > 0 ) {
966
+ $new .= "<br />\n";
967
+ }
968
+ }
969
+
970
+ return $new;
971
+ }
972
+
973
+ public function fixup_error_messages_wc( \WP_Error $error ) {
974
+ $error->add( 1, __( 'WC Error' ) );
975
+ }
976
+
977
+ /**
978
+ * Return current (error) message to show, if any
979
+ *
980
+ * @return string
981
+ */
982
+ public function get_message() {
983
+ /* Check external whitelist */
984
+ if ( $this->is_ip_whitelisted() ) {
985
+ return '';
986
+ }
987
+
988
+ /* Is lockout in effect? */
989
+ if ( ! $this->is_limit_login_ok() ) {
990
+ return $this->error_msg();
991
+ }
992
+
993
+ return $this->retries_remaining_msg();
994
+ }
995
+
996
+ /**
997
+ * Construct retries remaining message
998
+ *
999
+ * @return string
1000
+ */
1001
+ public function retries_remaining_msg() {
1002
+ $ip = $this->get_address();
1003
+ $retries = $this->get_option( 'retries' );
1004
+ $valid = $this->get_option( 'retries_valid' );
1005
+ $a = $this->checkKey($retries, $ip);
1006
+ $b = $this->checkKey($retries, $this->getHash($ip));
1007
+ $c = $this->checkKey($valid, $ip);
1008
+ $d = $this->checkKey($valid, $this->getHash($ip));
1009
+
1010
+ /* Should we show retries remaining? */
1011
+ if ( ! is_array( $retries ) || ! is_array( $valid ) ) {
1012
+ /* no retries at all */
1013
+ return '';
1014
+ }
1015
+ if (
1016
+ (! isset( $retries[ $ip ] ) && ! isset( $retries[ $this->getHash($ip) ] )) ||
1017
+ (! isset( $valid[ $ip ] ) && ! isset( $valid[ $this->getHash($ip) ] )) ||
1018
+ ( time() > $c && time() > $d )
1019
+ ) {
1020
+ /* no: no valid retries */
1021
+ return '';
1022
+ }
1023
+ if (
1024
+ ( $a % $this->get_option( 'allowed_retries' ) ) == 0 &&
1025
+ ( $b % $this->get_option( 'allowed_retries' ) ) == 0
1026
+ ) {
1027
+ /* no: already been locked out for these retries */
1028
+ return '';
1029
+ }
1030
+
1031
+ $remaining = max( ( $this->get_option( 'allowed_retries' ) - ( ($a + $b) % $this->get_option( 'allowed_retries' ) ) ), 0 );
1032
+
1033
+ return sprintf( _n( "<strong>%d</strong> attempt remaining.", "<strong>%d</strong> attempts remaining.", $remaining, 'limit-login-attempts-reloaded' ), $remaining );
1034
+ }
1035
+
1036
+ /**
1037
+ * Get correct remote address
1038
+ *
1039
+ * @param string $type_name
1040
+ *
1041
+ * @return string
1042
+ */
1043
+ public function get_address() {
1044
+
1045
+ if ( !empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) && filter_var( $_SERVER['HTTP_X_FORWARDED_FOR'], FILTER_VALIDATE_IP ) )
1046
+ $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
1047
+
1048
+ elseif ( !empty( $_SERVER['HTTP_X_SUCURI_CLIENTIP'] ) && filter_var( $_SERVER['HTTP_X_SUCURI_CLIENTIP'], FILTER_VALIDATE_IP ) )
1049
+ $ip = $_SERVER['HTTP_X_SUCURI_CLIENTIP'];
1050
+
1051
+ elseif ( isset( $_SERVER['REMOTE_ADDR'] ) )
1052
+ $ip = $_SERVER['REMOTE_ADDR'];
1053
+
1054
+ else
1055
+ $ip = '';
1056
+
1057
+ $ip = preg_replace('/^(\d+\.\d+\.\d+\.\d+):\d+$/', '\1', $ip);
1058
+
1059
+ return $ip;
1060
+ }
1061
+
1062
+ /**
1063
+ * Clean up old lockouts and retries, and save supplied arrays
1064
+ *
1065
+ * @param null $retries
1066
+ * @param null $lockouts
1067
+ * @param null $valid
1068
+ */
1069
+ public function cleanup( $retries = null, $lockouts = null, $valid = null ) {
1070
+ $now = time();
1071
+ $lockouts = ! is_null( $lockouts ) ? $lockouts : $this->get_option( 'lockouts' );
1072
+
1073
+ /* remove old lockouts */
1074
+ if ( is_array( $lockouts ) ) {
1075
+ foreach ( $lockouts as $ip => $lockout ) {
1076
+ if ( $lockout < $now ) {
1077
+ unset( $lockouts[ $ip ] );
1078
+ }
1079
+ }
1080
+ $this->update_option( 'lockouts', $lockouts );
1081
+ }
1082
+
1083
+ /* remove retries that are no longer valid */
1084
+ $valid = ! is_null( $valid ) ? $valid : $this->get_option( 'retries_valid' );
1085
+ $retries = ! is_null( $retries ) ? $retries : $this->get_option( 'retries' );
1086
+ if ( ! is_array( $valid ) || ! is_array( $retries ) ) {
1087
+ return;
1088
+ }
1089
+
1090
+ foreach ( $valid as $ip => $lockout ) {
1091
+ if ( $lockout < $now ) {
1092
+ unset( $valid[ $ip ] );
1093
+ unset( $retries[ $ip ] );
1094
+ }
1095
+ }
1096
+
1097
+ /* go through retries directly, if for some reason they've gone out of sync */
1098
+ foreach ( $retries as $ip => $retry ) {
1099
+ if ( ! isset( $valid[ $ip ] ) ) {
1100
+ unset( $retries[ $ip ] );
1101
+ }
1102
+ }
1103
+
1104
+ $this->update_option( 'retries', $retries );
1105
+ $this->update_option( 'retries_valid', $valid );
1106
+ }
1107
+
1108
+ /**
1109
+ * Render admin options page
1110
+ */
1111
+ public function options_page() {
1112
+ $this->use_local_options = !is_network_admin();
1113
+ $this->cleanup();
1114
+
1115
+ if( !empty( $_POST ) )
1116
+ {
1117
+ check_admin_referer( 'limit-login-attempts-options' );
1118
+
1119
+ if ( is_network_admin() )
1120
+ $this->update_option( 'allow_local_options', !empty($_POST['allow_local_options']) );
1121
+
1122
+ elseif ( $this->network_mode )
1123
+ $this->update_option( 'use_local_options', empty($_POST['use_global_options']) );
1124
+
1125
+ /* Should we support GDPR */
1126
+ if( isset( $_POST[ 'gdpr' ] ) )
1127
+ {
1128
+ $this->update_option( 'gdpr', 1 );
1129
+ }
1130
+ else {
1131
+ $this->update_option( 'gdpr', 0 );
1132
+ }
1133
+
1134
+ /* Should we clear log? */
1135
+ if( isset( $_POST[ 'clear_log' ] ) )
1136
+ {
1137
+ $this->update_option( 'logged', '' );
1138
+ $this->show_error( __( 'Cleared IP log', 'limit-login-attempts-reloaded' ) );
1139
+ }
1140
+
1141
+ /* Should we reset counter? */
1142
+ if( isset( $_POST[ 'reset_total' ] ) )
1143
+ {
1144
+ $this->update_option( 'lockouts_total', 0 );
1145
+ $this->show_error( __( 'Reset lockout count', 'limit-login-attempts-reloaded' ) );
1146
+ }
1147
+
1148
+ /* Should we restore current lockouts? */
1149
+ if( isset( $_POST[ 'reset_current' ] ) )
1150
+ {
1151
+ $this->update_option( 'lockouts', array() );
1152
+ $this->show_error( __( 'Cleared current lockouts', 'limit-login-attempts-reloaded' ) );
1153
+ }
1154
+
1155
+ /* Should we update options? */
1156
+ if( isset( $_POST[ 'update_options' ] ) )
1157
+ {
1158
+ $this->update_option('allowed_retries', (int)$_POST['allowed_retries'] );
1159
+ $this->update_option('lockout_duration', (int)$_POST['lockout_duration'] * 60 );
1160
+ $this->update_option('valid_duration', (int)$_POST['valid_duration'] * 3600 );
1161
+ $this->update_option('allowed_lockouts', (int)$_POST['allowed_lockouts'] );
1162
+ $this->update_option('long_duration', (int)$_POST['long_duration'] * 3600 );
1163
+ $this->update_option('notify_email_after', (int)$_POST['email_after'] );
1164
+
1165
+ $white_list_ips = ( !empty( $_POST['lla_whitelist_ips'] ) ) ? explode("\n", str_replace("\r", "", stripslashes($_POST['lla_whitelist_ips']) ) ) : array();
1166
+
1167
+ if( !empty( $white_list_ips ) ) {
1168
+ foreach( $white_list_ips as $key => $ip ) {
1169
+ if( '' == $ip ) {
1170
+ unset( $white_list_ips[ $key ] );
1171
+ }
1172
+ }
1173
+ }
1174
+ $this->update_option('whitelist', $white_list_ips );
1175
+
1176
+ $white_list_usernames = ( !empty( $_POST['lla_whitelist_usernames'] ) ) ? explode("\n", str_replace("\r", "", stripslashes($_POST['lla_whitelist_usernames']) ) ) : array();
1177
+
1178
+ if( !empty( $white_list_usernames ) ) {
1179
+ foreach( $white_list_usernames as $key => $ip ) {
1180
+ if( '' == $ip ) {
1181
+ unset( $white_list_usernames[ $key ] );
1182
+ }
1183
+ }
1184
+ }
1185
+ $this->update_option('whitelist_usernames', $white_list_usernames );
1186
+
1187
+ $black_list_ips = ( !empty( $_POST['lla_blacklist_ips'] ) ) ? explode("\n", str_replace("\r", "", stripslashes($_POST['lla_blacklist_ips']) ) ) : array();
1188
+
1189
+ if( !empty( $black_list_ips ) ) {
1190
+ foreach( $black_list_ips as $key => $ip ) {
1191
+ $range = array_map('trim', explode('-', $ip) );
1192
+ if ( count( $range ) > 1 && (float)sprintf("%u",ip2long($range[0])) > (float)sprintf("%u",ip2long($range[1]))) {
1193
+ $this->show_error( __( 'The "'. $ip .'" IP range is invalid', 'limit-login-attempts-reloaded' ) );
1194
+ }
1195
+ if( '' == $ip ) {
1196
+ unset( $black_list_ips[ $key ] );
1197
+ }
1198
+ }
1199
+ }
1200
+ $this->update_option('blacklist', $black_list_ips );
1201
+
1202
+ $black_list_usernames = ( !empty( $_POST['lla_blacklist_usernames'] ) ) ? explode("\n", str_replace("\r", "", stripslashes($_POST['lla_blacklist_usernames']) ) ) : array();
1203
+
1204
+ if( !empty( $black_list_usernames ) ) {
1205
+ foreach( $black_list_usernames as $key => $ip ) {
1206
+ if( '' == $ip ) {
1207
+ unset( $black_list_usernames[ $key ] );
1208
+ }
1209
+ }
1210
+ }
1211
+ $this->update_option('blacklist_usernames', $black_list_usernames );
1212
+
1213
+ $notify_methods = array();
1214
+ if( isset( $_POST[ 'lockout_notify_log' ] ) ) {
1215
+ $notify_methods[] = 'log';
1216
+ }
1217
+ if( isset( $_POST[ 'lockout_notify_email' ] ) ) {
1218
+ $notify_methods[] = 'email';
1219
+ }
1220
+ $this->update_option('lockout_notify', implode( ',', $notify_methods ) );
1221
+
1222
+ $this->sanitize_options();
1223
+
1224
+ $this->show_error( __( 'Options saved.', 'limit-login-attempts-reloaded' ) );
1225
+ }
1226
+ }
1227
+
1228
+ include_once( LLA_PLUGIN_DIR . '/views/options-page.php' );
1229
+ }
1230
+
1231
+ public function ajax_unlock()
1232
+ {
1233
+ check_ajax_referer('limit-login-unlock', 'sec');
1234
+ $ip = (string)@$_POST['ip'];
1235
+
1236
+ $lockouts = (array)$this->get_option('lockouts');
1237
+
1238
+ if ( isset( $lockouts[ $ip ] ) )
1239
+ {
1240
+ unset( $lockouts[ $ip ] );
1241
+ $this->update_option( 'lockouts', $lockouts );
1242
+ }
1243
+
1244
+ //save to log
1245
+ $user_login = @(string)$_POST['username'];
1246
+ $log = $this->get_option( 'logged' );
1247
+
1248
+ if ( @$log[ $ip ][ $user_login ] )
1249
+ {
1250
+ if ( !is_array( $log[ $ip ][ $user_login ] ) )
1251
+ $log[ $ip ][ $user_login ] = array(
1252
+ 'counter' => $log[ $ip ][ $user_login ],
1253
+ );
1254
+ $log[ $ip ][ $user_login ]['unlocked'] = true;
1255
+
1256
+ $this->update_option( 'logged', $log );
1257
+ }
1258
+
1259
+ header('Content-Type: application/json');
1260
+ echo 'true';
1261
+ exit;
1262
+ }
1263
+
1264
+ /**
1265
+ * Show error message
1266
+ *
1267
+ * @param $msg
1268
+ */
1269
+ public function show_error( $msg ) {
1270
+ LLA_Helpers::show_error( $msg );
1271
+ }
1272
+
1273
+ /**
1274
+ * returns IP with its md5 value
1275
+ */
1276
+ private function getHash($str)
1277
+ {
1278
+ return md5($str);
1279
+ }
1280
+
1281
+ /**
1282
+ * @param $arr - array
1283
+ * @param $k - key
1284
+ * @return int array value at given index or zero
1285
+ */
1286
+ private function checkKey($arr, $k)
1287
+ {
1288
+ return isset($arr[$k]) ? $arr[$k] : 0;
1289
+ }
1290
  }
core/Logger.php CHANGED
@@ -1,23 +1,23 @@
1
- <?php
2
-
3
- /**
4
- * Class LLA_Logger
5
- */
6
- class LLA_Logger {
7
-
8
- private static $_log_file_name = 'log.txt';
9
-
10
- /**
11
- * TODO
12
- * @param $msg
13
- * @return int|void
14
- */
15
- public static function add_log( $msg ) {
16
- if( ! $msg ) {
17
- return;
18
- }
19
-
20
- return file_put_contents( LLA_PLUGIN_DIR . DIRECTORY_SEPARATOR . self::$_log_file_name, $msg . "\n\r", FILE_APPEND );
21
- }
22
-
23
  }
1
+ <?php
2
+
3
+ /**
4
+ * Class LLA_Logger
5
+ */
6
+ class LLA_Logger {
7
+
8
+ private static $_log_file_name = 'log.txt';
9
+
10
+ /**
11
+ * TODO
12
+ * @param $msg
13
+ * @return int|void
14
+ */
15
+ public static function add_log( $msg ) {
16
+ if( ! $msg ) {
17
+ return;
18
+ }
19
+
20
+ return file_put_contents( LLA_PLUGIN_DIR . DIRECTORY_SEPARATOR . self::$_log_file_name, $msg . "\n\r", FILE_APPEND );
21
+ }
22
+
23
  }
limit-login-attempts-reloaded.php CHANGED
@@ -1,14 +1,15 @@
1
  <?php
2
  /*
3
- Plugin Name: Limit Login Attempts Reloaded
4
- Description: Limit the rate of login attempts, including by way of cookies and for each IP address.
5
- Author: wpchefgadget
6
- Text Domain: limit-login-attempts-reloaded
7
- Version: 2.7.1
 
8
 
9
- Copyright 2008 - 2012 Johan Eenfeldt, 2016 - 2017 WPChef
10
 
11
- Thanks to Michael Skerwiderski for reverse proxy handling suggestions.
12
  */
13
 
14
  /***************************************************************************************
1
  <?php
2
  /*
3
+ Plugin Name: Limit Login Attempts Reloaded
4
+ Description: Limit the rate of login attempts, including by way of cookies and for each IP address.
5
+ Author: WPChef
6
+ Author URI: https://wpchef.org
7
+ Text Domain: limit-login-attempts-reloaded
8
+ Version: 2.7.2
9
 
10
+ Copyright 2008 - 2012 Johan Eenfeldt, 2016 - 2019 WPChef
11
 
12
+ Thanks to Michael Skerwiderski for reverse proxy handling suggestions.
13
  */
14
 
15
  /***************************************************************************************
readme.txt CHANGED
@@ -1,109 +1,113 @@
1
- === Limit Login Attempts Reloaded ===
2
- Contributors: wpchefgadget
3
- Tags: login, security, authentication, Limit Login Attempts, GDPR, brute-force attack, brute force, login abuse, ddos protection
4
- Requires at least: 3.0
5
- Tested up to: 4.9.7
6
- Stable tag: 2.7.1
7
-
8
- Reloaded version of the original Limit Login Attempts plugin for Login Protection by a team of WordPress developers. GDPR compliant.
9
-
10
- == Description ==
11
-
12
- Limit the number of login attempts that possible both through the normal login as well as using the auth cookies.
13
- WordPress by default allows unlimited login attempts either through the login page or by sending special cookies. This allows passwords (or hashes) to be cracked via brute-force relatively easily.
14
- Limit Login Attempts Reloaded blocks an Internet address from making further attempts after a specified limit on retries has been reached, making a brute-force attack difficult or impossible.
15
-
16
- Features:
17
-
18
- * Limit the number of retry attempts when logging in (per each IP). This is fully customizable.
19
- * Limit the number of attempts to log in using authorization cookies in the same way.
20
- * Informs the user about the remaining retries or lockout time on the login page.
21
- * Optional logging and optional email notification.
22
- * Handles server behind the reverse proxy.
23
- * It is possible to whitelist/blacklist IPs and Usernames.
24
- * Sucuri Website Firewall compatibility.
25
- * **XMLRPC** gateway protection.
26
- * **Woocommerce** login page protection.
27
- * **Multi-site** compatibility with extra MU settings.
28
- * **GDPR** compliant. With this feature turned on, all logged IPs get obfuscated (md5-hashed).
29
-
30
- = Upgrading from the old Limit Login Attempts plugin =
31
- 1. Go to the Plugins section in your site's backend.
32
- 1. Remove the Limit Login Attempts plugin.
33
- 1. Install the Limit Login Attempts Reloaded plugin.
34
-
35
- All your settings will be kept in tact!
36
-
37
- Many languages are currently supported in Limit Login Attempts Reloaded plugin but we welcome any additional ones.
38
- Help us bring Limit Login Attempts Reloaded to even more cultures.
39
-
40
- Translations: Bulgarian, Brazilian Portuguese, Catalan, Chinese (Traditional), Czech, Dutch, Finnish, French, German, Hungarian, Norwegian, Persian, Romanian, Russian, Spanish, Swedish, Turkish
41
-
42
- Plugin uses standard actions and filters only.
43
-
44
- Based on the original code from Limit Login Attemps plugin by Johan Eenfeldt.
45
-
46
- == Screenshots ==
47
-
48
- 1. Loginscreen after a failed login with remaining retries
49
- 2. Lockout loginscreen
50
- 3. Administration interface in WordPress 4.5.3
51
-
52
- == Changelog ==
53
-
54
- = 2.7.1 =
55
- * A security issue inherited from the ancestor plugin Limit Login Attempts has been fixed.
56
-
57
- = 2.7.0 =
58
- * GDPR compliance implemented.
59
-
60
- * Fixed: ip_in_range() loop $ip overrides itself causing invalid results.
61
- https://wordpress.org/support/topic/ip_in_range-loop-ip-overrides-itself-causing-invalid-results/
62
-
63
- * Fixed: the plugin was locking out the same IP address multiple times, each with a different port.
64
- https://wordpress.org/support/topic/same-ip-different-port/
65
-
66
- = 2.6.3 =
67
- * Added support of Sucuri Website Firewall.
68
-
69
- = 2.6.2 =
70
- * Fixed the issue with backslashes in usernames.
71
-
72
- = 2.6.1 =
73
- * Plugin returns the 403 Forbidden header after the limit of login attempts via XMLRPC is reached.
74
-
75
- * Added support of IP ranges in white/black lists.
76
-
77
- * Lockouts now can be released selectively.
78
-
79
- * Fixed the issue with encoding of special symbols in email notifications.
80
-
81
- = 2.5.0 =
82
- * Added Multi-site Compatibility and additional MU settings. https://wordpress.org/support/topic/multisite-compatibility-47/
83
-
84
- = 2.4.0 =
85
- * Usernames and IP addresses can be white-listed and black-listed now. https://wordpress.org/support/topic/banning-specific-usernames/ https://wordpress.org/support/topic/good-831/
86
- * The lockouts log has been inversed. https://wordpress.org/support/topic/inverse-log/
87
-
88
- = 2.3.0 =
89
- * IP addresses can be white-listed now. https://wordpress.org/support/topic/legal-user/
90
- * A "Gateway" column is added to the lockouts log. It shows what endpoint an attacker was blocked from. https://wordpress.org/support/topic/xmlrpc-7/
91
- * The "Undefined index: client_type" error is fixed. https://wordpress.org/support/topic/php-notice-when-updating-settings-page/
92
-
93
- = 2.2.0 =
94
- * Removed the "Handle cookie login" setting as they are now obsolete.
95
- * Added bruteforce protection against Woocommerce login page attacks. https://wordpress.org/support/topic/how-to-integrate-with-woocommerce-2/
96
- * Added bruteforce protection against XMLRPC attacks. https://wordpress.org/support/topic/xmlrpc-7/
97
-
98
- = 2.1.0 =
99
- * The site connection settings are now applied automatically and therefore have been removed from the admin interface.
100
- * Now compatible with PHP 5.2 to support some older WP installations.
101
-
102
- = 2.0.0 =
103
- * fixed PHP Warning: Illegal offset type in isset or empty https://wordpress.org/support/topic/limit-login-attempts-generating-php-errors
104
- * fixed the deprecated functions issue
105
- https://wordpress.org/support/topic/using-deprecated-function
106
- * Fixed error with function arguments: https://wordpress.org/support/topic/warning-missing-argument-2-5
107
- * added time stamp to unsuccessful tries on the plugin configuration page.
108
- * fixed .po translation files issue.
 
 
 
 
109
  * code refactoring and optimization.
1
+ === Limit Login Attempts Reloaded ===
2
+ Contributors: wpchefgadget
3
+ Tags: brute force, login, security, GDPR, protection
4
+ Requires at least: 3.0
5
+ Tested up to: 5.0.3
6
+ Stable tag: 2.7.2
7
+
8
+ Reloaded version of the original Limit Login Attempts plugin for Login Protection by a team of WordPress developers. GDPR compliant.
9
+
10
+ == Description ==
11
+
12
+ Limit the number of login attempts that possible both through the normal login as well as using the auth cookies.
13
+ WordPress by default allows unlimited login attempts either through the login page or by sending special cookies. This allows passwords (or hashes) to be cracked via brute-force relatively easily.
14
+ Limit Login Attempts Reloaded blocks an Internet address from making further attempts after a specified limit on retries has been reached, making a brute-force attack difficult or impossible.
15
+
16
+ Features:
17
+
18
+ * Limit the number of retry attempts when logging in (per each IP). This is fully customizable.
19
+ * Limit the number of attempts to log in using authorization cookies in the same way.
20
+ * Informs the user about the remaining retries or lockout time on the login page.
21
+ * Optional logging and optional email notification.
22
+ * Handles server behind the reverse proxy.
23
+ * It is possible to whitelist/blacklist IPs and Usernames.
24
+ * Sucuri Website Firewall compatibility.
25
+ * **XMLRPC** gateway protection.
26
+ * **Woocommerce** login page protection.
27
+ * **Multi-site** compatibility with extra MU settings.
28
+ * **GDPR** compliant. With this feature turned on, all logged IPs get obfuscated (md5-hashed).
29
+
30
+ = Upgrading from the old Limit Login Attempts plugin =
31
+ 1. Go to the Plugins section in your site's backend.
32
+ 1. Remove the Limit Login Attempts plugin.
33
+ 1. Install the Limit Login Attempts Reloaded plugin.
34
+
35
+ All your settings will be kept in tact!
36
+
37
+ Many languages are currently supported in Limit Login Attempts Reloaded plugin but we welcome any additional ones.
38
+ Help us bring Limit Login Attempts Reloaded to even more cultures.
39
+
40
+ Translations: Bulgarian, Brazilian Portuguese, Catalan, Chinese (Traditional), Czech, Dutch, Finnish, French, German, Hungarian, Norwegian, Persian, Romanian, Russian, Spanish, Swedish, Turkish
41
+
42
+ Plugin uses standard actions and filters only.
43
+
44
+ Based on the original code from Limit Login Attemps plugin by Johan Eenfeldt.
45
+
46
+ == Screenshots ==
47
+
48
+ 1. Loginscreen after a failed login with remaining retries
49
+ 2. Lockout loginscreen
50
+ 3. Administration interface in WordPress 4.5.3
51
+
52
+ == Changelog ==
53
+
54
+ = 2.7.2 =
55
+ * Settings are moved to a separate page.
56
+ * Fixed: login error message. https://wordpress.org/support/topic/how-to-change-login-error-message/
57
+
58
+ = 2.7.1 =
59
+ * A security issue inherited from the ancestor plugin Limit Login Attempts has been fixed.
60
+
61
+ = 2.7.0 =
62
+ * GDPR compliance implemented.
63
+
64
+ * Fixed: ip_in_range() loop $ip overrides itself causing invalid results.
65
+ https://wordpress.org/support/topic/ip_in_range-loop-ip-overrides-itself-causing-invalid-results/
66
+
67
+ * Fixed: the plugin was locking out the same IP address multiple times, each with a different port.
68
+ https://wordpress.org/support/topic/same-ip-different-port/
69
+
70
+ = 2.6.3 =
71
+ * Added support of Sucuri Website Firewall.
72
+
73
+ = 2.6.2 =
74
+ * Fixed the issue with backslashes in usernames.
75
+
76
+ = 2.6.1 =
77
+ * Plugin returns the 403 Forbidden header after the limit of login attempts via XMLRPC is reached.
78
+
79
+ * Added support of IP ranges in white/black lists.
80
+
81
+ * Lockouts now can be released selectively.
82
+
83
+ * Fixed the issue with encoding of special symbols in email notifications.
84
+
85
+ = 2.5.0 =
86
+ * Added Multi-site Compatibility and additional MU settings. https://wordpress.org/support/topic/multisite-compatibility-47/
87
+
88
+ = 2.4.0 =
89
+ * Usernames and IP addresses can be white-listed and black-listed now. https://wordpress.org/support/topic/banning-specific-usernames/ https://wordpress.org/support/topic/good-831/
90
+ * The lockouts log has been inversed. https://wordpress.org/support/topic/inverse-log/
91
+
92
+ = 2.3.0 =
93
+ * IP addresses can be white-listed now. https://wordpress.org/support/topic/legal-user/
94
+ * A "Gateway" column is added to the lockouts log. It shows what endpoint an attacker was blocked from. https://wordpress.org/support/topic/xmlrpc-7/
95
+ * The "Undefined index: client_type" error is fixed. https://wordpress.org/support/topic/php-notice-when-updating-settings-page/
96
+
97
+ = 2.2.0 =
98
+ * Removed the "Handle cookie login" setting as they are now obsolete.
99
+ * Added bruteforce protection against Woocommerce login page attacks. https://wordpress.org/support/topic/how-to-integrate-with-woocommerce-2/
100
+ * Added bruteforce protection against XMLRPC attacks. https://wordpress.org/support/topic/xmlrpc-7/
101
+
102
+ = 2.1.0 =
103
+ * The site connection settings are now applied automatically and therefore have been removed from the admin interface.
104
+ * Now compatible with PHP 5.2 to support some older WP installations.
105
+
106
+ = 2.0.0 =
107
+ * fixed PHP Warning: Illegal offset type in isset or empty https://wordpress.org/support/topic/limit-login-attempts-generating-php-errors
108
+ * fixed the deprecated functions issue
109
+ https://wordpress.org/support/topic/using-deprecated-function
110
+ * Fixed error with function arguments: https://wordpress.org/support/topic/warning-missing-argument-2-5
111
+ * added time stamp to unsuccessful tries on the plugin configuration page.
112
+ * fixed .po translation files issue.
113
  * code refactoring and optimization.
views/options-page.php CHANGED
@@ -1,249 +1,249 @@
1
- <?php
2
-
3
- if( !defined( 'ABSPATH' ) )
4
- exit();
5
-
6
- /**
7
- * @var $this Limit_Login_Attempts
8
- */
9
-
10
- $gdpr = $this->get_option( 'gdpr', 0 );
11
-
12
- $lockouts_total = $this->get_option( 'lockouts_total', 0 );
13
- $lockouts = $this->get_option( 'login_lockouts' );
14
- $lockouts_now = is_array( $lockouts ) ? count( $lockouts ) : 0;
15
-
16
- $v = explode( ',', $this->get_option( 'lockout_notify' ) );
17
- $log_checked = in_array( 'log', $v ) ? ' checked ' : '';
18
- $email_checked = in_array( 'email', $v ) ? ' checked ' : '';
19
-
20
- $white_list_ips = $this->get_option( 'whitelist' );
21
- $white_list_ips = ( is_array( $white_list_ips ) && !empty( $white_list_ips ) ) ? implode( "\n", $white_list_ips ) : '';
22
-
23
- $white_list_usernames = $this->get_option( 'whitelist_usernames' );
24
- $white_list_usernames = ( is_array( $white_list_usernames ) && !empty( $white_list_usernames ) ) ? implode( "\n", $white_list_usernames ) : '';
25
-
26
- $black_list_ips = $this->get_option( 'blacklist' );
27
- $black_list_ips = ( is_array( $black_list_ips ) && !empty( $black_list_ips ) ) ? implode( "\n", $black_list_ips ) : '';
28
-
29
- $black_list_usernames = $this->get_option( 'blacklist_usernames' );
30
- $black_list_usernames = ( is_array( $black_list_usernames ) && !empty( $black_list_usernames ) ) ? implode( "\n", $black_list_usernames ) : '';
31
- ?>
32
- <div class="wrap limit-login-page-settings">
33
- <h2><?php echo __( 'Limit Login Attempts Settings', 'limit-login-attempts-reloaded' ); ?></h2>
34
- <h3><?php echo __( 'Statistics', 'limit-login-attempts-reloaded' ); ?></h3>
35
- <form action="<?php echo $this->get_options_page_uri(); ?>" method="post">
36
- <?php wp_nonce_field( 'limit-login-attempts-options' ); ?>
37
- <table class="form-table">
38
- <tr>
39
- <th scope="row" valign="top"><?php echo __( 'Total lockouts', 'limit-login-attempts-reloaded' ); ?></th>
40
- <td>
41
- <?php if( $lockouts_total > 0 ) { ?>
42
- <input class="button" name="reset_total"
43
- value="<?php echo __( 'Reset Counter', 'limit-login-attempts-reloaded' ); ?>"
44
- type="submit"/>
45
- <?php echo sprintf( _n( '%d lockout since last reset', '%d lockouts since last reset', $lockouts_total, 'limit-login-attempts-reloaded' ), $lockouts_total ); ?>
46
- <?php } else {
47
- echo __( 'No lockouts yet', 'limit-login-attempts-reloaded' );
48
- } ?>
49
- </td>
50
- </tr>
51
- <?php if( $lockouts_now > 0 ) { ?>
52
- <tr>
53
- <th scope="row"
54
- valign="top"><?php echo __( 'Active lockouts', 'limit-login-attempts-reloaded' ); ?></th>
55
- <td>
56
- <input class="button" name="reset_current"
57
- value="<?php echo __( 'Restore Lockouts', 'limit-login-attempts-reloaded' ); ?>"
58
- type="submit"/>
59
- <?php echo sprintf( __( '%d IP is currently blocked from trying to log in', 'limit-login-attempts-reloaded' ), $lockouts_now ); ?>
60
- </td>
61
- </tr>
62
- <?php } ?>
63
- </table>
64
- </form>
65
- <h3><?php echo __( 'Options', 'limit-login-attempts-reloaded' ); ?></h3>
66
- <form action="<?php echo $this->get_options_page_uri(); ?>" method="post">
67
- <?php wp_nonce_field( 'limit-login-attempts-options' ); ?>
68
- <?php if ( is_network_admin() ): ?>
69
- <input type="checkbox" name="allow_local_options" <?php echo $this->get_option( 'allow_local_options' ) ? 'checked' : '' ?> value="1"/> <?php esc_html_e( 'Let network sites use their own settings', 'limit-login-attempts-reloaded' ); ?>
70
- <p class="description"><?php esc_html_e('If disabled, the global settings will be forcibly applied to the entire network.') ?></p>
71
- <?php elseif ( $this->network_mode ): ?>
72
- <input type="checkbox" name="use_global_options" <?php echo $this->get_option('use_local_options' ) ? '' : 'checked' ?> value="1" class="use_global_options"/> <?php echo __( 'Use global settings', 'limit-login-attempts-reloaded' ); ?><br/>
73
- <script>
74
- jQuery(function($){
75
- var first = true;
76
- $('.use_global_options').change( function(){
77
- var form = $(this).siblings('table');
78
- form.stop();
79
-
80
- if ( this.checked )
81
- first ? form.hide() : form.fadeOut();
82
- else
83
- first ? form.show() : form.fadeIn();
84
-
85
- first = false;
86
- }).change();
87
- });
88
- </script>
89
- <?php endif ?>
90
- <table class="form-table">
91
- <tr>
92
- <th scope="row"
93
- valign="top"><?php echo __( 'GDPR compliance', 'limit-login-attempts-reloaded' ); ?></th>
94
- <td>
95
- <input type="checkbox" name="gdpr" value="1" <?php if($gdpr): ?> checked <?php endif; ?>/>
96
- <?php echo __( 'this makes the plugin <a href="https://gdpr-info.eu/" target="_blank" >GDPR</a> compliant', 'limit-login-attempts-reloaded' ); ?> <br/>
97
- </td>
98
- </tr>
99
- <tr>
100
- <th scope="row" valign="top"><?php echo __( 'Lockout', 'limit-login-attempts-reloaded' ); ?></th>
101
- <td>
102
-
103
- <input type="text" size="3" maxlength="4"
104
- value="<?php echo( $this->get_option( 'allowed_retries' ) ); ?>"
105
- name="allowed_retries"/> <?php echo __( 'allowed retries', 'limit-login-attempts-reloaded' ); ?>
106
- <br/>
107
- <input type="text" size="3" maxlength="4"
108
- value="<?php echo( $this->get_option( 'lockout_duration' ) / 60 ); ?>"
109
- name="lockout_duration"/> <?php echo __( 'minutes lockout', 'limit-login-attempts-reloaded' ); ?>
110
- <br/>
111
- <input type="text" size="3" maxlength="4"
112
- value="<?php echo( $this->get_option( 'allowed_lockouts' ) ); ?>"
113
- name="allowed_lockouts"/> <?php echo __( 'lockouts increase lockout time to', 'limit-login-attempts-reloaded' ); ?>
114
- <input type="text" size="3" maxlength="4"
115
- value="<?php echo( $this->get_option( 'long_duration' ) / 3600 ); ?>"
116
- name="long_duration"/> <?php echo __( 'hours', 'limit-login-attempts-reloaded' ); ?> <br/>
117
- <input type="text" size="3" maxlength="4"
118
- value="<?php echo( $this->get_option( 'valid_duration' ) / 3600 ); ?>"
119
- name="valid_duration"/> <?php echo __( 'hours until retries are reset', 'limit-login-attempts-reloaded' ); ?>
120
- </td>
121
- </tr>
122
- <tr>
123
- <th scope="row"
124
- valign="top"><?php echo __( 'Notify on lockout', 'limit-login-attempts-reloaded' ); ?></th>
125
- <td>
126
- <input type="checkbox" name="lockout_notify_log" <?php echo $log_checked; ?>
127
- value="log"/> <?php echo __( 'Lockout log', 'limit-login-attempts-reloaded' ); ?><br/>
128
- <input type="checkbox" name="lockout_notify_email" <?php echo $email_checked; ?>
129
- value="email"/> <?php echo __( 'Email to admin after', 'limit-login-attempts-reloaded' ); ?>
130
- <input type="text" size="3" maxlength="4"
131
- value="<?php echo( $this->get_option( 'notify_email_after' ) ); ?>"
132
- name="email_after"/> <?php echo __( 'lockouts', 'limit-login-attempts-reloaded' ); ?>
133
- </td>
134
- </tr>
135
- <tr>
136
- <th scope="row"
137
- valign="top"><?php echo __( 'Whitelist', 'limit-login-attempts-reloaded' ); ?></th>
138
- <td>
139
- <div class="field-col">
140
- <p class="description"><?php _e( 'One IP or IP range (1.2.3.4-5.6.7.8) per line', 'limit-login-attempts-reloaded' ); ?></p>
141
- <textarea name="lla_whitelist_ips" rows="10" cols="50"><?php echo esc_textarea( $white_list_ips ); ?></textarea>
142
- </div>
143
- <div class="field-col">
144
- <p class="description"><?php _e( 'One Username per line', 'limit-login-attempts-reloaded' ); ?></p>
145
- <textarea name="lla_whitelist_usernames" rows="10" cols="50"><?php echo esc_textarea( $white_list_usernames ); ?></textarea>
146
- </div>
147
- </td>
148
- </tr>
149
- <tr>
150
- <th scope="row"
151
- valign="top"><?php echo __( 'Blacklist', 'limit-login-attempts-reloaded' ); ?></th>
152
- <td>
153
- <div class="field-col">
154
- <p class="description"><?php _e( 'One IP or IP range (1.2.3.4-5.6.7.8) per line', 'limit-login-attempts-reloaded' ); ?></p>
155
- <textarea name="lla_blacklist_ips" rows="10" cols="50"><?php echo esc_textarea( $black_list_ips ); ?></textarea>
156
- </div>
157
- <div class="field-col">
158
- <p class="description"><?php _e( 'One Username per line', 'limit-login-attempts-reloaded' ); ?></p>
159
- <textarea name="lla_blacklist_usernames" rows="10" cols="50"><?php echo esc_textarea( $black_list_usernames ); ?></textarea>
160
- </div>
161
- </td>
162
- </tr>
163
- </table>
164
- <p class="submit">
165
- <input class="button button-primary" name="update_options" value="<?php echo __( 'Save Options', 'limit-login-attempts-reloaded' ); ?>"
166
- type="submit"/>
167
- </p>
168
- </form>
169
- <?php
170
- $log = $this->get_option( 'logged' );
171
- $log = LLA_Helpers::sorted_log_by_date( $log );
172
-
173
- $lockouts = (array)$this->get_option('lockouts');
174
-
175
- if( is_array( $log ) && ! empty( $log ) ) { ?>
176
- <h3><?php echo __( 'Lockout log', 'limit-login-attempts-reloaded' ); ?></h3>
177
- <form action="<?php echo $this->get_options_page_uri(); ?>" method="post">
178
- <?php wp_nonce_field( 'limit-login-attempts-options' ); ?>
179
- <input type="hidden" value="true" name="clear_log"/>
180
- <p class="submit">
181
- <input class="button" name="submit" value="<?php echo __( 'Clear Log', 'limit-login-attempts-reloaded' ); ?>"
182
- type="submit"/>
183
- </p>
184
- </form>
185
-
186
- <div class="limit-login-log">
187
- <table class="form-table">
188
- <tr>
189
- <th scope="col"><?php _e( "Date", 'limit-login-attempts-reloaded' ); ?></th>
190
- <th scope="col"><?php echo _x( "IP", "Internet address", 'limit-login-attempts-reloaded' ); ?></th>
191
- <th scope="col"><?php _e( 'Tried to log in as', 'limit-login-attempts-reloaded' ); ?></th>
192
- <th scope="col"><?php _e( 'Gateway', 'limit-login-attempts-reloaded' ); ?></th>
193
- <th>
194
- </tr>
195
-
196
- <?php foreach ( $log as $date => $user_info ) : ?>
197
- <tr>
198
- <td class="limit-login-date"><?php echo date_i18n( 'F d, Y H:i', $date ); ?></td>
199
- <td class="limit-login-ip">
200
- <?php echo esc_html( $user_info['ip'] ); ?>
201
- </td>
202
- <td class="limit-login-max"><?php echo esc_html( $user_info['username'] ) . ' (' . esc_html( $user_info['counter'] ) .' lockouts)'; ?></td>
203
- <td class="limit-login-gateway"><?php echo esc_html( $user_info['gateway'] ); ?></td>
204
- <td>
205
- <?php if ( !empty( $lockouts[ $user_info['ip'] ] ) && $lockouts[ $user_info['ip'] ] > time() ) : ?>
206
- <a href="#" class="button limit-login-unlock" data-ip="<?=esc_attr($user_info['ip'])?>" data-username="<?=esc_attr($user_info['username'])?>">Unlock</a>
207
- <?php elseif ( $user_info['unlocked'] ): ?>
208
- Unlocked
209
- <?php endif ?>
210
- </tr>
211
- <?php endforeach; ?>
212
-
213
- </table>
214
- </div>
215
- <script>jQuery( function($) {
216
- $('.limit-login-log .limit-login-unlock').click( function()
217
- {
218
- var btn = $(this);
219
-
220
- if ( btn.hasClass('disabled') )
221
- return false;
222
- btn.addClass( 'disabled' );
223
-
224
- $.post( ajaxurl, {
225
- action: 'limit-login-unlock',
226
- sec: '<?=wp_create_nonce('limit-login-unlock') ?>',
227
- ip: btn.data('ip'),
228
- username: btn.data('username')
229
- } )
230
- .done( function(data) {
231
- if ( data === true )
232
- btn.fadeOut( function(){ $(this).parent().text('Unlocked') });
233
- else
234
- fail();
235
- }).fail( fail );
236
-
237
- function fail() {
238
- alert('Connection error');
239
- btn.removeClass('disabled');
240
- }
241
-
242
- return false;
243
- } );
244
- } )</script>
245
- <?php
246
- } /* if showing $log */
247
- ?>
248
-
249
  </div>
1
+ <?php
2
+
3
+ if( !defined( 'ABSPATH' ) )
4
+ exit();
5
+
6
+ /**
7
+ * @var $this Limit_Login_Attempts
8
+ */
9
+
10
+ $gdpr = $this->get_option( 'gdpr', 0 );
11
+
12
+ $lockouts_total = $this->get_option( 'lockouts_total', 0 );
13
+ $lockouts = $this->get_option( 'login_lockouts' );
14
+ $lockouts_now = is_array( $lockouts ) ? count( $lockouts ) : 0;
15
+
16
+ $v = explode( ',', $this->get_option( 'lockout_notify' ) );
17
+ $log_checked = in_array( 'log', $v ) ? ' checked ' : '';
18
+ $email_checked = in_array( 'email', $v ) ? ' checked ' : '';
19
+
20
+ $white_list_ips = $this->get_option( 'whitelist' );
21
+ $white_list_ips = ( is_array( $white_list_ips ) && !empty( $white_list_ips ) ) ? implode( "\n", $white_list_ips ) : '';
22
+
23
+ $white_list_usernames = $this->get_option( 'whitelist_usernames' );
24
+ $white_list_usernames = ( is_array( $white_list_usernames ) && !empty( $white_list_usernames ) ) ? implode( "\n", $white_list_usernames ) : '';
25
+
26
+ $black_list_ips = $this->get_option( 'blacklist' );
27
+ $black_list_ips = ( is_array( $black_list_ips ) && !empty( $black_list_ips ) ) ? implode( "\n", $black_list_ips ) : '';
28
+
29
+ $black_list_usernames = $this->get_option( 'blacklist_usernames' );
30
+ $black_list_usernames = ( is_array( $black_list_usernames ) && !empty( $black_list_usernames ) ) ? implode( "\n", $black_list_usernames ) : '';
31
+ ?>
32
+ <div class="wrap limit-login-page-settings">
33
+ <h2><?php echo __( 'Limit Login Attempts Settings', 'limit-login-attempts-reloaded' ); ?></h2>
34
+ <h3><?php echo __( 'Statistics', 'limit-login-attempts-reloaded' ); ?></h3>
35
+ <form action="<?php echo $this->get_options_page_uri(); ?>" method="post">
36
+ <?php wp_nonce_field( 'limit-login-attempts-options' ); ?>
37
+ <table class="form-table">
38
+ <tr>
39
+ <th scope="row" valign="top"><?php echo __( 'Total lockouts', 'limit-login-attempts-reloaded' ); ?></th>
40
+ <td>
41
+ <?php if( $lockouts_total > 0 ) { ?>
42
+ <input class="button" name="reset_total"
43
+ value="<?php echo __( 'Reset Counter', 'limit-login-attempts-reloaded' ); ?>"
44
+ type="submit"/>
45
+ <?php echo sprintf( _n( '%d lockout since last reset', '%d lockouts since last reset', $lockouts_total, 'limit-login-attempts-reloaded' ), $lockouts_total ); ?>
46
+ <?php } else {
47
+ echo __( 'No lockouts yet', 'limit-login-attempts-reloaded' );
48
+ } ?>
49
+ </td>
50
+ </tr>
51
+ <?php if( $lockouts_now > 0 ) { ?>
52
+ <tr>
53
+ <th scope="row"
54
+ valign="top"><?php echo __( 'Active lockouts', 'limit-login-attempts-reloaded' ); ?></th>
55
+ <td>
56
+ <input class="button" name="reset_current"
57
+ value="<?php echo __( 'Restore Lockouts', 'limit-login-attempts-reloaded' ); ?>"
58
+ type="submit"/>
59
+ <?php echo sprintf( __( '%d IP is currently blocked from trying to log in', 'limit-login-attempts-reloaded' ), $lockouts_now ); ?>
60
+ </td>
61
+ </tr>
62
+ <?php } ?>
63
+ </table>
64
+ </form>
65
+ <h3><?php echo __( 'Options', 'limit-login-attempts-reloaded' ); ?></h3>
66
+ <form action="<?php echo $this->get_options_page_uri(); ?>" method="post">
67
+ <?php wp_nonce_field( 'limit-login-attempts-options' ); ?>
68
+ <?php if ( is_network_admin() ): ?>
69
+ <input type="checkbox" name="allow_local_options" <?php echo $this->get_option( 'allow_local_options' ) ? 'checked' : '' ?> value="1"/> <?php esc_html_e( 'Let network sites use their own settings', 'limit-login-attempts-reloaded' ); ?>
70
+ <p class="description"><?php esc_html_e('If disabled, the global settings will be forcibly applied to the entire network.') ?></p>
71
+ <?php elseif ( $this->network_mode ): ?>
72
+ <input type="checkbox" name="use_global_options" <?php echo $this->get_option('use_local_options' ) ? '' : 'checked' ?> value="1" class="use_global_options"/> <?php echo __( 'Use global settings', 'limit-login-attempts-reloaded' ); ?><br/>
73
+ <script>
74
+ jQuery(function($){
75
+ var first = true;
76
+ $('.use_global_options').change( function(){
77
+ var form = $(this).siblings('table');
78
+ form.stop();
79
+
80
+ if ( this.checked )
81
+ first ? form.hide() : form.fadeOut();
82
+ else
83
+ first ? form.show() : form.fadeIn();
84
+
85
+ first = false;
86
+ }).change();
87
+ });
88
+ </script>
89
+ <?php endif ?>
90
+ <table class="form-table">
91
+ <tr>
92
+ <th scope="row"
93
+ valign="top"><?php echo __( 'GDPR compliance', 'limit-login-attempts-reloaded' ); ?></th>
94
+ <td>
95
+ <input type="checkbox" name="gdpr" value="1" <?php if($gdpr): ?> checked <?php endif; ?>/>
96
+ <?php echo __( 'this makes the plugin <a href="https://gdpr-info.eu/" target="_blank" >GDPR</a> compliant', 'limit-login-attempts-reloaded' ); ?> <br/>
97
+ </td>
98
+ </tr>
99
+ <tr>
100
+ <th scope="row" valign="top"><?php echo __( 'Lockout', 'limit-login-attempts-reloaded' ); ?></th>
101
+ <td>
102
+
103
+ <input type="text" size="3" maxlength="4"
104
+ value="<?php echo( $this->get_option( 'allowed_retries' ) ); ?>"
105
+ name="allowed_retries"/> <?php echo __( 'allowed retries', 'limit-login-attempts-reloaded' ); ?>
106
+ <br/>
107
+ <input type="text" size="3" maxlength="4"
108
+ value="<?php echo( $this->get_option( 'lockout_duration' ) / 60 ); ?>"
109
+ name="lockout_duration"/> <?php echo __( 'minutes lockout', 'limit-login-attempts-reloaded' ); ?>
110
+ <br/>
111
+ <input type="text" size="3" maxlength="4"
112
+ value="<?php echo( $this->get_option( 'allowed_lockouts' ) ); ?>"
113
+ name="allowed_lockouts"/> <?php echo __( 'lockouts increase lockout time to', 'limit-login-attempts-reloaded' ); ?>
114
+ <input type="text" size="3" maxlength="4"
115
+ value="<?php echo( $this->get_option( 'long_duration' ) / 3600 ); ?>"
116
+ name="long_duration"/> <?php echo __( 'hours', 'limit-login-attempts-reloaded' ); ?> <br/>
117
+ <input type="text" size="3" maxlength="4"
118
+ value="<?php echo( $this->get_option( 'valid_duration' ) / 3600 ); ?>"
119
+ name="valid_duration"/> <?php echo __( 'hours until retries are reset', 'limit-login-attempts-reloaded' ); ?>
120
+ </td>
121
+ </tr>
122
+ <tr>
123
+ <th scope="row"
124
+ valign="top"><?php echo __( 'Notify on lockout', 'limit-login-attempts-reloaded' ); ?></th>
125
+ <td>
126
+ <input type="checkbox" name="lockout_notify_log" <?php echo $log_checked; ?>
127
+ value="log"/> <?php echo __( 'Lockout log', 'limit-login-attempts-reloaded' ); ?><br/>
128
+ <input type="checkbox" name="lockout_notify_email" <?php echo $email_checked; ?>
129
+ value="email"/> <?php echo __( 'Email to admin after', 'limit-login-attempts-reloaded' ); ?>
130
+ <input type="text" size="3" maxlength="4"
131
+ value="<?php echo( $this->get_option( 'notify_email_after' ) ); ?>"
132
+ name="email_after"/> <?php echo __( 'lockouts', 'limit-login-attempts-reloaded' ); ?>
133
+ </td>
134
+ </tr>
135
+ <tr>
136
+ <th scope="row"
137
+ valign="top"><?php echo __( 'Whitelist', 'limit-login-attempts-reloaded' ); ?></th>
138
+ <td>
139
+ <div class="field-col">
140
+ <p class="description"><?php _e( 'One IP or IP range (1.2.3.4-5.6.7.8) per line', 'limit-login-attempts-reloaded' ); ?></p>
141
+ <textarea name="lla_whitelist_ips" rows="10" cols="50"><?php echo esc_textarea( $white_list_ips ); ?></textarea>
142
+ </div>
143
+ <div class="field-col">
144
+ <p class="description"><?php _e( 'One Username per line', 'limit-login-attempts-reloaded' ); ?></p>
145
+ <textarea name="lla_whitelist_usernames" rows="10" cols="50"><?php echo esc_textarea( $white_list_usernames ); ?></textarea>
146
+ </div>
147
+ </td>
148
+ </tr>
149
+ <tr>
150
+ <th scope="row"
151
+ valign="top"><?php echo __( 'Blacklist', 'limit-login-attempts-reloaded' ); ?></th>
152
+ <td>
153
+ <div class="field-col">
154
+ <p class="description"><?php _e( 'One IP or IP range (1.2.3.4-5.6.7.8) per line', 'limit-login-attempts-reloaded' ); ?></p>
155
+ <textarea name="lla_blacklist_ips" rows="10" cols="50"><?php echo esc_textarea( $black_list_ips ); ?></textarea>
156
+ </div>
157
+ <div class="field-col">
158
+ <p class="description"><?php _e( 'One Username per line', 'limit-login-attempts-reloaded' ); ?></p>
159
+ <textarea name="lla_blacklist_usernames" rows="10" cols="50"><?php echo esc_textarea( $black_list_usernames ); ?></textarea>
160
+ </div>
161
+ </td>
162
+ </tr>
163
+ </table>
164
+ <p class="submit">
165
+ <input class="button button-primary" name="update_options" value="<?php echo __( 'Save Options', 'limit-login-attempts-reloaded' ); ?>"
166
+ type="submit"/>
167
+ </p>
168
+ </form>
169
+ <?php
170
+ $log = $this->get_option( 'logged' );
171
+ $log = LLA_Helpers::sorted_log_by_date( $log );
172
+
173
+ $lockouts = (array)$this->get_option('lockouts');
174
+
175
+ if( is_array( $log ) && ! empty( $log ) ) { ?>
176
+ <h3><?php echo __( 'Lockout log', 'limit-login-attempts-reloaded' ); ?></h3>
177
+ <form action="<?php echo $this->get_options_page_uri(); ?>" method="post">
178
+ <?php wp_nonce_field( 'limit-login-attempts-options' ); ?>
179
+ <input type="hidden" value="true" name="clear_log"/>
180
+ <p class="submit">
181
+ <input class="button" name="submit" value="<?php echo __( 'Clear Log', 'limit-login-attempts-reloaded' ); ?>"
182
+ type="submit"/>
183
+ </p>
184
+ </form>
185
+
186
+ <div class="limit-login-log">
187
+ <table class="form-table">
188
+ <tr>
189
+ <th scope="col"><?php _e( "Date", 'limit-login-attempts-reloaded' ); ?></th>
190
+ <th scope="col"><?php echo _x( "IP", "Internet address", 'limit-login-attempts-reloaded' ); ?></th>
191
+ <th scope="col"><?php _e( 'Tried to log in as', 'limit-login-attempts-reloaded' ); ?></th>
192
+ <th scope="col"><?php _e( 'Gateway', 'limit-login-attempts-reloaded' ); ?></th>
193
+ <th>
194
+ </tr>
195
+
196
+ <?php foreach ( $log as $date => $user_info ) : ?>
197
+ <tr>
198
+ <td class="limit-login-date"><?php echo date_i18n( 'F d, Y H:i', $date ); ?></td>
199
+ <td class="limit-login-ip">
200
+ <?php echo esc_html( $user_info['ip'] ); ?>
201
+ </td>
202
+ <td class="limit-login-max"><?php echo esc_html( $user_info['username'] ) . ' (' . esc_html( $user_info['counter'] ) .' lockouts)'; ?></td>
203
+ <td class="limit-login-gateway"><?php echo esc_html( $user_info['gateway'] ); ?></td>
204
+ <td>
205
+ <?php if ( !empty( $lockouts[ $user_info['ip'] ] ) && $lockouts[ $user_info['ip'] ] > time() ) : ?>
206
+ <a href="#" class="button limit-login-unlock" data-ip="<?=esc_attr($user_info['ip'])?>" data-username="<?=esc_attr($user_info['username'])?>">Unlock</a>
207
+ <?php elseif ( $user_info['unlocked'] ): ?>
208
+ Unlocked
209
+ <?php endif ?>
210
+ </tr>
211
+ <?php endforeach; ?>
212
+
213
+ </table>
214
+ </div>
215
+ <script>jQuery( function($) {
216
+ $('.limit-login-log .limit-login-unlock').click( function()
217
+ {
218
+ var btn = $(this);
219
+
220
+ if ( btn.hasClass('disabled') )
221
+ return false;
222
+ btn.addClass( 'disabled' );
223
+
224
+ $.post( ajaxurl, {
225
+ action: 'limit-login-unlock',
226
+ sec: '<?=wp_create_nonce('limit-login-unlock') ?>',
227
+ ip: btn.data('ip'),
228
+ username: btn.data('username')
229
+ } )
230
+ .done( function(data) {
231
+ if ( data === true )
232
+ btn.fadeOut( function(){ $(this).parent().text('Unlocked') });
233
+ else
234
+ fail();
235
+ }).fail( fail );
236
+
237
+ function fail() {
238
+ alert('Connection error');
239
+ btn.removeClass('disabled');
240
+ }
241
+
242
+ return false;
243
+ } );
244
+ } )</script>
245
+ <?php
246
+ } /* if showing $log */
247
+ ?>
248
+
249
  </div>