Limit Login Attempts Reloaded - Version 2.5.0

Version Description

  • Added Multi-site Compatibility and additional MU settings. https://wordpress.org/support/topic/multisite-compatibility-47/
Download this release

Release Info

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

Code changes from version 2.4.0 to 2.5.0

core/LimitLoginAttempts.php CHANGED
@@ -1,1078 +1,1140 @@
1
  <?php
2
 
3
  /**
4
- * Class Limit_Login_Attempts
5
- */
6
- class Limit_Login_Attempts {
7
-
8
- /**
9
- * Main plugin options
10
- * @var array
11
- */
12
- public $_options = array(
13
- /* Are we behind a proxy? */
14
- 'client_type' => LLA_DIRECT_ADDR,
15
-
16
- /* Lock out after this many tries */
17
- 'allowed_retries' => 4,
18
-
19
- /* Lock out for this many seconds */
20
- 'lockout_duration' => 1200, // 20 minutes
21
-
22
- /* Long lock out after this many lockouts */
23
- 'allowed_lockouts' => 4,
24
-
25
- /* Long lock out for this many seconds */
26
- 'long_duration' => 86400, // 24 hours,
27
-
28
- /* Reset failed attempts after this many seconds */
29
- 'valid_duration' => 43200, // 12 hours
30
-
31
- /* Also limit malformed/forged cookies? */
32
- 'cookies' => true,
33
-
34
- /* Notify on lockout. Values: '', 'log', 'email', 'log,email' */
35
- 'lockout_notify' => 'log',
36
-
37
- /* If notify by email, do so after this number of lockouts */
38
- 'notify_email_after' => 4,
39
-
40
- 'whitelist' => array(),
41
- 'whitelist_usernames' => array(),
42
- 'blacklist' => array(),
43
- 'blacklist_usernames' => array(),
44
- );
45
-
46
- /**
47
- * Admin options page slug
48
- * @var string
49
- */
50
- private $_options_page_slug = 'limit-login-attempts';
51
-
52
- /**
53
- * Errors messages
54
- *
55
- * @var array
56
- */
57
- public $_errors = array();
58
-
59
- public function __construct() {
60
- $this->hooks_init();
61
- }
62
-
63
- /**
64
- * Register wp hooks and filters
65
- */
66
- public function hooks_init() {
67
- add_action( 'plugins_loaded', array( $this, 'setup' ), 9999 );
68
- add_action( 'admin_enqueue_scripts', array( $this, 'enqueue' ) );
69
- add_filter( 'limit_login_whitelist_ip', array( $this, 'check_whitelist_ips' ), 10, 2 );
70
- add_filter( 'limit_login_whitelist_usernames', array( $this, 'check_whitelist_usernames' ), 10, 2 );
71
- add_filter( 'limit_login_blacklist_ip', array( $this, 'check_blacklist_ips' ), 10, 2 );
72
- add_filter( 'limit_login_blacklist_usernames', array( $this, 'check_blacklist_usernames' ), 10, 2 );
73
- }
74
-
75
- /**
76
- * Hook 'plugins_loaded'
77
- */
78
- public function setup() {
79
-
80
- // Load languages files
81
- load_plugin_textdomain( 'limit-login-attempts-reloaded', false, plugin_basename( dirname( __FILE__ ) ) . '/../languages' );
82
-
83
- // Check if installed old plugin
84
- $this->check_original_installed();
85
-
86
- // Setup default plugin options
87
- $this->setup_options();
88
-
89
- add_action( 'wp_login_failed', array( $this, 'limit_login_failed' ) );
90
- add_filter( 'wp_authenticate_user', array( $this, 'wp_authenticate_user' ), 99999, 2 );
91
-
92
- add_filter( 'shake_error_codes', array( $this, 'failure_shake' ) );
93
- add_action( 'login_head', array( $this, 'add_error_message' ) );
94
- add_action( 'login_errors', array( $this, 'fixup_error_messages' ) );
95
- add_action( 'admin_menu', array( $this, 'admin_menu' ) );
96
-
97
- // Add notices for XMLRPC request
98
- add_filter( 'xmlrpc_login_error', array( $this, 'xmlrpc_error_messages' ) );
99
-
100
- // Add notices to woocommerce login page
101
- add_action( 'wp_head', array( $this, 'add_wc_notices' ) );
102
-
103
- /*
104
- * This action should really be changed to the 'authenticate' filter as
105
- * it will probably be deprecated. That is however only available in
106
- * later versions of WP.
107
- */
108
- add_action( 'wp_authenticate', array( $this, 'track_credentials' ), 10, 2 );
109
- add_action( 'authenticate', array( $this, 'authenticate_filter' ), 5, 3 );
110
- }
111
-
112
- public function check_whitelist_ips( $allow, $ip ) {
113
- return in_array( $ip, (array) $this->get_option( 'whitelist' ) );
114
- }
115
-
116
- public function check_whitelist_usernames( $allow, $username ) {
117
- return in_array( $username, (array) $this->get_option( 'whitelist_usernames' ) );
118
- }
119
-
120
- public function check_blacklist_ips( $allow, $ip ) {
121
- return in_array( $ip, (array) $this->get_option( 'blacklist' ) );
122
- }
123
-
124
- public function check_blacklist_usernames( $allow, $username ) {
125
- return in_array( $username, (array) $this->get_option( 'blacklist_usernames' ) );
126
- }
127
-
128
- /**
129
- * @param $error IXR_Error
130
- *
131
- * @return IXR_Error
132
- */
133
- public function xmlrpc_error_messages( $error ) {
134
-
135
- if ( ! class_exists( 'IXR_Error' ) ) {
136
- return $error;
137
- }
138
-
139
- if ( ! $this->is_limit_login_ok() ) {
140
- return new IXR_Error( 403, $this->error_msg() );
141
- }
142
-
143
- $ip = $this->get_address();
144
- $retries = get_option( 'limit_login_retries' );
145
- $valid = get_option( 'limit_login_retries_valid' );
146
-
147
- /* Should we show retries remaining? */
148
-
149
- if ( ! is_array( $retries ) || ! is_array( $valid ) ) {
150
- /* no retries at all */
151
- return $error;
152
- }
153
- if ( ! isset( $retries[ $ip ] ) || ! isset( $valid[ $ip ] ) || time() > $valid[ $ip ] ) {
154
- /* no: no valid retries */
155
- return $error;
156
- }
157
- if ( ( $retries[ $ip ] % $this->get_option( 'allowed_retries' ) ) == 0 ) {
158
- /* no: already been locked out for these retries */
159
- return $error;
160
- }
161
-
162
- $remaining = max( ( $this->get_option( 'allowed_retries' ) - ( $retries[ $ip ] % $this->get_option( 'allowed_retries' ) ) ), 0 );
163
-
164
- return new IXR_Error( 403, sprintf( _n( "<strong>%d</strong> attempt remaining.", "<strong>%d</strong> attempts remaining.", $remaining, 'limit-login-attempts-reloaded' ), $remaining ) );
165
- }
166
-
167
- /**
168
- * Errors on WooCommerce account page
169
- */
170
- public function add_wc_notices() {
171
-
172
- global $limit_login_just_lockedout, $limit_login_nonempty_credentials, $limit_login_my_error_shown;
173
-
174
- if ( ! function_exists( 'is_account_page' ) || ! function_exists( 'wc_add_notice' ) ) {
175
- return;
176
- }
177
-
178
- /*
179
- * During lockout we do not want to show any other error messages (like
180
- * unknown user or empty password).
181
- */
182
- if ( empty( $_POST ) && ! $this->is_limit_login_ok() && ! $limit_login_just_lockedout ) {
183
- if ( is_account_page() ) {
184
- wc_add_notice( $this->error_msg(), 'error' );
185
- }
186
- }
187
-
188
- }
189
-
190
- /**
191
- * @param $user
192
- * @param $username
193
- * @param $password
194
- *
195
- * @return WP_Error | WP_User
196
- */
197
- public function authenticate_filter( $user, $username, $password ) {
198
-
199
- if ( ! empty( $username ) && ! empty( $password ) ) {
200
-
201
- $ip = $this->get_address();
202
-
203
- // Check if username is blacklisted
204
- if ( ! $this->is_username_whitelisted( $username ) && ! $this->is_ip_whitelisted( $ip ) &&
205
- ( $this->is_username_blacklisted( $username ) || $this->is_ip_blacklisted( $ip ) )
206
- ) {
207
-
208
- remove_filter( 'login_errors', array( $this, 'fixup_error_messages' ) );
209
- remove_filter( 'login_head', array( $this, 'add_error_message' ) );
210
- remove_filter( 'wp_login_failed', array( $this, 'limit_login_failed' ) );
211
- remove_filter( 'wp_authenticate_user', array( $this, 'wp_authenticate_user' ), 99999 );
212
- remove_filter( 'login_head', array( $this, 'add_error_message' ) );
213
- remove_filter( 'login_errors', array( $this, 'fixup_error_messages' ) );
214
-
215
- remove_filter( 'authenticate', 'wp_authenticate_username_password', 20 );
216
- remove_filter( 'authenticate', 'wp_authenticate_email_password', 20 );
217
-
218
- $user = new WP_Error();
219
- $user->add( 'username_blacklisted', "<strong>ERROR:</strong> Too many failed login attempts." );
220
-
221
- } elseif ( $this->is_username_whitelisted( $username ) || $this->is_ip_whitelisted( $ip ) ) {
222
-
223
- remove_filter( 'wp_login_failed', array( $this, 'limit_login_failed' ) );
224
- remove_filter( 'wp_authenticate_user', array( $this, 'wp_authenticate_user' ), 99999 );
225
- remove_filter( 'login_head', array( $this, 'add_error_message' ) );
226
- remove_filter( 'login_errors', array( $this, 'fixup_error_messages' ) );
227
-
228
- }
229
-
230
- }
231
-
232
- return $user;
233
- }
234
-
235
- /**
236
- * Check if the original plugin is installed
237
- */
238
- private function check_original_installed() {
239
-
240
- if ( defined( 'LIMIT_LOGIN_DIRECT_ADDR' ) ) { // Original plugin is installed
241
-
242
- if ( $active_plugins = get_option( 'active_plugins' ) ) {
243
- $deactivate_this = array(
244
- 'limit-login-attempts-reloaded/limit-login-attempts-reloaded.php'
245
- );
246
- $active_plugins = array_diff( $active_plugins, $deactivate_this );
247
- update_option( 'active_plugins', $active_plugins );
248
-
249
- add_action( 'admin_notices', function () {
250
- ?>
251
- <div class="notice notice-error is-dismissible">
252
- <p><?php _e( 'Please deactivate the Limit Login Attempts first.', 'limit-login-attempts-reloaded' ); ?></p>
253
- </div>
254
- <?php
255
- } );
256
- }
257
- }
258
- }
259
-
260
- /**
261
- * Enqueue js and css
262
- */
263
- public function enqueue() {
264
- wp_enqueue_style( 'lla-main', LLA_PLUGIN_URL . '/assets/css/limit-login-attempts.css' );
265
- }
266
-
267
- /**
268
- * Add admin options page
269
- */
270
- public function admin_menu() {
271
- global $wp_version;
272
-
273
- // Modern WP?
274
- if ( version_compare( $wp_version, '3.0', '>=' ) ) {
275
- add_options_page( 'Limit Login Attempts', 'Limit Login Attempts', 'manage_options', $this->_options_page_slug, array(
276
- $this,
277
- 'options_page'
278
- ) );
279
-
280
- return;
281
- }
282
-
283
- // Older WPMU?
284
- if ( function_exists( "get_current_site" ) ) {
285
- add_submenu_page( 'wpmu-admin.php', 'Limit Login Attempts', 'Limit Login Attempts', 9, $this->_options_page_slug, array(
286
- $this,
287
- 'options_page'
288
- ) );
289
-
290
- return;
291
- }
292
-
293
- // Older WP
294
- add_options_page( 'Limit Login Attempts', 'Limit Login Attempts', 9, $this->_options_page_slug, array(
295
- $this,
296
- 'options_page'
297
- ) );
298
- }
299
-
300
- /**
301
- * Get the correct options page URI
302
- *
303
- * @return mixed
304
- */
305
- public function get_options_page_uri() {
306
- return menu_page_url( $this->_options_page_slug, false );
307
- }
308
-
309
- /**
310
- * Get option by name
311
- *
312
- * @param $option_name
313
- *
314
- * @return null
315
- */
316
- public function get_option( $option_name ) {
317
- return ( isset( $this->_options[ $option_name ] ) ) ? $this->_options[ $option_name ] : null;
318
- }
319
-
320
- /**
321
- * Setup main options
322
- */
323
- public function setup_options() {
324
- $this->update_option_from_db( 'limit_login_client_type', 'client_type' );
325
- $this->update_option_from_db( 'limit_login_allowed_retries', 'allowed_retries' );
326
- $this->update_option_from_db( 'limit_login_lockout_duration', 'lockout_duration' );
327
- $this->update_option_from_db( 'limit_login_valid_duration', 'valid_duration' );
328
- $this->update_option_from_db( 'limit_login_cookies', 'cookies' );
329
- $this->update_option_from_db( 'limit_login_lockout_notify', 'lockout_notify' );
330
- $this->update_option_from_db( 'limit_login_allowed_lockouts', 'allowed_lockouts' );
331
- $this->update_option_from_db( 'limit_login_long_duration', 'long_duration' );
332
- $this->update_option_from_db( 'limit_login_notify_email_after', 'notify_email_after' );
333
- $this->update_option_from_db( 'limit_login_whitelist', 'whitelist' );
334
- $this->update_option_from_db( 'limit_login_whitelist_usernames', 'whitelist_usernames' );
335
- $this->update_option_from_db( 'limit_login_blacklist', 'blacklist' );
336
- $this->update_option_from_db( 'limit_login_blacklist_usernames', 'blacklist_usernames' );
337
-
338
- $this->sanitize_variables();
339
- }
340
-
341
- public function sanitize_variables() {
342
-
343
- $this->sanitize_simple_int( 'allowed_retries' );
344
- $this->sanitize_simple_int( 'lockout_duration' );
345
- $this->sanitize_simple_int( 'valid_duration' );
346
- $this->sanitize_simple_int( 'allowed_lockouts' );
347
- $this->sanitize_simple_int( 'long_duration' );
348
-
349
- $this->_options['cookies'] = ! ! $this->get_option( 'cookies' );
350
-
351
- $notify_email_after = max( 1, intval( $this->get_option( 'notify_email_after' ) ) );
352
- $this->_options['notify_email_after'] = min( $this->get_option( 'allowed_lockouts' ), $notify_email_after );
353
-
354
- $args = explode( ',', $this->get_option( 'lockout_notify' ) );
355
- $args_allowed = explode( ',', LLA_LOCKOUT_NOTIFY_ALLOWED );
356
- $new_args = array();
357
- foreach ( $args as $a ) {
358
- if ( in_array( $a, $args_allowed ) ) {
359
- $new_args[] = $a;
360
- }
361
- }
362
- $this->_options['lockout_notify'] = implode( ',', $new_args );
363
-
364
- if ( $this->get_option( 'client_type' ) != LLA_DIRECT_ADDR && $this->get_option( 'client_type' ) != LLA_PROXY_ADDR ) {
365
- $this->_options['client_type'] = LLA_DIRECT_ADDR;
366
- }
367
-
368
- }
369
-
370
- /**
371
- * Make sure the variables make sense -- simple integer
372
- *
373
- * @param $var_name
374
- */
375
- public function sanitize_simple_int( $var_name ) {
376
- $this->_options[ $var_name ] = max( 1, intval( $this->get_option( $var_name ) ) );
377
- }
378
-
379
- /**
380
- * Update options in db from global variables
381
- */
382
- public function update_options() {
383
- update_option( 'limit_login_client_type', $this->get_option( 'client_type' ) );
384
- update_option( 'limit_login_allowed_retries', $this->get_option( 'allowed_retries' ) );
385
- update_option( 'limit_login_lockout_duration', $this->get_option( 'lockout_duration' ) );
386
- update_option( 'limit_login_allowed_lockouts', $this->get_option( 'allowed_lockouts' ) );
387
- update_option( 'limit_login_long_duration', $this->get_option( 'long_duration' ) );
388
- update_option( 'limit_login_valid_duration', $this->get_option( 'valid_duration' ) );
389
- update_option( 'limit_login_lockout_notify', $this->get_option( 'lockout_notify' ) );
390
- update_option( 'limit_login_notify_email_after', $this->get_option( 'notify_email_after' ) );
391
- update_option( 'limit_login_cookies', $this->get_option( 'cookies' ) ? '1' : '0' );
392
- update_option( 'limit_login_whitelist', $this->get_option( 'whitelist' ) );
393
- update_option( 'limit_login_whitelist_usernames', $this->get_option( 'whitelist_usernames' ) );
394
- update_option( 'limit_login_blacklist', $this->get_option( 'blacklist' ) );
395
- update_option( 'limit_login_blacklist_usernames', $this->get_option( 'blacklist_usernames' ) );
396
- }
397
-
398
- public function update_option_from_db( $option, $var_name ) {
399
- if ( false !== ( $option_value = get_option( $option ) ) ) {
400
- $this->_options[ $var_name ] = $option_value;
401
- }
402
- }
403
-
404
- /**
405
- * Check if it is ok to login
406
- *
407
- * @return bool
408
- */
409
- public function is_limit_login_ok() {
410
-
411
- $ip = $this->get_address();
412
-
413
- /* Check external whitelist filter */
414
- if ( $this->is_ip_whitelisted( $ip ) ) {
415
- return true;
416
- }
417
-
418
- /* lockout active? */
419
- $lockouts = get_option( 'limit_login_lockouts' );
420
-
421
- return ( ! is_array( $lockouts ) || ! isset( $lockouts[ $ip ] ) || time() >= $lockouts[ $ip ] );
422
- }
423
-
424
- /**
425
- * Action when login attempt failed
426
- *
427
- * Increase nr of retries (if necessary). Reset valid value. Setup
428
- * lockout if nr of retries are above threshold. And more!
429
- *
430
- * A note on external whitelist: retries and statistics are still counted and
431
- * notifications done as usual, but no lockout is done.
432
- *
433
- * @param $username
434
- */
435
- public function limit_login_failed( $username ) {
436
-
437
- $ip = $this->get_address();
438
-
439
- /* if currently locked-out, do not add to retries */
440
- $lockouts = get_option( 'limit_login_lockouts' );
441
-
442
- if ( ! is_array( $lockouts ) ) {
443
- $lockouts = array();
444
- }
445
-
446
- if ( isset( $lockouts[ $ip ] ) && time() < $lockouts[ $ip ] ) {
447
- return;
448
- }
449
-
450
- /* Get the arrays with retries and retries-valid information */
451
- $retries = get_option( 'limit_login_retries' );
452
- $valid = get_option( 'limit_login_retries_valid' );
453
-
454
- if ( ! is_array( $retries ) ) {
455
- $retries = array();
456
- add_option( 'limit_login_retries', $retries, '', 'no' );
457
- }
458
-
459
- if ( ! is_array( $valid ) ) {
460
- $valid = array();
461
- add_option( 'limit_login_retries_valid', $valid, '', 'no' );
462
- }
463
-
464
- /* Check validity and add one to retries */
465
- if ( isset( $retries[ $ip ] ) && isset( $valid[ $ip ] ) && time() < $valid[ $ip ] ) {
466
- $retries[ $ip ] ++;
467
- } else {
468
- $retries[ $ip ] = 1;
469
- }
470
- $valid[ $ip ] = time() + $this->get_option( 'valid_duration' );
471
-
472
- /* lockout? */
473
- if ( $retries[ $ip ] % $this->get_option( 'allowed_retries' ) != 0 ) {
474
- /*
475
- * Not lockout (yet!)
476
- * Do housecleaning (which also saves retry/valid values).
477
- */
478
- $this->cleanup( $retries, null, $valid );
479
-
480
- return;
481
- }
482
-
483
- /* lockout! */
484
-
485
- $whitelisted = $this->is_ip_whitelisted( $ip );
486
-
487
- $retries_long = $this->get_option( 'allowed_retries' ) * $this->get_option( 'allowed_lockouts' );
488
-
489
- /*
490
- * Note that retries and statistics are still counted and notifications
491
- * done as usual for whitelisted ips , but no lockout is done.
492
- */
493
- if ( $whitelisted ) {
494
- if ( $retries[ $ip ] >= $retries_long ) {
495
- unset( $retries[ $ip ] );
496
- unset( $valid[ $ip ] );
497
- }
498
- } else {
499
- global $limit_login_just_lockedout;
500
- $limit_login_just_lockedout = true;
501
-
502
- /* setup lockout, reset retries as needed */
503
- if ( $retries[ $ip ] >= $retries_long ) {
504
- /* long lockout */
505
- $lockouts[ $ip ] = time() + $this->get_option( 'long_duration' );
506
- unset( $retries[ $ip ] );
507
- unset( $valid[ $ip ] );
508
- } else {
509
- /* normal lockout */
510
- $lockouts[ $ip ] = time() + $this->get_option( 'lockout_duration' );
511
- }
512
- }
513
-
514
- /* do housecleaning and save values */
515
- $this->cleanup( $retries, $lockouts, $valid );
516
-
517
- /* do any notification */
518
- $this->notify( $username );
519
-
520
- /* increase statistics */
521
- $total = get_option( 'limit_login_lockouts_total' );
522
- if ( $total === false || ! is_numeric( $total ) ) {
523
- add_option( 'limit_login_lockouts_total', 1, '', 'no' );
524
- } else {
525
- update_option( 'limit_login_lockouts_total', $total + 1 );
526
- }
527
- }
528
-
529
- /**
530
- * Handle notification in event of lockout
531
- *
532
- * @param $user
533
- */
534
- public function notify( $user ) {
535
- $args = explode( ',', $this->get_option( 'lockout_notify' ) );
536
-
537
- if ( empty( $args ) ) {
538
- return;
539
- }
540
-
541
- foreach ( $args as $mode ) {
542
- switch ( trim( $mode ) ) {
543
- case 'email':
544
- $this->notify_email( $user );
545
- break;
546
- case 'log':
547
- $this->notify_log( $user );
548
- break;
549
- }
550
- }
551
- }
552
-
553
- /**
554
- * Email notification of lockout to admin (if configured)
555
- *
556
- * @param $user
557
- */
558
- public function notify_email( $user ) {
559
- $ip = $this->get_address();
560
- $whitelisted = $this->is_ip_whitelisted( $ip );
561
-
562
- $retries = get_option( 'limit_login_retries' );
563
- if ( ! is_array( $retries ) ) {
564
- $retries = array();
565
- }
566
-
567
- /* check if we are at the right nr to do notification */
568
- if ( isset( $retries[ $ip ] ) && ( ( $retries[ $ip ] / $this->get_option( 'allowed_retries' ) ) % $this->get_option( 'notify_email_after' ) ) != 0 ) {
569
- return;
570
- }
571
-
572
- /* Format message. First current lockout duration */
573
- if ( ! isset( $retries[ $ip ] ) ) {
574
- /* longer lockout */
575
- $count = $this->get_option( 'allowed_retries' )
576
- * $this->get_option( 'allowed_lockouts' );
577
- $lockouts = $this->get_option( 'allowed_lockouts' );
578
- $time = round( $this->get_option( 'long_duration' ) / 3600 );
579
- $when = sprintf( _n( '%d hour', '%d hours', $time, 'limit-login-attempts-reloaded' ), $time );
580
- } else {
581
- /* normal lockout */
582
- $count = $retries[ $ip ];
583
- $lockouts = floor( $count / $this->get_option( 'allowed_retries' ) );
584
- $time = round( $this->get_option( 'lockout_duration' ) / 60 );
585
- $when = sprintf( _n( '%d minute', '%d minutes', $time, 'limit-login-attempts-reloaded' ), $time );
586
- }
587
-
588
- $blogname = $this->is_multisite() ? get_site_option( 'site_name' ) : get_option( 'blogname' );
589
-
590
- if ( $whitelisted ) {
591
- $subject = sprintf( __( "[%s] Failed login attempts from whitelisted IP"
592
- , 'limit-login-attempts-reloaded' )
593
- , $blogname );
594
- } else {
595
- $subject = sprintf( __( "[%s] Too many failed login attempts"
596
- , 'limit-login-attempts-reloaded' )
597
- , $blogname );
598
- }
599
-
600
- $message = sprintf( __( "%d failed login attempts (%d lockout(s)) from IP: %s"
601
- , 'limit-login-attempts-reloaded' ) . "\r\n\r\n"
602
- , $count, $lockouts, $ip );
603
- if ( $user != '' ) {
604
- $message .= sprintf( __( "Last user attempted: %s", 'limit-login-attempts-reloaded' )
605
- . "\r\n\r\n", $user );
606
- }
607
- if ( $whitelisted ) {
608
- $message .= __( "IP was NOT blocked because of external whitelist.", 'limit-login-attempts-reloaded' );
609
- } else {
610
- $message .= sprintf( __( "IP was blocked for %s", 'limit-login-attempts-reloaded' ), $when );
611
- }
612
-
613
- $admin_email = $this->is_multisite() ? get_site_option( 'admin_email' ) : get_option( 'admin_email' );
614
-
615
- @wp_mail( $admin_email, $subject, $message );
616
- }
617
-
618
- /**
619
- * Is this WP Multisite?
620
- *
621
- * @return bool
622
- */
623
- public function is_multisite() {
624
- return function_exists( 'get_site_option' ) && function_exists( 'is_multisite' ) && is_multisite();
625
- }
626
-
627
- /**
628
- * Logging of lockout (if configured)
629
- *
630
- * @param $user_login
631
- *
632
- * @internal param $user
633
- */
634
- public function notify_log( $user_login ) {
635
-
636
- if ( ! $user_login ) {
637
- return;
638
- }
639
-
640
- $log = $option = get_option( 'limit_login_logged' );
641
- if ( ! is_array( $log ) ) {
642
- $log = array();
643
- }
644
- $ip = $this->get_address();
645
-
646
- /* can be written much simpler, if you do not mind php warnings */
647
- if ( isset( $log[ $ip ] ) ) {
648
- if ( isset( $log[ $ip ][ $user_login ] ) ) {
649
-
650
- if ( is_array( $log[ $ip ][ $user_login ] ) ) { // For new plugin version
651
- $log[ $ip ][ $user_login ]['counter'] += 1;
652
- } else { // For old plugin version
653
- $temp_counter = $log[ $ip ][ $user_login ];
654
- $log[ $ip ][ $user_login ] = array(
655
- 'counter' => $temp_counter + 1
656
- );
657
- }
658
- } else {
659
- $log[ $ip ][ $user_login ] = array(
660
- 'counter' => 1
661
- );
662
- }
663
- } else {
664
- $log[ $ip ] = array(
665
- $user_login => array(
666
- 'counter' => 1
667
- )
668
- );
669
- }
670
-
671
- $log[ $ip ][ $user_login ]['date'] = time();
672
-
673
- if ( isset( $_POST['woocommerce-login-nonce'] ) ) {
674
- $gateway = 'WooCommerce';
675
- } elseif ( isset( $GLOBALS['wp_xmlrpc_server'] ) && is_object( $GLOBALS['wp_xmlrpc_server'] ) ) {
676
- $gateway = 'XMLRPC';
677
- } else {
678
- $gateway = 'WP Login';
679
- }
680
-
681
- $log[ $ip ][ $user_login ]['gateway'] = $gateway;
682
-
683
- if ( $option === false ) {
684
- add_option( 'limit_login_logged', $log, '', 'no' ); /* no autoload */
685
- } else {
686
- update_option( 'limit_login_logged', $log );
687
- }
688
- }
689
-
690
- /**
691
- * Check if IP is whitelisted.
692
- *
693
- * This function allow external ip whitelisting using a filter. Note that it can
694
- * be called multiple times during the login process.
695
- *
696
- * Note that retries and statistics are still counted and notifications
697
- * done as usual for whitelisted ips , but no lockout is done.
698
- *
699
- * Example:
700
- * function my_ip_whitelist($allow, $ip) {
701
- * return ($ip == 'my-ip') ? true : $allow;
702
- * }
703
- * add_filter('limit_login_whitelist_ip', 'my_ip_whitelist', 10, 2);
704
- *
705
- * @param null $ip
706
- *
707
- * @return bool
708
- */
709
- public function is_ip_whitelisted( $ip = null ) {
710
-
711
- if ( is_null( $ip ) ) {
712
- $ip = $this->get_address();
713
- }
714
-
715
- $whitelisted = apply_filters( 'limit_login_whitelist_ip', false, $ip );
716
-
717
- return ( $whitelisted === true );
718
- }
719
-
720
- public function is_username_whitelisted( $username ) {
721
-
722
- if ( empty( $username ) ) {
723
- return false;
724
- }
725
-
726
- $whitelisted = apply_filters( 'limit_login_whitelist_usernames', false, $username );
727
-
728
- return ( $whitelisted === true );
729
- }
730
-
731
- public function is_ip_blacklisted( $ip = null ) {
732
-
733
- if ( is_null( $ip ) ) {
734
- $ip = $this->get_address();
735
- }
736
-
737
- $whitelisted = apply_filters( 'limit_login_blacklist_ip', false, $ip );
738
-
739
- return ( $whitelisted === true );
740
- }
741
-
742
- public function is_username_blacklisted( $username ) {
743
-
744
- if ( empty( $username ) ) {
745
- return false;
746
- }
747
-
748
- $whitelisted = apply_filters( 'limit_login_blacklist_usernames', false, $username );
749
-
750
- return ( $whitelisted === true );
751
- }
752
-
753
- /**
754
- * Filter: allow login attempt? (called from wp_authenticate())
755
- *
756
- * @param $user WP_User
757
- * @param $password
758
- *
759
- * @return \WP_Error
760
- */
761
- public function wp_authenticate_user( $user, $password ) {
762
-
763
- if ( is_wp_error( $user ) ||
764
- $this->check_whitelist_ips( false, $this->get_address() ) ||
765
- $this->check_whitelist_usernames( false, $user->user_login ) ||
766
- $this->is_limit_login_ok()
767
- ) {
768
-
769
- return $user;
770
- }
771
-
772
- $error = new WP_Error();
773
-
774
- global $limit_login_my_error_shown;
775
- $limit_login_my_error_shown = true;
776
-
777
- if ( $this->is_username_blacklisted( $user->user_login ) || $this->is_ip_blacklisted( $this->get_address() ) ) {
778
- $error->add( 'username_blacklisted', "<strong>ERROR:</strong> Too many failed login attempts." );
779
- } else {
780
- // This error should be the same as in "shake it" filter below
781
- $error->add( 'too_many_retries', $this->error_msg() );
782
- }
783
-
784
- return $error;
785
- }
786
-
787
- /**
788
- * Filter: add this failure to login page "Shake it!"
789
- *
790
- * @param $error_codes
791
- *
792
- * @return array
793
- */
794
- public function failure_shake( $error_codes ) {
795
- $error_codes[] = 'too_many_retries';
796
  $error_codes[] = 'username_blacklisted';
797
 
798
- return $error_codes;
799
- }
800
-
801
- /**
802
- * Keep track of if user or password are empty, to filter errors correctly
803
- *
804
- * @param $user
805
- * @param $password
806
- */
807
- public function track_credentials( $user, $password ) {
808
- global $limit_login_nonempty_credentials;
809
-
810
- $limit_login_nonempty_credentials = ( ! empty( $user ) && ! empty( $password ) );
811
- }
812
-
813
- /**
814
- * Should we show errors and messages on this page?
815
- *
816
- * @return bool
817
- */
818
- public function login_show_msg() {
819
- if ( isset( $_GET['key'] ) ) {
820
- /* reset password */
821
- return false;
822
- }
823
-
824
- $action = isset( $_REQUEST['action'] ) ? $_REQUEST['action'] : '';
825
-
826
- return ( $action != 'lostpassword' && $action != 'retrievepassword'
827
- && $action != 'resetpass' && $action != 'rp'
828
- && $action != 'register' );
829
- }
830
-
831
- /**
832
- * Construct informative error message
833
- *
834
- * @return string
835
- */
836
- public function error_msg() {
837
- $ip = $this->get_address();
838
- $lockouts = get_option( 'limit_login_lockouts' );
839
-
840
- $msg = __( '<strong>ERROR</strong>: Too many failed login attempts.', 'limit-login-attempts-reloaded' ) . ' ';
841
-
842
- if ( ! is_array( $lockouts ) || ! isset( $lockouts[ $ip ] ) || time() >= $lockouts[ $ip ] ) {
843
- /* Huh? No timeout active? */
844
- $msg .= __( 'Please try again later.', 'limit-login-attempts-reloaded' );
845
-
846
- return $msg;
847
- }
848
-
849
- $when = ceil( ( $lockouts[ $ip ] - time() ) / 60 );
850
- if ( $when > 60 ) {
851
- $when = ceil( $when / 60 );
852
- $msg .= sprintf( _n( 'Please try again in %d hour.', 'Please try again in %d hours.', $when, 'limit-login-attempts-reloaded' ), $when );
853
- } else {
854
- $msg .= sprintf( _n( 'Please try again in %d minute.', 'Please try again in %d minutes.', $when, 'limit-login-attempts-reloaded' ), $when );
855
- }
856
-
857
- return $msg;
858
- }
859
-
860
- /**
861
- * Add a message to login page when necessary
862
- */
863
- public function add_error_message() {
864
- global $error, $limit_login_my_error_shown;
865
-
866
- if ( ! $this->login_show_msg() || $limit_login_my_error_shown ) {
867
- return;
868
- }
869
-
870
- $msg = $this->get_message();
871
-
872
- if ( $msg != '' ) {
873
- $limit_login_my_error_shown = true;
874
- $error .= $msg;
875
- }
876
-
877
- return;
878
- }
879
-
880
- /**
881
- * Fix up the error message before showing it
882
- *
883
- * @param $content
884
- *
885
- * @return string
886
- */
887
- public function fixup_error_messages( $content ) {
888
- global $limit_login_just_lockedout, $limit_login_nonempty_credentials, $limit_login_my_error_shown;
889
-
890
- if ( ! $this->login_show_msg() ) {
891
- return $content;
892
- }
893
-
894
- /*
895
- * During lockout we do not want to show any other error messages (like
896
- * unknown user or empty password).
897
- */
898
- if ( ! $this->is_limit_login_ok() && ! $limit_login_just_lockedout ) {
899
- return $this->error_msg();
900
- }
901
-
902
- /*
903
- * We want to filter the messages 'Invalid username' and
904
- * 'Invalid password' as that is an information leak regarding user
905
- * account names (prior to WP 2.9?).
906
- *
907
- * Also, if more than one error message, put an extra <br /> tag between
908
- * them.
909
- */
910
- $msgs = explode( "<br />\n", $content );
911
-
912
- if ( strlen( end( $msgs ) ) == 0 ) {
913
- /* remove last entry empty string */
914
- array_pop( $msgs );
915
- }
916
-
917
- $count = count( $msgs );
918
- $my_warn_count = $limit_login_my_error_shown ? 1 : 0;
919
-
920
- if ( $limit_login_nonempty_credentials && $count > $my_warn_count ) {
921
- /* Replace error message, including ours if necessary */
922
- $content = __( '<strong>ERROR</strong>: Incorrect username or password.', 'limit-login-attempts-reloaded' ) . "<br />\n";
923
-
924
- if ( $limit_login_my_error_shown || $this->get_message() ) {
925
- $content .= "<br />\n" . $this->get_message() . "<br />\n";
926
- }
927
-
928
- return $content;
929
- } elseif ( $count <= 1 ) {
930
- return $content;
931
- }
932
-
933
- $new = '';
934
- while ( $count -- > 0 ) {
935
- $new .= array_shift( $msgs ) . "<br />\n";
936
- if ( $count > 0 ) {
937
- $new .= "<br />\n";
938
- }
939
- }
940
-
941
- return $new;
942
- }
943
-
944
- public function fixup_error_messages_wc( \WP_Error $error ) {
945
- $error->add( 1, __( 'WC Error' ) );
946
- }
947
-
948
- /**
949
- * Return current (error) message to show, if any
950
- *
951
- * @return string
952
- */
953
- public function get_message() {
954
- /* Check external whitelist */
955
- if ( $this->is_ip_whitelisted() ) {
956
- return '';
957
- }
958
-
959
- /* Is lockout in effect? */
960
- if ( ! $this->is_limit_login_ok() ) {
961
- return $this->error_msg();
962
- }
963
-
964
- return $this->retries_remaining_msg();
965
- }
966
-
967
- /**
968
- * Construct retries remaining message
969
- *
970
- * @return string
971
- */
972
- public function retries_remaining_msg() {
973
- $ip = $this->get_address();
974
- $retries = get_option( 'limit_login_retries' );
975
- $valid = get_option( 'limit_login_retries_valid' );
976
-
977
- /* Should we show retries remaining? */
978
-
979
- if ( ! is_array( $retries ) || ! is_array( $valid ) ) {
980
- /* no retries at all */
981
- return '';
982
- }
983
- if ( ! isset( $retries[ $ip ] ) || ! isset( $valid[ $ip ] ) || time() > $valid[ $ip ] ) {
984
- /* no: no valid retries */
985
- return '';
986
- }
987
- if ( ( $retries[ $ip ] % $this->get_option( 'allowed_retries' ) ) == 0 ) {
988
- /* no: already been locked out for these retries */
989
- return '';
990
- }
991
-
992
- $remaining = max( ( $this->get_option( 'allowed_retries' ) - ( $retries[ $ip ] % $this->get_option( 'allowed_retries' ) ) ), 0 );
993
-
994
- return sprintf( _n( "<strong>%d</strong> attempt remaining.", "<strong>%d</strong> attempts remaining.", $remaining, 'limit-login-attempts-reloaded' ), $remaining );
995
- }
996
-
997
- /**
998
- * Get correct remote address
999
- *
1000
- * @param string $type_name
1001
- *
1002
- * @return string
1003
- */
1004
- public function get_address( $type_name = '' ) {
1005
-
1006
- if ( isset( $_SERVER['HTTP_X_FORWARDED_FOR'] ) && ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
1007
- return $_SERVER['HTTP_X_FORWARDED_FOR'];
1008
- } elseif ( isset( $_SERVER['REMOTE_ADDR'] ) ) {
1009
- return $_SERVER['REMOTE_ADDR'];
1010
- } else {
1011
- return '';
1012
- }
1013
- }
1014
-
1015
- /**
1016
- * Clean up old lockouts and retries, and save supplied arrays
1017
- *
1018
- * @param null $retries
1019
- * @param null $lockouts
1020
- * @param null $valid
1021
- */
1022
- public function cleanup( $retries = null, $lockouts = null, $valid = null ) {
1023
- $now = time();
1024
- $lockouts = ! is_null( $lockouts ) ? $lockouts : get_option( 'limit_login_lockouts' );
1025
-
1026
- /* remove old lockouts */
1027
- if ( is_array( $lockouts ) ) {
1028
- foreach ( $lockouts as $ip => $lockout ) {
1029
- if ( $lockout < $now ) {
1030
- unset( $lockouts[ $ip ] );
1031
- }
1032
- }
1033
- update_option( 'limit_login_lockouts', $lockouts );
1034
- }
1035
-
1036
- /* remove retries that are no longer valid */
1037
- $valid = ! is_null( $valid ) ? $valid : get_option( 'limit_login_retries_valid' );
1038
- $retries = ! is_null( $retries ) ? $retries : get_option( 'limit_login_retries' );
1039
- if ( ! is_array( $valid ) || ! is_array( $retries ) ) {
1040
- return;
1041
- }
1042
-
1043
- foreach ( $valid as $ip => $lockout ) {
1044
- if ( $lockout < $now ) {
1045
- unset( $valid[ $ip ] );
1046
- unset( $retries[ $ip ] );
1047
- }
1048
- }
1049
-
1050
- /* go through retries directly, if for some reason they've gone out of sync */
1051
- foreach ( $retries as $ip => $retry ) {
1052
- if ( ! isset( $valid[ $ip ] ) ) {
1053
- unset( $retries[ $ip ] );
1054
- }
1055
- }
1056
-
1057
- update_option( 'limit_login_retries', $retries );
1058
- update_option( 'limit_login_retries_valid', $valid );
1059
- }
1060
-
1061
- /**
1062
- * Render admin options page
1063
- */
1064
- public function options_page() {
1065
- $this->cleanup();
1066
- include_once( LLA_PLUGIN_DIR . '/views/options-page.php' );
1067
- }
1068
-
1069
- /**
1070
- * Show error message
1071
- *
1072
- * @param $msg
1073
- */
1074
- public function show_error( $msg ) {
1075
- LLA_Helpers::show_error( $msg );
1076
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1077
 
1078
  }
1
  <?php
2
 
3
  /**
4
+ * Class Limit_Login_Attempts
5
+ */
6
+ class Limit_Login_Attempts
7
+ {
8
+ public $default_options = array(
9
+ /* Are we behind a proxy? */
10
+ 'client_type' => LLA_DIRECT_ADDR,
11
+
12
+ /* Lock out after this many tries */
13
+ 'allowed_retries' => 4,
14
+
15
+ /* Lock out for this many seconds */
16
+ 'lockout_duration' => 1200, // 20 minutes
17
+
18
+ /* Long lock out after this many lockouts */
19
+ 'allowed_lockouts' => 4,
20
+
21
+ /* Long lock out for this many seconds */
22
+ 'long_duration' => 86400, // 24 hours,
23
+
24
+ /* Reset failed attempts after this many seconds */
25
+ 'valid_duration' => 43200, // 12 hours
26
+
27
+ /* Also limit malformed/forged cookies? */
28
+ 'cookies' => true,
29
+
30
+ /* Notify on lockout. Values: '', 'log', 'email', 'log,email' */
31
+ 'lockout_notify' => 'log',
32
+
33
+ /* If notify by email, do so after this number of lockouts */
34
+ 'notify_email_after' => 4,
35
+
36
+ 'whitelist' => array(),
37
+ 'whitelist_usernames' => array(),
38
+ 'blacklist' => array(),
39
+ 'blacklist_usernames' => array(),
40
+ );
41
+ /**
42
+ * Admin options page slug
43
+ * @var string
44
+ */
45
+ private $_options_page_slug = 'limit-login-attempts';
46
+
47
+ /**
48
+ * Errors messages
49
+ *
50
+ * @var array
51
+ */
52
+ public $_errors = array();
53
+
54
+ public function __construct() {
55
+ $this->hooks_init();
56
+ }
57
+
58
+ /**
59
+ * Register wp hooks and filters
60
+ */
61
+ public function hooks_init() {
62
+ add_action( 'plugins_loaded', array( $this, 'setup' ), 9999 );
63
+ add_action( 'admin_enqueue_scripts', array( $this, 'enqueue' ) );
64
+ add_filter( 'limit_login_whitelist_ip', array( $this, 'check_whitelist_ips' ), 10, 2 );
65
+ add_filter( 'limit_login_whitelist_usernames', array( $this, 'check_whitelist_usernames' ), 10, 2 );
66
+ add_filter( 'limit_login_blacklist_ip', array( $this, 'check_blacklist_ips' ), 10, 2 );
67
+ add_filter( 'limit_login_blacklist_usernames', array( $this, 'check_blacklist_usernames' ), 10, 2 );
68
+ }
69
+
70
+ /**
71
+ * Hook 'plugins_loaded'
72
+ */
73
+ public function setup() {
74
+
75
+ // Load languages files
76
+ load_plugin_textdomain( 'limit-login-attempts-reloaded', false, plugin_basename( dirname( __FILE__ ) ) . '/../languages' );
77
+
78
+ // Check if installed old plugin
79
+ $this->check_original_installed();
80
+
81
+ if ( is_multisite() )
82
+ require_once ABSPATH.'wp-admin/includes/plugin.php';
83
+
84
+ $this->network_mode = is_multisite() && is_plugin_active_for_network('limit-login-attempts-reloaded/limit-login-attempts-reloaded.php');
85
+
86
+
87
+ if ( $this->network_mode )
88
+ {
89
+ $this->allow_local_options = get_site_option( 'limit_login_allow_local_options', false );
90
+ $this->use_local_options = $this->allow_local_options && get_option( 'limit_login_use_local_options', false );
91
+ }
92
+ else
93
+ {
94
+ $this->allow_local_options = true;
95
+ $this->use_local_options = true;
96
+ }
97
+
98
+
99
+ // Setup default plugin options
100
+ //$this->sanitize_options();
101
+
102
+ add_action( 'wp_login_failed', array( $this, 'limit_login_failed' ) );
103
+ add_filter( 'wp_authenticate_user', array( $this, 'wp_authenticate_user' ), 99999, 2 );
104
+
105
+ add_filter( 'shake_error_codes', array( $this, 'failure_shake' ) );
106
+ add_action( 'login_head', array( $this, 'add_error_message' ) );
107
+ add_action( 'login_errors', array( $this, 'fixup_error_messages' ) );
108
+
109
+ if ( $this->network_mode )
110
+ add_action( 'network_admin_menu', array( $this, 'network_admin_menu' ) );
111
+
112
+ if ( $this->allow_local_options )
113
+ add_action( 'admin_menu', array( $this, 'admin_menu' ) );
114
+
115
+ // Add notices for XMLRPC request
116
+ add_filter( 'xmlrpc_login_error', array( $this, 'xmlrpc_error_messages' ) );
117
+
118
+ // Add notices to woocommerce login page
119
+ add_action( 'wp_head', array( $this, 'add_wc_notices' ) );
120
+
121
+ /*
122
+ * This action should really be changed to the 'authenticate' filter as
123
+ * it will probably be deprecated. That is however only available in
124
+ * later versions of WP.
125
+ */
126
+ add_action( 'wp_authenticate', array( $this, 'track_credentials' ), 10, 2 );
127
+ add_action( 'authenticate', array( $this, 'authenticate_filter' ), 5, 3 );
128
+ }
129
+
130
+ public function check_whitelist_ips( $allow, $ip ) {
131
+ return in_array( $ip, (array) $this->get_option( 'whitelist' ) );
132
+ }
133
+
134
+ public function check_whitelist_usernames( $allow, $username ) {
135
+ return in_array( $username, (array) $this->get_option( 'whitelist_usernames' ) );
136
+ }
137
+
138
+ public function check_blacklist_ips( $allow, $ip ) {
139
+ return in_array( $ip, (array) $this->get_option( 'blacklist' ) );
140
+ }
141
+
142
+ public function check_blacklist_usernames( $allow, $username ) {
143
+ return in_array( $username, (array) $this->get_option( 'blacklist_usernames' ) );
144
+ }
145
+
146
+ /**
147
+ * @param $error IXR_Error
148
+ *
149
+ * @return IXR_Error
150
+ */
151
+ public function xmlrpc_error_messages( $error ) {
152
+
153
+ if ( ! class_exists( 'IXR_Error' ) ) {
154
+ return $error;
155
+ }
156
+
157
+ if ( ! $this->is_limit_login_ok() ) {
158
+ return new IXR_Error( 403, $this->error_msg() );
159
+ }
160
+
161
+ $ip = $this->get_address();
162
+ $retries = $this->get_option( 'retries' );
163
+ $valid = $this->get_option( 'retries_valid' );
164
+
165
+ /* Should we show retries remaining? */
166
+
167
+ if ( ! is_array( $retries ) || ! is_array( $valid ) ) {
168
+ /* no retries at all */
169
+ return $error;
170
+ }
171
+ if ( ! isset( $retries[ $ip ] ) || ! isset( $valid[ $ip ] ) || time() > $valid[ $ip ] ) {
172
+ /* no: no valid retries */
173
+ return $error;
174
+ }
175
+ if ( ( $retries[ $ip ] % $this->get_option( 'allowed_retries' ) ) == 0 ) {
176
+ /* no: already been locked out for these retries */
177
+ return $error;
178
+ }
179
+
180
+ $remaining = max( ( $this->get_option( 'allowed_retries' ) - ( $retries[ $ip ] % $this->get_option( 'allowed_retries' ) ) ), 0 );
181
+
182
+ return new IXR_Error( 403, sprintf( _n( "<strong>%d</strong> attempt remaining.", "<strong>%d</strong> attempts remaining.", $remaining, 'limit-login-attempts-reloaded' ), $remaining ) );
183
+ }
184
+
185
+ /**
186
+ * Errors on WooCommerce account page
187
+ */
188
+ public function add_wc_notices() {
189
+
190
+ global $limit_login_just_lockedout, $limit_login_nonempty_credentials, $limit_login_my_error_shown;
191
+
192
+ if ( ! function_exists( 'is_account_page' ) || ! function_exists( 'wc_add_notice' ) ) {
193
+ return;
194
+ }
195
+
196
+ /*
197
+ * During lockout we do not want to show any other error messages (like
198
+ * unknown user or empty password).
199
+ */
200
+ if ( empty( $_POST ) && ! $this->is_limit_login_ok() && ! $limit_login_just_lockedout ) {
201
+ if ( is_account_page() ) {
202
+ wc_add_notice( $this->error_msg(), 'error' );
203
+ }
204
+ }
205
+
206
+ }
207
+
208
+ /**
209
+ * @param $user
210
+ * @param $username
211
+ * @param $password
212
+ *
213
+ * @return WP_Error | WP_User
214
+ */
215
+ public function authenticate_filter( $user, $username, $password ) {
216
+
217
+ if ( ! empty( $username ) && ! empty( $password ) ) {
218
+
219
+ $ip = $this->get_address();
220
+
221
+ // Check if username is blacklisted
222
+ if ( ! $this->is_username_whitelisted( $username ) && ! $this->is_ip_whitelisted( $ip ) &&
223
+ ( $this->is_username_blacklisted( $username ) || $this->is_ip_blacklisted( $ip ) )
224
+ ) {
225
+
226
+ remove_filter( 'login_errors', array( $this, 'fixup_error_messages' ) );
227
+ remove_filter( 'login_head', array( $this, 'add_error_message' ) );
228
+ remove_filter( 'wp_login_failed', array( $this, 'limit_login_failed' ) );
229
+ remove_filter( 'wp_authenticate_user', array( $this, 'wp_authenticate_user' ), 99999 );
230
+ remove_filter( 'login_head', array( $this, 'add_error_message' ) );
231
+ remove_filter( 'login_errors', array( $this, 'fixup_error_messages' ) );
232
+
233
+ remove_filter( 'authenticate', 'wp_authenticate_username_password', 20 );
234
+ remove_filter( 'authenticate', 'wp_authenticate_email_password', 20 );
235
+
236
+ $user = new WP_Error();
237
+ $user->add( 'username_blacklisted', "<strong>ERROR:</strong> Too many failed login attempts." );
238
+
239
+ } elseif ( $this->is_username_whitelisted( $username ) || $this->is_ip_whitelisted( $ip ) ) {
240
+
241
+ remove_filter( 'wp_login_failed', array( $this, 'limit_login_failed' ) );
242
+ remove_filter( 'wp_authenticate_user', array( $this, 'wp_authenticate_user' ), 99999 );
243
+ remove_filter( 'login_head', array( $this, 'add_error_message' ) );
244
+ remove_filter( 'login_errors', array( $this, 'fixup_error_messages' ) );
245
+
246
+ }
247
+
248
+ }
249
+
250
+ return $user;
251
+ }
252
+
253
+ /**
254
+ * Check if the original plugin is installed
255
+ */
256
+ private function check_original_installed()
257
+ {
258
+ require_once( ABSPATH . '/wp-admin/includes/plugin.php' );
259
+ if ( is_plugin_active('limit-login-attempts/limit-login-attempts.php') )
260
+ {
261
+ deactivate_plugins( 'limit-login-attempts/limit-login-attempts.php', true );
262
+ //add_action('plugins_loaded', 'limit_login_setup', 99999);
263
+ remove_action( 'plugins_loaded', 'limit_login_setup', 99999 );
264
+ }
265
+ }
266
+
267
+ /**
268
+ * Enqueue js and css
269
+ */
270
+ public function enqueue() {
271
+ wp_enqueue_style( 'lla-main', LLA_PLUGIN_URL . '/assets/css/limit-login-attempts.css' );
272
+ }
273
+
274
+ /**
275
+ * Add admin options page
276
+ */
277
+ public function network_admin_menu()
278
+ {
279
+ add_submenu_page( 'settings.php', 'Limit Login Attempts', 'Limit Login Attempts', 'manage_options', $this->_options_page_slug, array( $this, 'options_page' ) );
280
+ }
281
+
282
+ public function admin_menu()
283
+ {
284
+ add_options_page( 'Limit Login Attempts', 'Limit Login Attempts', 'manage_options', $this->_options_page_slug, array( $this, 'options_page' ) );
285
+ }
286
+
287
+ /**
288
+ * Get the correct options page URI
289
+ *
290
+ * @return mixed
291
+ */
292
+ public function get_options_page_uri()
293
+ {
294
+ if ( is_network_admin() )
295
+ return network_admin_url( 'settings.php?page=limit-login-attempts' );
296
+
297
+ return menu_page_url( $this->_options_page_slug, false );
298
+ }
299
+
300
+ /**
301
+ * Get option by name
302
+ *
303
+ * @param $option_name
304
+ *
305
+ * @return null
306
+ */
307
+ public function get_option( $option_name, $local = null )
308
+ {
309
+ if ( is_null( $local ) )
310
+ $local = $this->use_local_options;
311
+
312
+ $option = 'limit_login_'.$option_name;
313
+
314
+ $func = $local ? 'get_option' : 'get_site_option';
315
+ $value = $func( $option, null );
316
+
317
+ if ( is_null( $value ) && isset( $this->default_options[ $option_name ] ) )
318
+ $value = $this->default_options[ $option_name ];
319
+
320
+ return $value;
321
+ }
322
+
323
+ public function update_option( $option_name, $value, $local = null )
324
+ {
325
+ if ( is_null( $local ) )
326
+ $local = $this->use_local_options;
327
+
328
+ $option = 'limit_login_'.$option_name;
329
+
330
+ $func = $local ? 'update_option' : 'update_site_option';
331
+
332
+ return $func( $option, $value );
333
+ }
334
+
335
+ public function add_option( $option_name, $value, $local=null )
336
+ {
337
+ if ( is_null( $local ) )
338
+ $local = $this->use_local_options;
339
+
340
+ $option = 'limit_login_'.$option_name;
341
+
342
+ $func = $local ? 'add_option' : 'add_site_option';
343
+
344
+ return $func( $option, $value, '', 'no' );
345
+ }
346
+
347
+ /**
348
+ * Setup main options
349
+ */
350
+ public function sanitize_options()
351
+ {
352
+ $simple_int_options = array( 'allowed_retries', 'lockout_duration', 'valid_duration', 'allowed_lockouts', 'long_duration', 'notify_email_after');
353
+ foreach ( $simple_int_options as $option )
354
+ {
355
+ $val = $this->get_option( $option );
356
+ if ( (int)$val != $val || (int)$val <= 0 )
357
+ $this->update_option( $option, 1 );
358
+ }
359
+ if ( $this->get_option('notify_email_after') > $this->get_option( 'allowed_lockouts' ) )
360
+ $this->update_option( 'notify_email_after', $this->get_option( 'allowed_lockouts' ) );
361
+
362
+ $args = explode( ',', $this->get_option( 'lockout_notify' ) );
363
+ $args_allowed = explode( ',', LLA_LOCKOUT_NOTIFY_ALLOWED );
364
+ $new_args = array_intersect( $args, $args_allowed );
365
+
366
+ $this->update_option( 'lockout_notify', implode( ',', $new_args ) );
367
+
368
+ $ctype = $this->get_option( 'client_type' );
369
+ if ( $ctype != LLA_DIRECT_ADDR && $ctype != LLA_PROXY_ADDR )
370
+ $this->update_option( 'client_type', LLA_DIRECT_ADDR );
371
+ }
372
+
373
+ /**
374
+ * Check if it is ok to login
375
+ *
376
+ * @return bool
377
+ */
378
+ public function is_limit_login_ok() {
379
+
380
+ $ip = $this->get_address();
381
+
382
+ /* Check external whitelist filter */
383
+ if ( $this->is_ip_whitelisted( $ip ) ) {
384
+ return true;
385
+ }
386
+
387
+ /* lockout active? */
388
+ $lockouts = $this->get_option( 'lockouts' );
389
+
390
+ return ( ! is_array( $lockouts ) || ! isset( $lockouts[ $ip ] ) || time() >= $lockouts[ $ip ] );
391
+ }
392
+
393
+ /**
394
+ * Action when login attempt failed
395
+ *
396
+ * Increase nr of retries (if necessary). Reset valid value. Setup
397
+ * lockout if nr of retries are above threshold. And more!
398
+ *
399
+ * A note on external whitelist: retries and statistics are still counted and
400
+ * notifications done as usual, but no lockout is done.
401
+ *
402
+ * @param $username
403
+ */
404
+ public function limit_login_failed( $username ) {
405
+
406
+ $ip = $this->get_address();
407
+
408
+ /* if currently locked-out, do not add to retries */
409
+ $lockouts = $this->get_option( 'lockouts' );
410
+
411
+ if ( ! is_array( $lockouts ) ) {
412
+ $lockouts = array();
413
+ }
414
+
415
+ if ( isset( $lockouts[ $ip ] ) && time() < $lockouts[ $ip ] ) {
416
+ return;
417
+ }
418
+
419
+ /* Get the arrays with retries and retries-valid information */
420
+ $retries = $this->get_option( 'retries' );
421
+ $valid = $this->get_option( 'retries_valid' );
422
+
423
+ if ( ! is_array( $retries ) ) {
424
+ $retries = array();
425
+ $this->add_option( 'retries', $retries );
426
+ }
427
+
428
+ if ( ! is_array( $valid ) ) {
429
+ $valid = array();
430
+ $this->add_option( 'retries_valid', $valid );
431
+ }
432
+
433
+ /* Check validity and add one to retries */
434
+ if ( isset( $retries[ $ip ] ) && isset( $valid[ $ip ] ) && time() < $valid[ $ip ] ) {
435
+ $retries[ $ip ] ++;
436
+ } else {
437
+ $retries[ $ip ] = 1;
438
+ }
439
+ $valid[ $ip ] = time() + $this->get_option( 'valid_duration' );
440
+
441
+ /* lockout? */
442
+ if ( $retries[ $ip ] % $this->get_option( 'allowed_retries' ) != 0 ) {
443
+ /*
444
+ * Not lockout (yet!)
445
+ * Do housecleaning (which also saves retry/valid values).
446
+ */
447
+ $this->cleanup( $retries, null, $valid );
448
+
449
+ return;
450
+ }
451
+
452
+ /* lockout! */
453
+
454
+ $whitelisted = $this->is_ip_whitelisted( $ip );
455
+
456
+ $retries_long = $this->get_option( 'allowed_retries' ) * $this->get_option( 'allowed_lockouts' );
457
+
458
+ /*
459
+ * Note that retries and statistics are still counted and notifications
460
+ * done as usual for whitelisted ips , but no lockout is done.
461
+ */
462
+ if ( $whitelisted ) {
463
+ if ( $retries[ $ip ] >= $retries_long ) {
464
+ unset( $retries[ $ip ] );
465
+ unset( $valid[ $ip ] );
466
+ }
467
+ } else {
468
+ global $limit_login_just_lockedout;
469
+ $limit_login_just_lockedout = true;
470
+
471
+ /* setup lockout, reset retries as needed */
472
+ if ( $retries[ $ip ] >= $retries_long ) {
473
+ /* long lockout */
474
+ $lockouts[ $ip ] = time() + $this->get_option( 'long_duration' );
475
+ unset( $retries[ $ip ] );
476
+ unset( $valid[ $ip ] );
477
+ } else {
478
+ /* normal lockout */
479
+ $lockouts[ $ip ] = time() + $this->get_option( 'lockout_duration' );
480
+ }
481
+ }
482
+
483
+ /* do housecleaning and save values */
484
+ $this->cleanup( $retries, $lockouts, $valid );
485
+
486
+ /* do any notification */
487
+ $this->notify( $username );
488
+
489
+ /* increase statistics */
490
+ $total = $this->get_option( 'lockouts_total' );
491
+ if ( $total === false || ! is_numeric( $total ) ) {
492
+ $this->add_option( 'lockouts_total', 1 );
493
+ } else {
494
+ $this->update_option( 'lockouts_total', $total + 1 );
495
+ }
496
+ }
497
+
498
+ /**
499
+ * Handle notification in event of lockout
500
+ *
501
+ * @param $user
502
+ */
503
+ public function notify( $user ) {
504
+ $args = explode( ',', $this->get_option( 'lockout_notify' ) );
505
+
506
+ if ( empty( $args ) ) {
507
+ return;
508
+ }
509
+
510
+ foreach ( $args as $mode ) {
511
+ switch ( trim( $mode ) ) {
512
+ case 'email':
513
+ $this->notify_email( $user );
514
+ break;
515
+ case 'log':
516
+ $this->notify_log( $user );
517
+ break;
518
+ }
519
+ }
520
+ }
521
+
522
+ /**
523
+ * Email notification of lockout to admin (if configured)
524
+ *
525
+ * @param $user
526
+ */
527
+ public function notify_email( $user ) {
528
+ $ip = $this->get_address();
529
+ $whitelisted = $this->is_ip_whitelisted( $ip );
530
+
531
+ $retries = $this->get_option( 'retries' );
532
+ if ( ! is_array( $retries ) ) {
533
+ $retries = array();
534
+ }
535
+
536
+ /* check if we are at the right nr to do notification */
537
+ if ( isset( $retries[ $ip ] ) && ( ( $retries[ $ip ] / $this->get_option( 'allowed_retries' ) ) % $this->get_option( 'notify_email_after' ) ) != 0 ) {
538
+ return;
539
+ }
540
+
541
+ /* Format message. First current lockout duration */
542
+ if ( ! isset( $retries[ $ip ] ) ) {
543
+ /* longer lockout */
544
+ $count = $this->get_option( 'allowed_retries' )
545
+ * $this->get_option( 'allowed_lockouts' );
546
+ $lockouts = $this->get_option( 'allowed_lockouts' );
547
+ $time = round( $this->get_option( 'long_duration' ) / 3600 );
548
+ $when = sprintf( _n( '%d hour', '%d hours', $time, 'limit-login-attempts-reloaded' ), $time );
549
+ } else {
550
+ /* normal lockout */
551
+ $count = $retries[ $ip ];
552
+ $lockouts = floor( $count / $this->get_option( 'allowed_retries' ) );
553
+ $time = round( $this->get_option( 'lockout_duration' ) / 60 );
554
+ $when = sprintf( _n( '%d minute', '%d minutes', $time, 'limit-login-attempts-reloaded' ), $time );
555
+ }
556
+
557
+ $blogname = $this->use_local_options ? get_option( 'blogname' ) : get_site_option( 'site_name' );
558
+
559
+ if ( $whitelisted ) {
560
+ $subject = sprintf( __( "[%s] Failed login attempts from whitelisted IP"
561
+ , 'limit-login-attempts-reloaded' )
562
+ , $blogname );
563
+ } else {
564
+ $subject = sprintf( __( "[%s] Too many failed login attempts"
565
+ , 'limit-login-attempts-reloaded' )
566
+ , $blogname );
567
+ }
568
+
569
+ $message = sprintf( __( "%d failed login attempts (%d lockout(s)) from IP: %s"
570
+ , 'limit-login-attempts-reloaded' ) . "\r\n\r\n"
571
+ , $count, $lockouts, $ip );
572
+ if ( $user != '' ) {
573
+ $message .= sprintf( __( "Last user attempted: %s", 'limit-login-attempts-reloaded' )
574
+ . "\r\n\r\n", $user );
575
+ }
576
+ if ( $whitelisted ) {
577
+ $message .= __( "IP was NOT blocked because of external whitelist.", 'limit-login-attempts-reloaded' );
578
+ } else {
579
+ $message .= sprintf( __( "IP was blocked for %s", 'limit-login-attempts-reloaded' ), $when );
580
+ }
581
+
582
+ $admin_email = $this->use_local_options ? get_option( 'admin_email' ) : get_site_option( 'admin_email' );
583
+
584
+ @wp_mail( $admin_email, $subject, $message );
585
+ }
586
+
587
+ /**
588
+ * Logging of lockout (if configured)
589
+ *
590
+ * @param $user_login
591
+ *
592
+ * @internal param $user
593
+ */
594
+ public function notify_log( $user_login ) {
595
+
596
+ if ( ! $user_login ) {
597
+ return;
598
+ }
599
+
600
+ $log = $option = $this->get_option( 'logged' );
601
+ if ( ! is_array( $log ) ) {
602
+ $log = array();
603
+ }
604
+ $ip = $this->get_address();
605
+
606
+ /* can be written much simpler, if you do not mind php warnings */
607
+ if ( isset( $log[ $ip ] ) ) {
608
+ if ( isset( $log[ $ip ][ $user_login ] ) ) {
609
+
610
+ if ( is_array( $log[ $ip ][ $user_login ] ) ) { // For new plugin version
611
+ $log[ $ip ][ $user_login ]['counter'] += 1;
612
+ } else { // For old plugin version
613
+ $temp_counter = $log[ $ip ][ $user_login ];
614
+ $log[ $ip ][ $user_login ] = array(
615
+ 'counter' => $temp_counter + 1
616
+ );
617
+ }
618
+ } else {
619
+ $log[ $ip ][ $user_login ] = array(
620
+ 'counter' => 1
621
+ );
622
+ }
623
+ } else {
624
+ $log[ $ip ] = array(
625
+ $user_login => array(
626
+ 'counter' => 1
627
+ )
628
+ );
629
+ }
630
+
631
+ $log[ $ip ][ $user_login ]['date'] = time();
632
+
633
+ if ( isset( $_POST['woocommerce-login-nonce'] ) ) {
634
+ $gateway = 'WooCommerce';
635
+ } elseif ( isset( $GLOBALS['wp_xmlrpc_server'] ) && is_object( $GLOBALS['wp_xmlrpc_server'] ) ) {
636
+ $gateway = 'XMLRPC';
637
+ } else {
638
+ $gateway = 'WP Login';
639
+ }
640
+
641
+ $log[ $ip ][ $user_login ]['gateway'] = $gateway;
642
+
643
+ if ( $option === false ) {
644
+ $this->add_option( 'logged', $log );
645
+ } else {
646
+ $this->update_option( 'logged', $log );
647
+ }
648
+ }
649
+
650
+ /**
651
+ * Check if IP is whitelisted.
652
+ *
653
+ * This function allow external ip whitelisting using a filter. Note that it can
654
+ * be called multiple times during the login process.
655
+ *
656
+ * Note that retries and statistics are still counted and notifications
657
+ * done as usual for whitelisted ips , but no lockout is done.
658
+ *
659
+ * Example:
660
+ * function my_ip_whitelist($allow, $ip) {
661
+ * return ($ip == 'my-ip') ? true : $allow;
662
+ * }
663
+ * add_filter('limit_login_whitelist_ip', 'my_ip_whitelist', 10, 2);
664
+ *
665
+ * @param null $ip
666
+ *
667
+ * @return bool
668
+ */
669
+ public function is_ip_whitelisted( $ip = null ) {
670
+
671
+ if ( is_null( $ip ) ) {
672
+ $ip = $this->get_address();
673
+ }
674
+
675
+ $whitelisted = apply_filters( 'limit_login_whitelist_ip', false, $ip );
676
+
677
+ return ( $whitelisted === true );
678
+ }
679
+
680
+ public function is_username_whitelisted( $username ) {
681
+
682
+ if ( empty( $username ) ) {
683
+ return false;
684
+ }
685
+
686
+ $whitelisted = apply_filters( 'limit_login_whitelist_usernames', false, $username );
687
+
688
+ return ( $whitelisted === true );
689
+ }
690
+
691
+ public function is_ip_blacklisted( $ip = null ) {
692
+
693
+ if ( is_null( $ip ) ) {
694
+ $ip = $this->get_address();
695
+ }
696
+
697
+ $whitelisted = apply_filters( 'limit_login_blacklist_ip', false, $ip );
698
+
699
+ return ( $whitelisted === true );
700
+ }
701
+
702
+ public function is_username_blacklisted( $username ) {
703
+
704
+ if ( empty( $username ) ) {
705
+ return false;
706
+ }
707
+
708
+ $whitelisted = apply_filters( 'limit_login_blacklist_usernames', false, $username );
709
+
710
+ return ( $whitelisted === true );
711
+ }
712
+
713
+ /**
714
+ * Filter: allow login attempt? (called from wp_authenticate())
715
+ *
716
+ * @param $user WP_User
717
+ * @param $password
718
+ *
719
+ * @return \WP_Error
720
+ */
721
+ public function wp_authenticate_user( $user, $password ) {
722
+
723
+ if ( is_wp_error( $user ) ||
724
+ $this->check_whitelist_ips( false, $this->get_address() ) ||
725
+ $this->check_whitelist_usernames( false, $user->user_login ) ||
726
+ $this->is_limit_login_ok()
727
+ ) {
728
+
729
+ return $user;
730
+ }
731
+
732
+ $error = new WP_Error();
733
+
734
+ global $limit_login_my_error_shown;
735
+ $limit_login_my_error_shown = true;
736
+
737
+ if ( $this->is_username_blacklisted( $user->user_login ) || $this->is_ip_blacklisted( $this->get_address() ) ) {
738
+ $error->add( 'username_blacklisted', "<strong>ERROR:</strong> Too many failed login attempts." );
739
+ } else {
740
+ // This error should be the same as in "shake it" filter below
741
+ $error->add( 'too_many_retries', $this->error_msg() );
742
+ }
743
+
744
+ return $error;
745
+ }
746
+
747
+ /**
748
+ * Filter: add this failure to login page "Shake it!"
749
+ *
750
+ * @param $error_codes
751
+ *
752
+ * @return array
753
+ */
754
+ public function failure_shake( $error_codes ) {
755
+ $error_codes[] = 'too_many_retries';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
756
  $error_codes[] = 'username_blacklisted';
757
 
758
+ return $error_codes;
759
+ }
760
+
761
+ /**
762
+ * Keep track of if user or password are empty, to filter errors correctly
763
+ *
764
+ * @param $user
765
+ * @param $password
766
+ */
767
+ public function track_credentials( $user, $password ) {
768
+ global $limit_login_nonempty_credentials;
769
+
770
+ $limit_login_nonempty_credentials = ( ! empty( $user ) && ! empty( $password ) );
771
+ }
772
+
773
+ /**
774
+ * Should we show errors and messages on this page?
775
+ *
776
+ * @return bool
777
+ */
778
+ public function login_show_msg() {
779
+ if ( isset( $_GET['key'] ) ) {
780
+ /* reset password */
781
+ return false;
782
+ }
783
+
784
+ $action = isset( $_REQUEST['action'] ) ? $_REQUEST['action'] : '';
785
+
786
+ return ( $action != 'lostpassword' && $action != 'retrievepassword'
787
+ && $action != 'resetpass' && $action != 'rp'
788
+ && $action != 'register' );
789
+ }
790
+
791
+ /**
792
+ * Construct informative error message
793
+ *
794
+ * @return string
795
+ */
796
+ public function error_msg() {
797
+ $ip = $this->get_address();
798
+ $lockouts = $this->get_option( 'lockouts' );
799
+
800
+ $msg = __( '<strong>ERROR</strong>: Too many failed login attempts.', 'limit-login-attempts-reloaded' ) . ' ';
801
+
802
+ if ( ! is_array( $lockouts ) || ! isset( $lockouts[ $ip ] ) || time() >= $lockouts[ $ip ] ) {
803
+ /* Huh? No timeout active? */
804
+ $msg .= __( 'Please try again later.', 'limit-login-attempts-reloaded' );
805
+
806
+ return $msg;
807
+ }
808
+
809
+ $when = ceil( ( $lockouts[ $ip ] - time() ) / 60 );
810
+ if ( $when > 60 ) {
811
+ $when = ceil( $when / 60 );
812
+ $msg .= sprintf( _n( 'Please try again in %d hour.', 'Please try again in %d hours.', $when, 'limit-login-attempts-reloaded' ), $when );
813
+ } else {
814
+ $msg .= sprintf( _n( 'Please try again in %d minute.', 'Please try again in %d minutes.', $when, 'limit-login-attempts-reloaded' ), $when );
815
+ }
816
+
817
+ return $msg;
818
+ }
819
+
820
+ /**
821
+ * Add a message to login page when necessary
822
+ */
823
+ public function add_error_message() {
824
+ global $error, $limit_login_my_error_shown;
825
+
826
+ if ( ! $this->login_show_msg() || $limit_login_my_error_shown ) {
827
+ return;
828
+ }
829
+
830
+ $msg = $this->get_message();
831
+
832
+ if ( $msg != '' ) {
833
+ $limit_login_my_error_shown = true;
834
+ $error .= $msg;
835
+ }
836
+
837
+ return;
838
+ }
839
+
840
+ /**
841
+ * Fix up the error message before showing it
842
+ *
843
+ * @param $content
844
+ *
845
+ * @return string
846
+ */
847
+ public function fixup_error_messages( $content ) {
848
+ global $limit_login_just_lockedout, $limit_login_nonempty_credentials, $limit_login_my_error_shown;
849
+
850
+ if ( ! $this->login_show_msg() ) {
851
+ return $content;
852
+ }
853
+
854
+ /*
855
+ * During lockout we do not want to show any other error messages (like
856
+ * unknown user or empty password).
857
+ */
858
+ if ( ! $this->is_limit_login_ok() && ! $limit_login_just_lockedout ) {
859
+ return $this->error_msg();
860
+ }
861
+
862
+ /*
863
+ * We want to filter the messages 'Invalid username' and
864
+ * 'Invalid password' as that is an information leak regarding user
865
+ * account names (prior to WP 2.9?).
866
+ *
867
+ * Also, if more than one error message, put an extra <br /> tag between
868
+ * them.
869
+ */
870
+ $msgs = explode( "<br />\n", $content );
871
+
872
+ if ( strlen( end( $msgs ) ) == 0 ) {
873
+ /* remove last entry empty string */
874
+ array_pop( $msgs );
875
+ }
876
+
877
+ $count = count( $msgs );
878
+ $my_warn_count = $limit_login_my_error_shown ? 1 : 0;
879
+
880
+ if ( $limit_login_nonempty_credentials && $count > $my_warn_count ) {
881
+ /* Replace error message, including ours if necessary */
882
+ $content = __( '<strong>ERROR</strong>: Incorrect username or password.', 'limit-login-attempts-reloaded' ) . "<br />\n";
883
+
884
+ if ( $limit_login_my_error_shown || $this->get_message() ) {
885
+ $content .= "<br />\n" . $this->get_message() . "<br />\n";
886
+ }
887
+
888
+ return $content;
889
+ } elseif ( $count <= 1 ) {
890
+ return $content;
891
+ }
892
+
893
+ $new = '';
894
+ while ( $count -- > 0 ) {
895
+ $new .= array_shift( $msgs ) . "<br />\n";
896
+ if ( $count > 0 ) {
897
+ $new .= "<br />\n";
898
+ }
899
+ }
900
+
901
+ return $new;
902
+ }
903
+
904
+ public function fixup_error_messages_wc( \WP_Error $error ) {
905
+ $error->add( 1, __( 'WC Error' ) );
906
+ }
907
+
908
+ /**
909
+ * Return current (error) message to show, if any
910
+ *
911
+ * @return string
912
+ */
913
+ public function get_message() {
914
+ /* Check external whitelist */
915
+ if ( $this->is_ip_whitelisted() ) {
916
+ return '';
917
+ }
918
+
919
+ /* Is lockout in effect? */
920
+ if ( ! $this->is_limit_login_ok() ) {
921
+ return $this->error_msg();
922
+ }
923
+
924
+ return $this->retries_remaining_msg();
925
+ }
926
+
927
+ /**
928
+ * Construct retries remaining message
929
+ *
930
+ * @return string
931
+ */
932
+ public function retries_remaining_msg() {
933
+ $ip = $this->get_address();
934
+ $retries = $this->get_option( 'retries' );
935
+ $valid = $this->get_option( 'retries_valid' );
936
+
937
+ /* Should we show retries remaining? */
938
+
939
+ if ( ! is_array( $retries ) || ! is_array( $valid ) ) {
940
+ /* no retries at all */
941
+ return '';
942
+ }
943
+ if ( ! isset( $retries[ $ip ] ) || ! isset( $valid[ $ip ] ) || time() > $valid[ $ip ] ) {
944
+ /* no: no valid retries */
945
+ return '';
946
+ }
947
+ if ( ( $retries[ $ip ] % $this->get_option( 'allowed_retries' ) ) == 0 ) {
948
+ /* no: already been locked out for these retries */
949
+ return '';
950
+ }
951
+
952
+ $remaining = max( ( $this->get_option( 'allowed_retries' ) - ( $retries[ $ip ] % $this->get_option( 'allowed_retries' ) ) ), 0 );
953
+
954
+ return sprintf( _n( "<strong>%d</strong> attempt remaining.", "<strong>%d</strong> attempts remaining.", $remaining, 'limit-login-attempts-reloaded' ), $remaining );
955
+ }
956
+
957
+ /**
958
+ * Get correct remote address
959
+ *
960
+ * @param string $type_name
961
+ *
962
+ * @return string
963
+ */
964
+ public function get_address( $type_name = '' ) {
965
+
966
+ if ( isset( $_SERVER['HTTP_X_FORWARDED_FOR'] ) && ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
967
+ return $_SERVER['HTTP_X_FORWARDED_FOR'];
968
+ } elseif ( isset( $_SERVER['REMOTE_ADDR'] ) ) {
969
+ return $_SERVER['REMOTE_ADDR'];
970
+ } else {
971
+ return '';
972
+ }
973
+ }
974
+
975
+ /**
976
+ * Clean up old lockouts and retries, and save supplied arrays
977
+ *
978
+ * @param null $retries
979
+ * @param null $lockouts
980
+ * @param null $valid
981
+ */
982
+ public function cleanup( $retries = null, $lockouts = null, $valid = null ) {
983
+ $now = time();
984
+ $lockouts = ! is_null( $lockouts ) ? $lockouts : $this->get_option( 'lockouts' );
985
+
986
+ /* remove old lockouts */
987
+ if ( is_array( $lockouts ) ) {
988
+ foreach ( $lockouts as $ip => $lockout ) {
989
+ if ( $lockout < $now ) {
990
+ unset( $lockouts[ $ip ] );
991
+ }
992
+ }
993
+ $this->update_option( 'lockouts', $lockouts );
994
+ }
995
+
996
+ /* remove retries that are no longer valid */
997
+ $valid = ! is_null( $valid ) ? $valid : $this->get_option( 'retries_valid' );
998
+ $retries = ! is_null( $retries ) ? $retries : $this->get_option( 'retries' );
999
+ if ( ! is_array( $valid ) || ! is_array( $retries ) ) {
1000
+ return;
1001
+ }
1002
+
1003
+ foreach ( $valid as $ip => $lockout ) {
1004
+ if ( $lockout < $now ) {
1005
+ unset( $valid[ $ip ] );
1006
+ unset( $retries[ $ip ] );
1007
+ }
1008
+ }
1009
+
1010
+ /* go through retries directly, if for some reason they've gone out of sync */
1011
+ foreach ( $retries as $ip => $retry ) {
1012
+ if ( ! isset( $valid[ $ip ] ) ) {
1013
+ unset( $retries[ $ip ] );
1014
+ }
1015
+ }
1016
+
1017
+ $this->update_option( 'retries', $retries );
1018
+ $this->update_option( 'retries_valid', $valid );
1019
+ }
1020
+
1021
+ /**
1022
+ * Render admin options page
1023
+ */
1024
+ public function options_page() {
1025
+ $this->use_local_options = !is_network_admin();
1026
+ $this->cleanup();
1027
+
1028
+ if( !empty( $_POST ) )
1029
+ {
1030
+ check_admin_referer( 'limit-login-attempts-options' );
1031
+
1032
+ if ( is_network_admin() )
1033
+ $this->update_option( 'allow_local_options', !empty($_POST['allow_local_options']) );
1034
+
1035
+ elseif ( $this->network_mode )
1036
+ $this->update_option( 'use_local_options', empty($_POST['use_global_options']) );
1037
+
1038
+ /* Should we clear log? */
1039
+ if( isset( $_POST[ 'clear_log' ] ) )
1040
+ {
1041
+ $this->update_option( 'logged', '' );
1042
+ $this->show_error( __( 'Cleared IP log', 'limit-login-attempts-reloaded' ) );
1043
+ }
1044
+
1045
+ /* Should we reset counter? */
1046
+ if( isset( $_POST[ 'reset_total' ] ) )
1047
+ {
1048
+ $this->update_option( 'lockouts_total', 0 );
1049
+ $this->show_error( __( 'Reset lockout count', 'limit-login-attempts-reloaded' ) );
1050
+ }
1051
+
1052
+ /* Should we restore current lockouts? */
1053
+ if( isset( $_POST[ 'reset_current' ] ) )
1054
+ {
1055
+ $this->update_option( 'lockouts', array() );
1056
+ $this->show_error( __( 'Cleared current lockouts', 'limit-login-attempts-reloaded' ) );
1057
+ }
1058
+
1059
+ /* Should we update options? */
1060
+ if( isset( $_POST[ 'update_options' ] ) )
1061
+ {
1062
+ $this->update_option('allowed_retries', (int)$_POST['allowed_retries'] );
1063
+ $this->update_option('lockout_duration', (int)$_POST['lockout_duration'] * 60 );
1064
+ $this->update_option('valid_duration', (int)$_POST['valid_duration'] * 3600 );
1065
+ $this->update_option('allowed_lockouts', (int)$_POST['allowed_lockouts'] );
1066
+ $this->update_option('long_duration', (int)$_POST['long_duration'] * 3600 );
1067
+ $this->update_option('notify_email_after', (int)$_POST['email_after'] * 3600 );
1068
+
1069
+ $white_list_ips = ( !empty( $_POST['lla_whitelist_ips'] ) ) ? explode("\n", str_replace("\r", "", $_POST['lla_whitelist_ips'] ) ) : array();
1070
+
1071
+ if( !empty( $white_list_ips ) ) {
1072
+ foreach( $white_list_ips as $key => $ip ) {
1073
+ if( '' == $ip ) {
1074
+ unset( $white_list_ips[ $key ] );
1075
+ }
1076
+ }
1077
+ }
1078
+ $this->update_option('whitelist', $white_list_ips );
1079
+
1080
+ $white_list_usernames = ( !empty( $_POST['lla_whitelist_usernames'] ) ) ? explode("\n", str_replace("\r", "", $_POST['lla_whitelist_usernames'] ) ) : array();
1081
+
1082
+ if( !empty( $white_list_usernames ) ) {
1083
+ foreach( $white_list_usernames as $key => $ip ) {
1084
+ if( '' == $ip ) {
1085
+ unset( $white_list_usernames[ $key ] );
1086
+ }
1087
+ }
1088
+ }
1089
+ $this->update_option('whitelist_usernames', $white_list_usernames );
1090
+
1091
+ $black_list_ips = ( !empty( $_POST['lla_blacklist_ips'] ) ) ? explode("\n", str_replace("\r", "", $_POST['lla_blacklist_ips'] ) ) : array();
1092
+
1093
+ if( !empty( $black_list_ips ) ) {
1094
+ foreach( $black_list_ips as $key => $ip ) {
1095
+ if( '' == $ip ) {
1096
+ unset( $black_list_ips[ $key ] );
1097
+ }
1098
+ }
1099
+ }
1100
+ $this->update_option('blacklist', $black_list_ips );
1101
+
1102
+ $black_list_usernames = ( !empty( $_POST['lla_blacklist_usernames'] ) ) ? explode("\n", str_replace("\r", "", $_POST['lla_blacklist_usernames'] ) ) : array();
1103
+
1104
+ if( !empty( $black_list_usernames ) ) {
1105
+ foreach( $black_list_usernames as $key => $ip ) {
1106
+ if( '' == $ip ) {
1107
+ unset( $black_list_usernames[ $key ] );
1108
+ }
1109
+ }
1110
+ }
1111
+ $this->update_option('blacklist_usernames', $black_list_usernames );
1112
+
1113
+ $notify_methods = array();
1114
+ if( isset( $_POST[ 'lockout_notify_log' ] ) ) {
1115
+ $notify_methods[] = 'log';
1116
+ }
1117
+ if( isset( $_POST[ 'lockout_notify_email' ] ) ) {
1118
+ $notify_methods[] = 'email';
1119
+ }
1120
+ $this->update_option('lockout_notify', implode( ',', $notify_methods ) );
1121
+
1122
+ $this->sanitize_options();
1123
+
1124
+ $this->show_error( __( 'Options changed', 'limit-login-attempts-reloaded' ) );
1125
+ }
1126
+ }
1127
+
1128
+ include_once( LLA_PLUGIN_DIR . '/views/options-page.php' );
1129
+ }
1130
+
1131
+ /**
1132
+ * Show error message
1133
+ *
1134
+ * @param $msg
1135
+ */
1136
+ public function show_error( $msg ) {
1137
+ LLA_Helpers::show_error( $msg );
1138
+ }
1139
 
1140
  }
limit-login-attempts-reloaded.php CHANGED
@@ -4,9 +4,9 @@
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.4.0
8
 
9
- Copyright 2008 - 2012 Johan Eenfeldt
10
 
11
  Thanks to Michael Skerwiderski for reverse proxy handling suggestions.
12
  */
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.5.0
8
 
9
+ Copyright 2008 - 2012 Johan Eenfeldt, 2016 - 2017 WPChef
10
 
11
  Thanks to Michael Skerwiderski for reverse proxy handling suggestions.
12
  */
readme.txt CHANGED
@@ -1,9 +1,9 @@
1
  === Limit Login Attempts Reloaded ===
2
  Contributors: wpchefgadget
3
  Tags: login, security, authentication, Limit Login Attempts, Limit Login Attempts Reloaded, Limit Login Attempts Revamped, Limit Login Attempts Renovated, Limit Login Attempts Updated, Better Limit Login Attempts, Limit Login Attempts Renewed, Limit Login Attempts Upgraded
4
- Requires at least: 2.8
5
- Tested up to: 4.7
6
- Stable tag: 2.4.0
7
 
8
  Reloaded version of the original Limit Login Attempts plugin for Login Protection by a team of WordPress developers.
9
 
@@ -23,6 +23,7 @@ Features:
23
  * It is possible to whitelist/blacklist IPs and Usernames.
24
  * **XMLRPC** gateway protection.
25
  * **Woocommerce** login page protection.
 
26
 
27
  = Upgrading from the old Limit Login Attempts plugin =
28
  1. Go to the Plugins section in your site's backend.
@@ -48,6 +49,9 @@ Based on the original code from Limit Login Attemps plugin by Johan Eenfeldt.
48
 
49
  == Changelog ==
50
 
 
 
 
51
  = 2.4.0 =
52
  * 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/
53
  * The lockouts log has been inversed. https://wordpress.org/support/topic/inverse-log/
1
  === Limit Login Attempts Reloaded ===
2
  Contributors: wpchefgadget
3
  Tags: login, security, authentication, Limit Login Attempts, Limit Login Attempts Reloaded, Limit Login Attempts Revamped, Limit Login Attempts Renovated, Limit Login Attempts Updated, Better Limit Login Attempts, Limit Login Attempts Renewed, Limit Login Attempts Upgraded
4
+ Requires at least: 3.0
5
+ Tested up to: 4.7.2
6
+ Stable tag: 2.5.0
7
 
8
  Reloaded version of the original Limit Login Attempts plugin for Login Protection by a team of WordPress developers.
9
 
23
  * It is possible to whitelist/blacklist IPs and Usernames.
24
  * **XMLRPC** gateway protection.
25
  * **Woocommerce** login page protection.
26
+ * **Multi-site** compatibility with extra MU settings.
27
 
28
  = Upgrading from the old Limit Login Attempts plugin =
29
  1. Go to the Plugins section in your site's backend.
49
 
50
  == Changelog ==
51
 
52
+ = 2.5.0 =
53
+ * Added Multi-site Compatibility and additional MU settings. https://wordpress.org/support/topic/multisite-compatibility-47/
54
+
55
  = 2.4.0 =
56
  * 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/
57
  * The lockouts log has been inversed. https://wordpress.org/support/topic/inverse-log/
views/options-page.php CHANGED
@@ -7,109 +7,8 @@ if( !defined( 'ABSPATH' ) )
7
  * @var $this Limit_Login_Attempts
8
  */
9
 
10
- if( !current_user_can( 'manage_options' ) ) {
11
- wp_die( 'Sorry, but you do not have permissions to change settings.' );
12
- }
13
-
14
- /* Make sure post was from this page */
15
- if( !empty( $_POST ) ) {
16
- check_admin_referer( 'limit-login-attempts-options' );
17
- }
18
-
19
- /* Should we clear log? */
20
- if( isset( $_POST[ 'clear_log' ] ) ) {
21
- delete_option( 'limit_login_logged' );
22
- $this->show_error( __( 'Cleared IP log', 'limit-login-attempts-reloaded' ) );
23
- }
24
-
25
- /* Should we reset counter? */
26
- if( isset( $_POST[ 'reset_total' ] ) ) {
27
- update_option( 'limit_login_lockouts_total', 0 );
28
- $this->show_error( __( 'Reset lockout count', 'limit-login-attempts-reloaded' ) );
29
- }
30
-
31
- /* Should we restore current lockouts? */
32
- if( isset( $_POST[ 'reset_current' ] ) ) {
33
- update_option( 'limit_login_lockouts', array() );
34
- $this->show_error( __( 'Cleared current lockouts', 'limit-login-attempts-reloaded' ) );
35
- }
36
-
37
- /* Should we update options? */
38
- if( isset( $_POST[ 'update_options' ] ) ) {
39
-
40
- $this->_options[ 'allowed_retries' ] = $_POST[ 'allowed_retries' ];
41
- $this->_options[ 'lockout_duration' ] = $_POST[ 'lockout_duration' ] * 60;
42
- $this->_options[ 'valid_duration' ] = $_POST[ 'valid_duration' ] * 3600;
43
- $this->_options[ 'allowed_lockouts' ] = $_POST[ 'allowed_lockouts' ];
44
- $this->_options[ 'long_duration' ] = $_POST[ 'long_duration' ] * 3600;
45
- $this->_options[ 'notify_email_after' ] = $_POST[ 'email_after' ];
46
-
47
- $white_list_ips = ( !empty( $_POST['lla_whitelist_ips'] ) ) ? explode("\n", str_replace("\r", "", $_POST['lla_whitelist_ips'] ) ) : array();
48
-
49
- if( !empty( $white_list_ips ) ) {
50
- foreach( $white_list_ips as $key => $ip ) {
51
- if( '' == $ip ) {
52
- unset( $white_list_ips[ $key ] );
53
- }
54
- }
55
- }
56
-
57
- $this->_options['whitelist'] = $white_list_ips;
58
-
59
- $white_list_usernames = ( !empty( $_POST['lla_whitelist_usernames'] ) ) ? explode("\n", str_replace("\r", "", $_POST['lla_whitelist_usernames'] ) ) : array();
60
-
61
- if( !empty( $white_list_usernames ) ) {
62
- foreach( $white_list_usernames as $key => $ip ) {
63
- if( '' == $ip ) {
64
- unset( $white_list_usernames[ $key ] );
65
- }
66
- }
67
- }
68
-
69
- $this->_options['whitelist_usernames'] = $white_list_usernames;
70
-
71
-
72
- $black_list_ips = ( !empty( $_POST['lla_blacklist_ips'] ) ) ? explode("\n", str_replace("\r", "", $_POST['lla_blacklist_ips'] ) ) : array();
73
-
74
- if( !empty( $black_list_ips ) ) {
75
- foreach( $black_list_ips as $key => $ip ) {
76
- if( '' == $ip ) {
77
- unset( $black_list_ips[ $key ] );
78
- }
79
- }
80
- }
81
-
82
- $this->_options['blacklist'] = $black_list_ips;
83
-
84
- $black_list_usernames = ( !empty( $_POST['lla_blacklist_usernames'] ) ) ? explode("\n", str_replace("\r", "", $_POST['lla_blacklist_usernames'] ) ) : array();
85
-
86
- if( !empty( $black_list_usernames ) ) {
87
- foreach( $black_list_usernames as $key => $ip ) {
88
- if( '' == $ip ) {
89
- unset( $black_list_usernames[ $key ] );
90
- }
91
- }
92
- }
93
-
94
- $this->_options['blacklist_usernames'] = $black_list_usernames;
95
-
96
- $notify_methods = array();
97
- if( isset( $_POST[ 'lockout_notify_log' ] ) ) {
98
- $notify_methods[] = 'log';
99
- }
100
- if( isset( $_POST[ 'lockout_notify_email' ] ) ) {
101
- $notify_methods[] = 'email';
102
- }
103
- $this->_options[ 'lockout_notify' ] = implode( ',', $notify_methods );
104
-
105
- $this->sanitize_variables();
106
- $this->update_options();
107
-
108
- $this->show_error( __( 'Options changed', 'limit-login-attempts-reloaded' ) );
109
- }
110
-
111
- $lockouts_total = get_option( 'limit_login_lockouts_total', 0 );
112
- $lockouts = get_option( 'limit_login_lockouts' );
113
  $lockouts_now = is_array( $lockouts ) ? count( $lockouts ) : 0;
114
 
115
  $v = explode( ',', $this->get_option( 'lockout_notify' ) );
@@ -164,10 +63,33 @@ $black_list_usernames = ( is_array( $black_list_usernames ) && !empty( $black_li
164
  <h3><?php echo __( 'Options', 'limit-login-attempts-reloaded' ); ?></h3>
165
  <form action="<?php echo $this->get_options_page_uri(); ?>" method="post">
166
  <?php wp_nonce_field( 'limit-login-attempts-options' ); ?>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  <table class="form-table">
168
  <tr>
169
  <th scope="row" valign="top"><?php echo __( 'Lockout', 'limit-login-attempts-reloaded' ); ?></th>
170
  <td>
 
171
  <input type="text" size="3" maxlength="4"
172
  value="<?php echo( $this->get_option( 'allowed_retries' ) ); ?>"
173
  name="allowed_retries"/> <?php echo __( 'allowed retries', 'limit-login-attempts-reloaded' ); ?>
@@ -235,7 +157,7 @@ $black_list_usernames = ( is_array( $black_list_usernames ) && !empty( $black_li
235
  </p>
236
  </form>
237
  <?php
238
- $log = get_option( 'limit_login_logged' );
239
  $log = LLA_Helpers::sorted_log_by_date( $log );
240
 
241
  if( is_array( $log ) && ! empty( $log ) ) { ?>
7
  * @var $this Limit_Login_Attempts
8
  */
9
 
10
+ $lockouts_total = $this->get_option( 'lockouts_total', 0 );
11
+ $lockouts = $this->get_option( 'login_lockouts' );
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  $lockouts_now = is_array( $lockouts ) ? count( $lockouts ) : 0;
13
 
14
  $v = explode( ',', $this->get_option( 'lockout_notify' ) );
63
  <h3><?php echo __( 'Options', 'limit-login-attempts-reloaded' ); ?></h3>
64
  <form action="<?php echo $this->get_options_page_uri(); ?>" method="post">
65
  <?php wp_nonce_field( 'limit-login-attempts-options' ); ?>
66
+ <?php if ( is_network_admin() ): ?>
67
+ <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' ); ?>
68
+ <p class="description"><?php esc_html_e('If disabled, the global settings will be forcibly applied to the entire network.') ?></p>
69
+ <?php elseif ( $this->network_mode ): ?>
70
+ <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/>
71
+ <script>
72
+ jQuery(function($){
73
+ var first = true;
74
+ $('.use_global_options').change( function(){
75
+ var form = $(this).siblings('table');
76
+ form.stop();
77
+
78
+ if ( this.checked )
79
+ first ? form.hide() : form.fadeOut();
80
+ else
81
+ first ? form.show() : form.fadeIn();
82
+
83
+ first = false;
84
+ }).change();
85
+ });
86
+ </script>
87
+ <?php endif ?>
88
  <table class="form-table">
89
  <tr>
90
  <th scope="row" valign="top"><?php echo __( 'Lockout', 'limit-login-attempts-reloaded' ); ?></th>
91
  <td>
92
+
93
  <input type="text" size="3" maxlength="4"
94
  value="<?php echo( $this->get_option( 'allowed_retries' ) ); ?>"
95
  name="allowed_retries"/> <?php echo __( 'allowed retries', 'limit-login-attempts-reloaded' ); ?>
157
  </p>
158
  </form>
159
  <?php
160
+ $log = $this->get_option( 'logged' );
161
  $log = LLA_Helpers::sorted_log_by_date( $log );
162
 
163
  if( is_array( $log ) && ! empty( $log ) ) { ?>