Limit Login Attempts Reloaded - Version 2.7.0

Version Description

  • GDPR compliance implemented.

  • Fixed: ip_in_range() loop $ip overrides itself causing invalid results. https://wordpress.org/support/topic/ip_in_range-loop-ip-overrides-itself-causing-invalid-results/

  • Fixed: the plugin was locking out the same IP address multiple times, each with a different port. https://wordpress.org/support/topic/same-ip-different-port/

Download this release

Release Info

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

Code changes from version 2.6.3 to 2.7.0

core/LimitLoginAttempts.php CHANGED
@@ -1,1215 +1,1284 @@
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
- if ( defined('XMLRPC_REQUEST') && XMLRPC_REQUEST )
130
- add_action( 'init', array( $this, 'check_xmlrpc_lock' ) );
131
-
132
- add_action('wp_ajax_limit-login-unlock', array( $this, 'ajax_unlock' ) );
133
- }
134
-
135
- public function check_xmlrpc_lock()
136
- {
137
- if ( is_user_logged_in() || $this->is_ip_whitelisted() )
138
- return;
139
-
140
- if ( $this->is_ip_blacklisted() || !$this->is_limit_login_ok() )
141
- {
142
- header('HTTP/1.0 403 Forbidden');
143
- exit;
144
- }
145
- }
146
-
147
- public function check_whitelist_ips( $allow, $ip ) {
148
- return $this->ip_in_range( $ip, (array) $this->get_option( 'whitelist' ) );
149
- }
150
-
151
- public function check_whitelist_usernames( $allow, $username ) {
152
- return in_array( $username, (array) $this->get_option( 'whitelist_usernames' ) );
153
- }
154
-
155
- public function check_blacklist_ips( $allow, $ip ) {
156
- return $this->ip_in_range( $ip, (array) $this->get_option( 'blacklist' ) );
157
- }
158
-
159
- public function check_blacklist_usernames( $allow, $username ) {
160
- return in_array( $username, (array) $this->get_option( 'blacklist_usernames' ) );
161
- }
162
-
163
- public function ip_in_range( $ip, $list )
164
- {
165
- foreach ( $list as $range )
166
- {
167
- $range = array_map('trim', explode('-', $range) );
168
- if ( count( $range ) == 1 )
169
- {
170
- if ( (string)$ip === (string)$range[0] )
171
- return true;
172
- }
173
- else
174
- {
175
- $low = ip2long( $range[0] );
176
- $high = ip2long( $range[1] );
177
- $ip = ip2long( $ip );
178
-
179
- if ( $low === false || $high === false || $ip === false )
180
- continue;
181
-
182
- $low = (float)sprintf("%u",$low);
183
- $high = (float)sprintf("%u",$high);
184
- $ip = (float)sprintf("%u",$ip);
185
-
186
- if ( $ip >= $low && $ip <= $high )
187
- return true;
188
- }
189
- }
190
-
191
- return false;
192
- }
193
-
194
- /**
195
- * @param $error IXR_Error
196
- *
197
- * @return IXR_Error
198
- */
199
- public function xmlrpc_error_messages( $error ) {
200
-
201
- if ( ! class_exists( 'IXR_Error' ) ) {
202
- return $error;
203
- }
204
-
205
- if ( ! $this->is_limit_login_ok() ) {
206
- return new IXR_Error( 403, $this->error_msg() );
207
- }
208
-
209
- $ip = $this->get_address();
210
- $retries = $this->get_option( 'retries' );
211
- $valid = $this->get_option( 'retries_valid' );
212
-
213
- /* Should we show retries remaining? */
214
-
215
- if ( ! is_array( $retries ) || ! is_array( $valid ) ) {
216
- /* no retries at all */
217
- return $error;
218
- }
219
- if ( ! isset( $retries[ $ip ] ) || ! isset( $valid[ $ip ] ) || time() > $valid[ $ip ] ) {
220
- /* no: no valid retries */
221
- return $error;
222
- }
223
- if ( ( $retries[ $ip ] % $this->get_option( 'allowed_retries' ) ) == 0 ) {
224
- /* no: already been locked out for these retries */
225
- return $error;
226
- }
227
-
228
- $remaining = max( ( $this->get_option( 'allowed_retries' ) - ( $retries[ $ip ] % $this->get_option( 'allowed_retries' ) ) ), 0 );
229
-
230
- return new IXR_Error( 403, sprintf( _n( "<strong>%d</strong> attempt remaining.", "<strong>%d</strong> attempts remaining.", $remaining, 'limit-login-attempts-reloaded' ), $remaining ) );
231
- }
232
-
233
- /**
234
- * Errors on WooCommerce account page
235
- */
236
- public function add_wc_notices() {
237
-
238
- global $limit_login_just_lockedout, $limit_login_nonempty_credentials, $limit_login_my_error_shown;
239
-
240
- if ( ! function_exists( 'is_account_page' ) || ! function_exists( 'wc_add_notice' ) ) {
241
- return;
242
- }
243
-
244
- /*
245
- * During lockout we do not want to show any other error messages (like
246
- * unknown user or empty password).
247
- */
248
- if ( empty( $_POST ) && ! $this->is_limit_login_ok() && ! $limit_login_just_lockedout ) {
249
- if ( is_account_page() ) {
250
- wc_add_notice( $this->error_msg(), 'error' );
251
- }
252
- }
253
-
254
- }
255
-
256
- /**
257
- * @param $user
258
- * @param $username
259
- * @param $password
260
- *
261
- * @return WP_Error | WP_User
262
- */
263
- public function authenticate_filter( $user, $username, $password ) {
264
-
265
- if ( ! empty( $username ) && ! empty( $password ) ) {
266
-
267
- $ip = $this->get_address();
268
-
269
- // Check if username is blacklisted
270
- if ( ! $this->is_username_whitelisted( $username ) && ! $this->is_ip_whitelisted( $ip ) &&
271
- ( $this->is_username_blacklisted( $username ) || $this->is_ip_blacklisted( $ip ) )
272
- ) {
273
-
274
- remove_filter( 'login_errors', array( $this, 'fixup_error_messages' ) );
275
- remove_filter( 'login_head', array( $this, 'add_error_message' ) );
276
- remove_filter( 'wp_login_failed', array( $this, 'limit_login_failed' ) );
277
- remove_filter( 'wp_authenticate_user', array( $this, 'wp_authenticate_user' ), 99999 );
278
- remove_filter( 'login_head', array( $this, 'add_error_message' ) );
279
- remove_filter( 'login_errors', array( $this, 'fixup_error_messages' ) );
280
-
281
- remove_filter( 'authenticate', 'wp_authenticate_username_password', 20 );
282
- remove_filter( 'authenticate', 'wp_authenticate_email_password', 20 );
283
-
284
- $user = new WP_Error();
285
- $user->add( 'username_blacklisted', "<strong>ERROR:</strong> Too many failed login attempts." );
286
-
287
- } elseif ( $this->is_username_whitelisted( $username ) || $this->is_ip_whitelisted( $ip ) ) {
288
-
289
- remove_filter( 'wp_login_failed', array( $this, 'limit_login_failed' ) );
290
- remove_filter( 'wp_authenticate_user', array( $this, 'wp_authenticate_user' ), 99999 );
291
- remove_filter( 'login_head', array( $this, 'add_error_message' ) );
292
- remove_filter( 'login_errors', array( $this, 'fixup_error_messages' ) );
293
-
294
- }
295
-
296
- }
297
-
298
- return $user;
299
- }
300
-
301
- /**
302
- * Check if the original plugin is installed
303
- */
304
- private function check_original_installed()
305
- {
306
- require_once( ABSPATH . '/wp-admin/includes/plugin.php' );
307
- if ( is_plugin_active('limit-login-attempts/limit-login-attempts.php') )
308
- {
309
- deactivate_plugins( 'limit-login-attempts/limit-login-attempts.php', true );
310
- //add_action('plugins_loaded', 'limit_login_setup', 99999);
311
- remove_action( 'plugins_loaded', 'limit_login_setup', 99999 );
312
- }
313
- }
314
-
315
- /**
316
- * Enqueue js and css
317
- */
318
- public function enqueue() {
319
- wp_enqueue_style( 'lla-main', LLA_PLUGIN_URL . '/assets/css/limit-login-attempts.css' );
320
- }
321
-
322
- /**
323
- * Add admin options page
324
- */
325
- public function network_admin_menu()
326
- {
327
- add_submenu_page( 'settings.php', 'Limit Login Attempts', 'Limit Login Attempts', 'manage_options', $this->_options_page_slug, array( $this, 'options_page' ) );
328
- }
329
-
330
- public function admin_menu()
331
- {
332
- add_options_page( 'Limit Login Attempts', 'Limit Login Attempts', 'manage_options', $this->_options_page_slug, array( $this, 'options_page' ) );
333
- }
334
-
335
- /**
336
- * Get the correct options page URI
337
- *
338
- * @return mixed
339
- */
340
- public function get_options_page_uri()
341
- {
342
- if ( is_network_admin() )
343
- return network_admin_url( 'settings.php?page=limit-login-attempts' );
344
-
345
- return menu_page_url( $this->_options_page_slug, false );
346
- }
347
-
348
- /**
349
- * Get option by name
350
- *
351
- * @param $option_name
352
- *
353
- * @return null
354
- */
355
- public function get_option( $option_name, $local = null )
356
- {
357
- if ( is_null( $local ) )
358
- $local = $this->use_local_options;
359
-
360
- $option = 'limit_login_'.$option_name;
361
-
362
- $func = $local ? 'get_option' : 'get_site_option';
363
- $value = $func( $option, null );
364
-
365
- if ( is_null( $value ) && isset( $this->default_options[ $option_name ] ) )
366
- $value = $this->default_options[ $option_name ];
367
-
368
- return $value;
369
- }
370
-
371
- public function update_option( $option_name, $value, $local = null )
372
- {
373
- if ( is_null( $local ) )
374
- $local = $this->use_local_options;
375
-
376
- $option = 'limit_login_'.$option_name;
377
-
378
- $func = $local ? 'update_option' : 'update_site_option';
379
-
380
- return $func( $option, $value );
381
- }
382
-
383
- public function add_option( $option_name, $value, $local=null )
384
- {
385
- if ( is_null( $local ) )
386
- $local = $this->use_local_options;
387
-
388
- $option = 'limit_login_'.$option_name;
389
-
390
- $func = $local ? 'add_option' : 'add_site_option';
391
-
392
- return $func( $option, $value, '', 'no' );
393
- }
394
-
395
- /**
396
- * Setup main options
397
- */
398
- public function sanitize_options()
399
- {
400
- $simple_int_options = array( 'allowed_retries', 'lockout_duration', 'valid_duration', 'allowed_lockouts', 'long_duration', 'notify_email_after');
401
- foreach ( $simple_int_options as $option )
402
- {
403
- $val = $this->get_option( $option );
404
- if ( (int)$val != $val || (int)$val <= 0 )
405
- $this->update_option( $option, 1 );
406
- }
407
- if ( $this->get_option('notify_email_after') > $this->get_option( 'allowed_lockouts' ) )
408
- $this->update_option( 'notify_email_after', $this->get_option( 'allowed_lockouts' ) );
409
-
410
- $args = explode( ',', $this->get_option( 'lockout_notify' ) );
411
- $args_allowed = explode( ',', LLA_LOCKOUT_NOTIFY_ALLOWED );
412
- $new_args = array_intersect( $args, $args_allowed );
413
-
414
- $this->update_option( 'lockout_notify', implode( ',', $new_args ) );
415
-
416
- $ctype = $this->get_option( 'client_type' );
417
- if ( $ctype != LLA_DIRECT_ADDR && $ctype != LLA_PROXY_ADDR )
418
- $this->update_option( 'client_type', LLA_DIRECT_ADDR );
419
- }
420
-
421
- /**
422
- * Check if it is ok to login
423
- *
424
- * @return bool
425
- */
426
- public function is_limit_login_ok() {
427
-
428
- $ip = $this->get_address();
429
-
430
- /* Check external whitelist filter */
431
- if ( $this->is_ip_whitelisted( $ip ) ) {
432
- return true;
433
- }
434
-
435
- /* lockout active? */
436
- $lockouts = $this->get_option( 'lockouts' );
437
-
438
- return ( ! is_array( $lockouts ) || ! isset( $lockouts[ $ip ] ) || time() >= $lockouts[ $ip ] );
439
- }
440
-
441
- /**
442
- * Action when login attempt failed
443
- *
444
- * Increase nr of retries (if necessary). Reset valid value. Setup
445
- * lockout if nr of retries are above threshold. And more!
446
- *
447
- * A note on external whitelist: retries and statistics are still counted and
448
- * notifications done as usual, but no lockout is done.
449
- *
450
- * @param $username
451
- */
452
- public function limit_login_failed( $username ) {
453
-
454
- $ip = $this->get_address();
455
-
456
- /* if currently locked-out, do not add to retries */
457
- $lockouts = $this->get_option( 'lockouts' );
458
-
459
- if ( ! is_array( $lockouts ) ) {
460
- $lockouts = array();
461
- }
462
-
463
- if ( isset( $lockouts[ $ip ] ) && time() < $lockouts[ $ip ] ) {
464
- return;
465
- }
466
-
467
- /* Get the arrays with retries and retries-valid information */
468
- $retries = $this->get_option( 'retries' );
469
- $valid = $this->get_option( 'retries_valid' );
470
-
471
- if ( ! is_array( $retries ) ) {
472
- $retries = array();
473
- $this->add_option( 'retries', $retries );
474
- }
475
-
476
- if ( ! is_array( $valid ) ) {
477
- $valid = array();
478
- $this->add_option( 'retries_valid', $valid );
479
- }
480
-
481
- /* Check validity and add one to retries */
482
- if ( isset( $retries[ $ip ] ) && isset( $valid[ $ip ] ) && time() < $valid[ $ip ] ) {
483
- $retries[ $ip ] ++;
484
- } else {
485
- $retries[ $ip ] = 1;
486
- }
487
- $valid[ $ip ] = time() + $this->get_option( 'valid_duration' );
488
-
489
- /* lockout? */
490
- if ( $retries[ $ip ] % $this->get_option( 'allowed_retries' ) != 0 ) {
491
- /*
492
- * Not lockout (yet!)
493
- * Do housecleaning (which also saves retry/valid values).
494
- */
495
- $this->cleanup( $retries, null, $valid );
496
-
497
- return;
498
- }
499
-
500
- /* lockout! */
501
-
502
- $whitelisted = $this->is_ip_whitelisted( $ip );
503
-
504
- $retries_long = $this->get_option( 'allowed_retries' ) * $this->get_option( 'allowed_lockouts' );
505
-
506
- /*
507
- * Note that retries and statistics are still counted and notifications
508
- * done as usual for whitelisted ips , but no lockout is done.
509
- */
510
- if ( $whitelisted ) {
511
- if ( $retries[ $ip ] >= $retries_long ) {
512
- unset( $retries[ $ip ] );
513
- unset( $valid[ $ip ] );
514
- }
515
- } else {
516
- global $limit_login_just_lockedout;
517
- $limit_login_just_lockedout = true;
518
-
519
- /* setup lockout, reset retries as needed */
520
- if ( $retries[ $ip ] >= $retries_long ) {
521
- /* long lockout */
522
- $lockouts[ $ip ] = time() + $this->get_option( 'long_duration' );
523
- unset( $retries[ $ip ] );
524
- unset( $valid[ $ip ] );
525
- } else {
526
- /* normal lockout */
527
- $lockouts[ $ip ] = time() + $this->get_option( 'lockout_duration' );
528
- }
529
- }
530
-
531
- /* do housecleaning and save values */
532
- $this->cleanup( $retries, $lockouts, $valid );
533
-
534
- /* do any notification */
535
- $this->notify( $username );
536
-
537
- /* increase statistics */
538
- $total = $this->get_option( 'lockouts_total' );
539
- if ( $total === false || ! is_numeric( $total ) ) {
540
- $this->add_option( 'lockouts_total', 1 );
541
- } else {
542
- $this->update_option( 'lockouts_total', $total + 1 );
543
- }
544
- }
545
-
546
- /**
547
- * Handle notification in event of lockout
548
- *
549
- * @param $user
550
- */
551
- public function notify( $user ) {
552
- $args = explode( ',', $this->get_option( 'lockout_notify' ) );
553
-
554
- if ( empty( $args ) ) {
555
- return;
556
- }
557
-
558
- foreach ( $args as $mode ) {
559
- switch ( trim( $mode ) ) {
560
- case 'email':
561
- $this->notify_email( $user );
562
- break;
563
- case 'log':
564
- $this->notify_log( $user );
565
- break;
566
- }
567
- }
568
- }
569
-
570
- /**
571
- * Email notification of lockout to admin (if configured)
572
- *
573
- * @param $user
574
- */
575
- public function notify_email( $user ) {
576
- $ip = $this->get_address();
577
- $whitelisted = $this->is_ip_whitelisted( $ip );
578
-
579
- $retries = $this->get_option( 'retries' );
580
- if ( ! is_array( $retries ) ) {
581
- $retries = array();
582
- }
583
-
584
- /* check if we are at the right nr to do notification */
585
- if ( isset( $retries[ $ip ] ) && ( ( $retries[ $ip ] / $this->get_option( 'allowed_retries' ) ) % $this->get_option( 'notify_email_after' ) ) != 0 ) {
586
- return;
587
- }
588
-
589
- /* Format message. First current lockout duration */
590
- if ( ! isset( $retries[ $ip ] ) ) {
591
- /* longer lockout */
592
- $count = $this->get_option( 'allowed_retries' )
593
- * $this->get_option( 'allowed_lockouts' );
594
- $lockouts = $this->get_option( 'allowed_lockouts' );
595
- $time = round( $this->get_option( 'long_duration' ) / 3600 );
596
- $when = sprintf( _n( '%d hour', '%d hours', $time, 'limit-login-attempts-reloaded' ), $time );
597
- } else {
598
- /* normal lockout */
599
- $count = $retries[ $ip ];
600
- $lockouts = floor( $count / $this->get_option( 'allowed_retries' ) );
601
- $time = round( $this->get_option( 'lockout_duration' ) / 60 );
602
- $when = sprintf( _n( '%d minute', '%d minutes', $time, 'limit-login-attempts-reloaded' ), $time );
603
- }
604
-
605
- $blogname = $this->use_local_options ? get_option( 'blogname' ) : get_site_option( 'site_name' );
606
- $blogname = htmlspecialchars_decode( $blogname, ENT_QUOTES );
607
-
608
- if ( $whitelisted ) {
609
- $subject = sprintf( __( "[%s] Failed login attempts from whitelisted IP"
610
- , 'limit-login-attempts-reloaded' )
611
- , $blogname );
612
- } else {
613
- $subject = sprintf( __( "[%s] Too many failed login attempts"
614
- , 'limit-login-attempts-reloaded' )
615
- , $blogname );
616
- }
617
-
618
- $message = sprintf( __( "%d failed login attempts (%d lockout(s)) from IP: %s"
619
- , 'limit-login-attempts-reloaded' ) . "\r\n\r\n"
620
- , $count, $lockouts, $ip );
621
- if ( $user != '' ) {
622
- $message .= sprintf( __( "Last user attempted: %s", 'limit-login-attempts-reloaded' )
623
- . "\r\n\r\n", $user );
624
- }
625
- if ( $whitelisted ) {
626
- $message .= __( "IP was NOT blocked because of external whitelist.", 'limit-login-attempts-reloaded' );
627
- } else {
628
- $message .= sprintf( __( "IP was blocked for %s", 'limit-login-attempts-reloaded' ), $when );
629
- }
630
-
631
- $admin_email = $this->use_local_options ? get_option( 'admin_email' ) : get_site_option( 'admin_email' );
632
-
633
- //var_dump( $blogname, $subject ); exit;
634
- @wp_mail( $admin_email, $subject, $message );
635
- }
636
-
637
- /**
638
- * Logging of lockout (if configured)
639
- *
640
- * @param $user_login
641
- *
642
- * @internal param $user
643
- */
644
- public function notify_log( $user_login ) {
645
-
646
- if ( ! $user_login ) {
647
- return;
648
- }
649
-
650
- $log = $option = $this->get_option( 'logged' );
651
- if ( ! is_array( $log ) ) {
652
- $log = array();
653
- }
654
- $ip = $this->get_address();
655
-
656
- /* can be written much simpler, if you do not mind php warnings */
657
- if ( !isset( $log[ $ip ] ) )
658
- $log[ $ip ] = array();
659
-
660
- if ( !isset( $log[ $ip ][ $user_login ] ) )
661
- $log[ $ip ][ $user_login ] = array( 'counter' => 0 );
662
-
663
- elseif ( !is_array( $log[ $ip ][ $user_login ] ) )
664
- $log[ $ip ][ $user_login ] = array(
665
- 'counter' => $log[ $ip ][ $user_login ],
666
- );
667
-
668
- $log[ $ip ][ $user_login ]['counter']++;
669
- $log[ $ip ][ $user_login ]['date'] = time();
670
-
671
- if ( isset( $_POST['woocommerce-login-nonce'] ) ) {
672
- $gateway = 'WooCommerce';
673
- } elseif ( isset( $GLOBALS['wp_xmlrpc_server'] ) && is_object( $GLOBALS['wp_xmlrpc_server'] ) ) {
674
- $gateway = 'XMLRPC';
675
- } else {
676
- $gateway = 'WP Login';
677
- }
678
-
679
- $log[ $ip ][ $user_login ]['gateway'] = $gateway;
680
-
681
- if ( $option === false ) {
682
- $this->add_option( 'logged', $log );
683
- } else {
684
- $this->update_option( 'logged', $log );
685
- }
686
- }
687
-
688
- /**
689
- * Check if IP is whitelisted.
690
- *
691
- * This function allow external ip whitelisting using a filter. Note that it can
692
- * be called multiple times during the login process.
693
- *
694
- * Note that retries and statistics are still counted and notifications
695
- * done as usual for whitelisted ips , but no lockout is done.
696
- *
697
- * Example:
698
- * function my_ip_whitelist($allow, $ip) {
699
- * return ($ip == 'my-ip') ? true : $allow;
700
- * }
701
- * add_filter('limit_login_whitelist_ip', 'my_ip_whitelist', 10, 2);
702
- *
703
- * @param null $ip
704
- *
705
- * @return bool
706
- */
707
- public function is_ip_whitelisted( $ip = null ) {
708
-
709
- if ( is_null( $ip ) ) {
710
- $ip = $this->get_address();
711
- }
712
-
713
- $whitelisted = apply_filters( 'limit_login_whitelist_ip', false, $ip );
714
-
715
- return ( $whitelisted === true );
716
- }
717
-
718
- public function is_username_whitelisted( $username ) {
719
-
720
- if ( empty( $username ) ) {
721
- return false;
722
- }
723
-
724
- $whitelisted = apply_filters( 'limit_login_whitelist_usernames', false, $username );
725
-
726
- return ( $whitelisted === true );
727
- }
728
-
729
- public function is_ip_blacklisted( $ip = null ) {
730
-
731
- if ( is_null( $ip ) ) {
732
- $ip = $this->get_address();
733
- }
734
-
735
- $whitelisted = apply_filters( 'limit_login_blacklist_ip', false, $ip );
736
-
737
- return ( $whitelisted === true );
738
- }
739
-
740
- public function is_username_blacklisted( $username ) {
741
-
742
- if ( empty( $username ) ) {
743
- return false;
744
- }
745
-
746
- $whitelisted = apply_filters( 'limit_login_blacklist_usernames', false, $username );
747
-
748
- return ( $whitelisted === true );
749
- }
750
-
751
- /**
752
- * Filter: allow login attempt? (called from wp_authenticate())
753
- *
754
- * @param $user WP_User
755
- * @param $password
756
- *
757
- * @return \WP_Error
758
- */
759
- public function wp_authenticate_user( $user, $password ) {
760
-
761
- if ( is_wp_error( $user ) ||
762
- $this->check_whitelist_ips( false, $this->get_address() ) ||
763
- $this->check_whitelist_usernames( false, $user->user_login ) ||
764
- $this->is_limit_login_ok()
765
- ) {
766
-
767
- return $user;
768
- }
769
-
770
- $error = new WP_Error();
771
-
772
- global $limit_login_my_error_shown;
773
- $limit_login_my_error_shown = true;
774
-
775
- if ( $this->is_username_blacklisted( $user->user_login ) || $this->is_ip_blacklisted( $this->get_address() ) ) {
776
- $error->add( 'username_blacklisted', "<strong>ERROR:</strong> Too many failed login attempts." );
777
- } else {
778
- // This error should be the same as in "shake it" filter below
779
- $error->add( 'too_many_retries', $this->error_msg() );
780
- }
781
-
782
- return $error;
783
- }
784
-
785
- /**
786
- * Filter: add this failure to login page "Shake it!"
787
- *
788
- * @param $error_codes
789
- *
790
- * @return array
791
- */
792
- public function failure_shake( $error_codes ) {
793
- $error_codes[] = 'too_many_retries';
794
- $error_codes[] = 'username_blacklisted';
795
-
796
- return $error_codes;
797
- }
798
-
799
- /**
800
- * Keep track of if user or password are empty, to filter errors correctly
801
- *
802
- * @param $user
803
- * @param $password
804
- */
805
- public function track_credentials( $user, $password ) {
806
- global $limit_login_nonempty_credentials;
807
-
808
- $limit_login_nonempty_credentials = ( ! empty( $user ) && ! empty( $password ) );
809
- }
810
-
811
- /**
812
- * Should we show errors and messages on this page?
813
- *
814
- * @return bool
815
- */
816
- public function login_show_msg() {
817
- if ( isset( $_GET['key'] ) ) {
818
- /* reset password */
819
- return false;
820
- }
821
-
822
- $action = isset( $_REQUEST['action'] ) ? $_REQUEST['action'] : '';
823
-
824
- return ( $action != 'lostpassword' && $action != 'retrievepassword'
825
- && $action != 'resetpass' && $action != 'rp'
826
- && $action != 'register' );
827
- }
828
-
829
- /**
830
- * Construct informative error message
831
- *
832
- * @return string
833
- */
834
- public function error_msg() {
835
- $ip = $this->get_address();
836
- $lockouts = $this->get_option( 'lockouts' );
837
-
838
- $msg = __( '<strong>ERROR</strong>: Too many failed login attempts.', 'limit-login-attempts-reloaded' ) . ' ';
839
-
840
- if ( ! is_array( $lockouts ) || ! isset( $lockouts[ $ip ] ) || time() >= $lockouts[ $ip ] ) {
841
- /* Huh? No timeout active? */
842
- $msg .= __( 'Please try again later.', 'limit-login-attempts-reloaded' );
843
-
844
- return $msg;
845
- }
846
-
847
- $when = ceil( ( $lockouts[ $ip ] - time() ) / 60 );
848
- if ( $when > 60 ) {
849
- $when = ceil( $when / 60 );
850
- $msg .= sprintf( _n( 'Please try again in %d hour.', 'Please try again in %d hours.', $when, 'limit-login-attempts-reloaded' ), $when );
851
- } else {
852
- $msg .= sprintf( _n( 'Please try again in %d minute.', 'Please try again in %d minutes.', $when, 'limit-login-attempts-reloaded' ), $when );
853
- }
854
-
855
- return $msg;
856
- }
857
-
858
- /**
859
- * Add a message to login page when necessary
860
- */
861
- public function add_error_message() {
862
- global $error, $limit_login_my_error_shown;
863
-
864
- if ( ! $this->login_show_msg() || $limit_login_my_error_shown ) {
865
- return;
866
- }
867
-
868
- $msg = $this->get_message();
869
-
870
- if ( $msg != '' ) {
871
- $limit_login_my_error_shown = true;
872
- $error .= $msg;
873
- }
874
-
875
- return;
876
- }
877
-
878
- /**
879
- * Fix up the error message before showing it
880
- *
881
- * @param $content
882
- *
883
- * @return string
884
- */
885
- public function fixup_error_messages( $content ) {
886
- global $limit_login_just_lockedout, $limit_login_nonempty_credentials, $limit_login_my_error_shown;
887
-
888
- if ( ! $this->login_show_msg() ) {
889
- return $content;
890
- }
891
-
892
- /*
893
- * During lockout we do not want to show any other error messages (like
894
- * unknown user or empty password).
895
- */
896
- if ( ! $this->is_limit_login_ok() && ! $limit_login_just_lockedout ) {
897
- return $this->error_msg();
898
- }
899
-
900
- /*
901
- * We want to filter the messages 'Invalid username' and
902
- * 'Invalid password' as that is an information leak regarding user
903
- * account names (prior to WP 2.9?).
904
- *
905
- * Also, if more than one error message, put an extra <br /> tag between
906
- * them.
907
- */
908
- $msgs = explode( "<br />\n", $content );
909
-
910
- if ( strlen( end( $msgs ) ) == 0 ) {
911
- /* remove last entry empty string */
912
- array_pop( $msgs );
913
- }
914
-
915
- $count = count( $msgs );
916
- $my_warn_count = $limit_login_my_error_shown ? 1 : 0;
917
-
918
- if ( $limit_login_nonempty_credentials && $count > $my_warn_count ) {
919
- /* Replace error message, including ours if necessary */
920
- $content = __( '<strong>ERROR</strong>: Incorrect username or password.', 'limit-login-attempts-reloaded' ) . "<br />\n";
921
-
922
- if ( $limit_login_my_error_shown || $this->get_message() ) {
923
- $content .= "<br />\n" . $this->get_message() . "<br />\n";
924
- }
925
-
926
- return $content;
927
- } elseif ( $count <= 1 ) {
928
- return $content;
929
- }
930
-
931
- $new = '';
932
- while ( $count -- > 0 ) {
933
- $new .= array_shift( $msgs ) . "<br />\n";
934
- if ( $count > 0 ) {
935
- $new .= "<br />\n";
936
- }
937
- }
938
-
939
- return $new;
940
- }
941
-
942
- public function fixup_error_messages_wc( \WP_Error $error ) {
943
- $error->add( 1, __( 'WC Error' ) );
944
- }
945
-
946
- /**
947
- * Return current (error) message to show, if any
948
- *
949
- * @return string
950
- */
951
- public function get_message() {
952
- /* Check external whitelist */
953
- if ( $this->is_ip_whitelisted() ) {
954
- return '';
955
- }
956
-
957
- /* Is lockout in effect? */
958
- if ( ! $this->is_limit_login_ok() ) {
959
- return $this->error_msg();
960
- }
961
-
962
- return $this->retries_remaining_msg();
963
- }
964
-
965
- /**
966
- * Construct retries remaining message
967
- *
968
- * @return string
969
- */
970
- public function retries_remaining_msg() {
971
- $ip = $this->get_address();
972
- $retries = $this->get_option( 'retries' );
973
- $valid = $this->get_option( 'retries_valid' );
974
-
975
- /* Should we show retries remaining? */
976
-
977
- if ( ! is_array( $retries ) || ! is_array( $valid ) ) {
978
- /* no retries at all */
979
- return '';
980
- }
981
- if ( ! isset( $retries[ $ip ] ) || ! isset( $valid[ $ip ] ) || time() > $valid[ $ip ] ) {
982
- /* no: no valid retries */
983
- return '';
984
- }
985
- if ( ( $retries[ $ip ] % $this->get_option( 'allowed_retries' ) ) == 0 ) {
986
- /* no: already been locked out for these retries */
987
- return '';
988
- }
989
-
990
- $remaining = max( ( $this->get_option( 'allowed_retries' ) - ( $retries[ $ip ] % $this->get_option( 'allowed_retries' ) ) ), 0 );
991
-
992
- return sprintf( _n( "<strong>%d</strong> attempt remaining.", "<strong>%d</strong> attempts remaining.", $remaining, 'limit-login-attempts-reloaded' ), $remaining );
993
- }
994
-
995
- /**
996
- * Get correct remote address
997
- *
998
- * @param string $type_name
999
- *
1000
- * @return string
1001
- */
1002
- public function get_address( $type_name = '' ) {
1003
-
1004
- if ( !empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) )
1005
- return $_SERVER['HTTP_X_FORWARDED_FOR'];
1006
-
1007
- elseif ( !empty( $_SERVER['HTTP_X_SUCURI_CLIENTIP'] ) )
1008
- return $_SERVER['HTTP_X_SUCURI_CLIENTIP'];
1009
-
1010
- elseif ( isset( $_SERVER['REMOTE_ADDR'] ) )
1011
- return $_SERVER['REMOTE_ADDR'];
1012
-
1013
- else
1014
- return '';
1015
- }
1016
-
1017
- /**
1018
- * Clean up old lockouts and retries, and save supplied arrays
1019
- *
1020
- * @param null $retries
1021
- * @param null $lockouts
1022
- * @param null $valid
1023
- */
1024
- public function cleanup( $retries = null, $lockouts = null, $valid = null ) {
1025
- $now = time();
1026
- $lockouts = ! is_null( $lockouts ) ? $lockouts : $this->get_option( 'lockouts' );
1027
-
1028
- /* remove old lockouts */
1029
- if ( is_array( $lockouts ) ) {
1030
- foreach ( $lockouts as $ip => $lockout ) {
1031
- if ( $lockout < $now ) {
1032
- unset( $lockouts[ $ip ] );
1033
- }
1034
- }
1035
- $this->update_option( 'lockouts', $lockouts );
1036
- }
1037
-
1038
- /* remove retries that are no longer valid */
1039
- $valid = ! is_null( $valid ) ? $valid : $this->get_option( 'retries_valid' );
1040
- $retries = ! is_null( $retries ) ? $retries : $this->get_option( 'retries' );
1041
- if ( ! is_array( $valid ) || ! is_array( $retries ) ) {
1042
- return;
1043
- }
1044
-
1045
- foreach ( $valid as $ip => $lockout ) {
1046
- if ( $lockout < $now ) {
1047
- unset( $valid[ $ip ] );
1048
- unset( $retries[ $ip ] );
1049
- }
1050
- }
1051
-
1052
- /* go through retries directly, if for some reason they've gone out of sync */
1053
- foreach ( $retries as $ip => $retry ) {
1054
- if ( ! isset( $valid[ $ip ] ) ) {
1055
- unset( $retries[ $ip ] );
1056
- }
1057
- }
1058
-
1059
- $this->update_option( 'retries', $retries );
1060
- $this->update_option( 'retries_valid', $valid );
1061
- }
1062
-
1063
- /**
1064
- * Render admin options page
1065
- */
1066
- public function options_page() {
1067
- $this->use_local_options = !is_network_admin();
1068
- $this->cleanup();
1069
-
1070
- if( !empty( $_POST ) )
1071
- {
1072
- check_admin_referer( 'limit-login-attempts-options' );
1073
-
1074
- if ( is_network_admin() )
1075
- $this->update_option( 'allow_local_options', !empty($_POST['allow_local_options']) );
1076
-
1077
- elseif ( $this->network_mode )
1078
- $this->update_option( 'use_local_options', empty($_POST['use_global_options']) );
1079
-
1080
- /* Should we clear log? */
1081
- if( isset( $_POST[ 'clear_log' ] ) )
1082
- {
1083
- $this->update_option( 'logged', '' );
1084
- $this->show_error( __( 'Cleared IP log', 'limit-login-attempts-reloaded' ) );
1085
- }
1086
-
1087
- /* Should we reset counter? */
1088
- if( isset( $_POST[ 'reset_total' ] ) )
1089
- {
1090
- $this->update_option( 'lockouts_total', 0 );
1091
- $this->show_error( __( 'Reset lockout count', 'limit-login-attempts-reloaded' ) );
1092
- }
1093
-
1094
- /* Should we restore current lockouts? */
1095
- if( isset( $_POST[ 'reset_current' ] ) )
1096
- {
1097
- $this->update_option( 'lockouts', array() );
1098
- $this->show_error( __( 'Cleared current lockouts', 'limit-login-attempts-reloaded' ) );
1099
- }
1100
-
1101
- /* Should we update options? */
1102
- if( isset( $_POST[ 'update_options' ] ) )
1103
- {
1104
- $this->update_option('allowed_retries', (int)$_POST['allowed_retries'] );
1105
- $this->update_option('lockout_duration', (int)$_POST['lockout_duration'] * 60 );
1106
- $this->update_option('valid_duration', (int)$_POST['valid_duration'] * 3600 );
1107
- $this->update_option('allowed_lockouts', (int)$_POST['allowed_lockouts'] );
1108
- $this->update_option('long_duration', (int)$_POST['long_duration'] * 3600 );
1109
- $this->update_option('notify_email_after', (int)$_POST['email_after'] );
1110
-
1111
- $white_list_ips = ( !empty( $_POST['lla_whitelist_ips'] ) ) ? explode("\n", str_replace("\r", "", stripslashes($_POST['lla_whitelist_ips']) ) ) : array();
1112
-
1113
- if( !empty( $white_list_ips ) ) {
1114
- foreach( $white_list_ips as $key => $ip ) {
1115
- if( '' == $ip ) {
1116
- unset( $white_list_ips[ $key ] );
1117
- }
1118
- }
1119
- }
1120
- $this->update_option('whitelist', $white_list_ips );
1121
-
1122
- $white_list_usernames = ( !empty( $_POST['lla_whitelist_usernames'] ) ) ? explode("\n", str_replace("\r", "", stripslashes($_POST['lla_whitelist_usernames']) ) ) : array();
1123
-
1124
- if( !empty( $white_list_usernames ) ) {
1125
- foreach( $white_list_usernames as $key => $ip ) {
1126
- if( '' == $ip ) {
1127
- unset( $white_list_usernames[ $key ] );
1128
- }
1129
- }
1130
- }
1131
- $this->update_option('whitelist_usernames', $white_list_usernames );
1132
-
1133
- $black_list_ips = ( !empty( $_POST['lla_blacklist_ips'] ) ) ? explode("\n", str_replace("\r", "", stripslashes($_POST['lla_blacklist_ips']) ) ) : array();
1134
-
1135
- if( !empty( $black_list_ips ) ) {
1136
- foreach( $black_list_ips as $key => $ip ) {
1137
- if( '' == $ip ) {
1138
- unset( $black_list_ips[ $key ] );
1139
- }
1140
- }
1141
- }
1142
- $this->update_option('blacklist', $black_list_ips );
1143
-
1144
- $black_list_usernames = ( !empty( $_POST['lla_blacklist_usernames'] ) ) ? explode("\n", str_replace("\r", "", stripslashes($_POST['lla_blacklist_usernames']) ) ) : array();
1145
-
1146
- if( !empty( $black_list_usernames ) ) {
1147
- foreach( $black_list_usernames as $key => $ip ) {
1148
- if( '' == $ip ) {
1149
- unset( $black_list_usernames[ $key ] );
1150
- }
1151
- }
1152
- }
1153
- $this->update_option('blacklist_usernames', $black_list_usernames );
1154
-
1155
- $notify_methods = array();
1156
- if( isset( $_POST[ 'lockout_notify_log' ] ) ) {
1157
- $notify_methods[] = 'log';
1158
- }
1159
- if( isset( $_POST[ 'lockout_notify_email' ] ) ) {
1160
- $notify_methods[] = 'email';
1161
- }
1162
- $this->update_option('lockout_notify', implode( ',', $notify_methods ) );
1163
-
1164
- $this->sanitize_options();
1165
-
1166
- $this->show_error( __( 'Options saved.', 'limit-login-attempts-reloaded' ) );
1167
- }
1168
- }
1169
-
1170
- include_once( LLA_PLUGIN_DIR . '/views/options-page.php' );
1171
- }
1172
-
1173
- public function ajax_unlock()
1174
- {
1175
- check_ajax_referer('limit-login-unlock', 'sec');
1176
- $ip = (string)@$_POST['ip'];
1177
-
1178
- $lockouts = (array)$this->get_option('lockouts');
1179
-
1180
- if ( isset( $lockouts[ $ip ] ) )
1181
- {
1182
- unset( $lockouts[ $ip ] );
1183
- $this->update_option( 'lockouts', $lockouts );
1184
- }
1185
-
1186
- //save to log
1187
- $user_login = @(string)$_POST['username'];
1188
- $log = $this->get_option( 'logged' );
1189
-
1190
- if ( @$log[ $ip ][ $user_login ] )
1191
- {
1192
- if ( !is_array( $log[ $ip ][ $user_login ] ) )
1193
- $log[ $ip ][ $user_login ] = array(
1194
- 'counter' => $log[ $ip ][ $user_login ],
1195
- );
1196
- $log[ $ip ][ $user_login ]['unlocked'] = true;
1197
-
1198
- $this->update_option( 'logged', $log );
1199
- }
1200
-
1201
- header('Content-Type: application/json');
1202
- echo 'true';
1203
- exit;
1204
- }
1205
-
1206
- /**
1207
- * Show error message
1208
- *
1209
- * @param $msg
1210
- */
1211
- public function show_error( $msg ) {
1212
- LLA_Helpers::show_error( $msg );
1213
- }
1214
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1215
  }
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'] ) )
1041
+ $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
1042
+
1043
+ elseif ( !empty( $_SERVER['HTTP_X_SUCURI_CLIENTIP'] ) )
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
+ return $ip;
1054
+ }
1055
+
1056
+ /**
1057
+ * Clean up old lockouts and retries, and save supplied arrays
1058
+ *
1059
+ * @param null $retries
1060
+ * @param null $lockouts
1061
+ * @param null $valid
1062
+ */
1063
+ public function cleanup( $retries = null, $lockouts = null, $valid = null ) {
1064
+ $now = time();
1065
+ $lockouts = ! is_null( $lockouts ) ? $lockouts : $this->get_option( 'lockouts' );
1066
+
1067
+ /* remove old lockouts */
1068
+ if ( is_array( $lockouts ) ) {
1069
+ foreach ( $lockouts as $ip => $lockout ) {
1070
+ if ( $lockout < $now ) {
1071
+ unset( $lockouts[ $ip ] );
1072
+ }
1073
+ }
1074
+ $this->update_option( 'lockouts', $lockouts );
1075
+ }
1076
+
1077
+ /* remove retries that are no longer valid */
1078
+ $valid = ! is_null( $valid ) ? $valid : $this->get_option( 'retries_valid' );
1079
+ $retries = ! is_null( $retries ) ? $retries : $this->get_option( 'retries' );
1080
+ if ( ! is_array( $valid ) || ! is_array( $retries ) ) {
1081
+ return;
1082
+ }
1083
+
1084
+ foreach ( $valid as $ip => $lockout ) {
1085
+ if ( $lockout < $now ) {
1086
+ unset( $valid[ $ip ] );
1087
+ unset( $retries[ $ip ] );
1088
+ }
1089
+ }
1090
+
1091
+ /* go through retries directly, if for some reason they've gone out of sync */
1092
+ foreach ( $retries as $ip => $retry ) {
1093
+ if ( ! isset( $valid[ $ip ] ) ) {
1094
+ unset( $retries[ $ip ] );
1095
+ }
1096
+ }
1097
+
1098
+ $this->update_option( 'retries', $retries );
1099
+ $this->update_option( 'retries_valid', $valid );
1100
+ }
1101
+
1102
+ /**
1103
+ * Render admin options page
1104
+ */
1105
+ public function options_page() {
1106
+ $this->use_local_options = !is_network_admin();
1107
+ $this->cleanup();
1108
+
1109
+ if( !empty( $_POST ) )
1110
+ {
1111
+ check_admin_referer( 'limit-login-attempts-options' );
1112
+
1113
+ if ( is_network_admin() )
1114
+ $this->update_option( 'allow_local_options', !empty($_POST['allow_local_options']) );
1115
+
1116
+ elseif ( $this->network_mode )
1117
+ $this->update_option( 'use_local_options', empty($_POST['use_global_options']) );
1118
+
1119
+ /* Should we support GDPR */
1120
+ if( isset( $_POST[ 'gdpr' ] ) )
1121
+ {
1122
+ $this->update_option( 'gdpr', 1 );
1123
+ }
1124
+ else {
1125
+ $this->update_option( 'gdpr', 0 );
1126
+ }
1127
+
1128
+ /* Should we clear log? */
1129
+ if( isset( $_POST[ 'clear_log' ] ) )
1130
+ {
1131
+ $this->update_option( 'logged', '' );
1132
+ $this->show_error( __( 'Cleared IP log', 'limit-login-attempts-reloaded' ) );
1133
+ }
1134
+
1135
+ /* Should we reset counter? */
1136
+ if( isset( $_POST[ 'reset_total' ] ) )
1137
+ {
1138
+ $this->update_option( 'lockouts_total', 0 );
1139
+ $this->show_error( __( 'Reset lockout count', 'limit-login-attempts-reloaded' ) );
1140
+ }
1141
+
1142
+ /* Should we restore current lockouts? */
1143
+ if( isset( $_POST[ 'reset_current' ] ) )
1144
+ {
1145
+ $this->update_option( 'lockouts', array() );
1146
+ $this->show_error( __( 'Cleared current lockouts', 'limit-login-attempts-reloaded' ) );
1147
+ }
1148
+
1149
+ /* Should we update options? */
1150
+ if( isset( $_POST[ 'update_options' ] ) )
1151
+ {
1152
+ $this->update_option('allowed_retries', (int)$_POST['allowed_retries'] );
1153
+ $this->update_option('lockout_duration', (int)$_POST['lockout_duration'] * 60 );
1154
+ $this->update_option('valid_duration', (int)$_POST['valid_duration'] * 3600 );
1155
+ $this->update_option('allowed_lockouts', (int)$_POST['allowed_lockouts'] );
1156
+ $this->update_option('long_duration', (int)$_POST['long_duration'] * 3600 );
1157
+ $this->update_option('notify_email_after', (int)$_POST['email_after'] );
1158
+
1159
+ $white_list_ips = ( !empty( $_POST['lla_whitelist_ips'] ) ) ? explode("\n", str_replace("\r", "", stripslashes($_POST['lla_whitelist_ips']) ) ) : array();
1160
+
1161
+ if( !empty( $white_list_ips ) ) {
1162
+ foreach( $white_list_ips as $key => $ip ) {
1163
+ if( '' == $ip ) {
1164
+ unset( $white_list_ips[ $key ] );
1165
+ }
1166
+ }
1167
+ }
1168
+ $this->update_option('whitelist', $white_list_ips );
1169
+
1170
+ $white_list_usernames = ( !empty( $_POST['lla_whitelist_usernames'] ) ) ? explode("\n", str_replace("\r", "", stripslashes($_POST['lla_whitelist_usernames']) ) ) : array();
1171
+
1172
+ if( !empty( $white_list_usernames ) ) {
1173
+ foreach( $white_list_usernames as $key => $ip ) {
1174
+ if( '' == $ip ) {
1175
+ unset( $white_list_usernames[ $key ] );
1176
+ }
1177
+ }
1178
+ }
1179
+ $this->update_option('whitelist_usernames', $white_list_usernames );
1180
+
1181
+ $black_list_ips = ( !empty( $_POST['lla_blacklist_ips'] ) ) ? explode("\n", str_replace("\r", "", stripslashes($_POST['lla_blacklist_ips']) ) ) : array();
1182
+
1183
+ if( !empty( $black_list_ips ) ) {
1184
+ foreach( $black_list_ips as $key => $ip ) {
1185
+ $range = array_map('trim', explode('-', $ip) );
1186
+ if ( count( $range ) > 1 && (float)sprintf("%u",ip2long($range[0])) > (float)sprintf("%u",ip2long($range[1]))) {
1187
+ $this->show_error( __( 'The "'. $ip .'" IP range is invalid', 'limit-login-attempts-reloaded' ) );
1188
+ }
1189
+ if( '' == $ip ) {
1190
+ unset( $black_list_ips[ $key ] );
1191
+ }
1192
+ }
1193
+ }
1194
+ $this->update_option('blacklist', $black_list_ips );
1195
+
1196
+ $black_list_usernames = ( !empty( $_POST['lla_blacklist_usernames'] ) ) ? explode("\n", str_replace("\r", "", stripslashes($_POST['lla_blacklist_usernames']) ) ) : array();
1197
+
1198
+ if( !empty( $black_list_usernames ) ) {
1199
+ foreach( $black_list_usernames as $key => $ip ) {
1200
+ if( '' == $ip ) {
1201
+ unset( $black_list_usernames[ $key ] );
1202
+ }
1203
+ }
1204
+ }
1205
+ $this->update_option('blacklist_usernames', $black_list_usernames );
1206
+
1207
+ $notify_methods = array();
1208
+ if( isset( $_POST[ 'lockout_notify_log' ] ) ) {
1209
+ $notify_methods[] = 'log';
1210
+ }
1211
+ if( isset( $_POST[ 'lockout_notify_email' ] ) ) {
1212
+ $notify_methods[] = 'email';
1213
+ }
1214
+ $this->update_option('lockout_notify', implode( ',', $notify_methods ) );
1215
+
1216
+ $this->sanitize_options();
1217
+
1218
+ $this->show_error( __( 'Options saved.', 'limit-login-attempts-reloaded' ) );
1219
+ }
1220
+ }
1221
+
1222
+ include_once( LLA_PLUGIN_DIR . '/views/options-page.php' );
1223
+ }
1224
+
1225
+ public function ajax_unlock()
1226
+ {
1227
+ check_ajax_referer('limit-login-unlock', 'sec');
1228
+ $ip = (string)@$_POST['ip'];
1229
+
1230
+ $lockouts = (array)$this->get_option('lockouts');
1231
+
1232
+ if ( isset( $lockouts[ $ip ] ) )
1233
+ {
1234
+ unset( $lockouts[ $ip ] );
1235
+ $this->update_option( 'lockouts', $lockouts );
1236
+ }
1237
+
1238
+ //save to log
1239
+ $user_login = @(string)$_POST['username'];
1240
+ $log = $this->get_option( 'logged' );
1241
+
1242
+ if ( @$log[ $ip ][ $user_login ] )
1243
+ {
1244
+ if ( !is_array( $log[ $ip ][ $user_login ] ) )
1245
+ $log[ $ip ][ $user_login ] = array(
1246
+ 'counter' => $log[ $ip ][ $user_login ],
1247
+ );
1248
+ $log[ $ip ][ $user_login ]['unlocked'] = true;
1249
+
1250
+ $this->update_option( 'logged', $log );
1251
+ }
1252
+
1253
+ header('Content-Type: application/json');
1254
+ echo 'true';
1255
+ exit;
1256
+ }
1257
+
1258
+ /**
1259
+ * Show error message
1260
+ *
1261
+ * @param $msg
1262
+ */
1263
+ public function show_error( $msg ) {
1264
+ LLA_Helpers::show_error( $msg );
1265
+ }
1266
+
1267
+ /**
1268
+ * returns IP with its md5 value
1269
+ */
1270
+ private function getHash($str)
1271
+ {
1272
+ return md5($str);
1273
+ }
1274
+
1275
+ /**
1276
+ * @param $arr - array
1277
+ * @param $k - key
1278
+ * @return int array value at given index or zero
1279
+ */
1280
+ private function checkKey($arr, $k)
1281
+ {
1282
+ return isset($arr[$k]) ? $arr[$k] : 0;
1283
+ }
1284
  }
limit-login-attempts-reloaded.php CHANGED
@@ -4,7 +4,7 @@
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.6.3
8
 
9
  Copyright 2008 - 2012 Johan Eenfeldt, 2016 - 2017 WPChef
10
 
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.0
8
 
9
  Copyright 2008 - 2012 Johan Eenfeldt, 2016 - 2017 WPChef
10
 
readme.txt CHANGED
@@ -1,11 +1,11 @@
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.9.1
6
- Stable tag: 2.6.3
7
 
8
- Reloaded version of the original Limit Login Attempts plugin for Login Protection by a team of WordPress developers.
9
 
10
  == Description ==
11
 
@@ -25,6 +25,7 @@ Features:
25
  * **XMLRPC** gateway protection.
26
  * **Woocommerce** login page protection.
27
  * **Multi-site** compatibility with extra MU settings.
 
28
 
29
  = Upgrading from the old Limit Login Attempts plugin =
30
  1. Go to the Plugins section in your site's backend.
@@ -50,20 +51,29 @@ Based on the original code from Limit Login Attemps plugin by Johan Eenfeldt.
50
 
51
  == Changelog ==
52
 
 
 
 
 
 
 
 
 
 
53
  = 2.6.3 =
54
- Added support of Sucuri Website Firewall.
55
 
56
  = 2.6.2 =
57
- Fixed the issue with backslashes in usernames.
58
 
59
  = 2.6.1 =
60
- Plugin returns the 403 Forbidden header after the limit of login attempts via XMLRPC is reached.
61
 
62
- Added support of IP ranges in white/black lists.
63
 
64
- Lockouts now can be released selectively.
65
 
66
- Fixed the issue with encoding of special symbols in email notifications.
67
 
68
  = 2.5.0 =
69
  * Added Multi-site Compatibility and additional MU settings. https://wordpress.org/support/topic/multisite-compatibility-47/
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.5
6
+ Stable tag: 2.7.0
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
 
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.
51
 
52
  == Changelog ==
53
 
54
+ = 2.7.0 =
55
+ * GDPR compliance implemented.
56
+
57
+ * Fixed: ip_in_range() loop $ip overrides itself causing invalid results.
58
+ https://wordpress.org/support/topic/ip_in_range-loop-ip-overrides-itself-causing-invalid-results/
59
+
60
+ * Fixed: the plugin was locking out the same IP address multiple times, each with a different port.
61
+ https://wordpress.org/support/topic/same-ip-different-port/
62
+
63
  = 2.6.3 =
64
+ * Added support of Sucuri Website Firewall.
65
 
66
  = 2.6.2 =
67
+ * Fixed the issue with backslashes in usernames.
68
 
69
  = 2.6.1 =
70
+ * Plugin returns the 403 Forbidden header after the limit of login attempts via XMLRPC is reached.
71
 
72
+ * Added support of IP ranges in white/black lists.
73
 
74
+ * Lockouts now can be released selectively.
75
 
76
+ * Fixed the issue with encoding of special symbols in email notifications.
77
 
78
  = 2.5.0 =
79
  * Added Multi-site Compatibility and additional MU settings. https://wordpress.org/support/topic/multisite-compatibility-47/
views/options-page.php CHANGED
@@ -7,6 +7,8 @@ if( !defined( 'ABSPATH' ) )
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;
@@ -86,6 +88,14 @@ $black_list_usernames = ( is_array( $black_list_usernames ) && !empty( $black_li
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>
@@ -186,7 +196,9 @@ $black_list_usernames = ( is_array( $black_list_usernames ) && !empty( $black_li
186
  <?php foreach ( $log as $date => $user_info ) : ?>
187
  <tr>
188
  <td class="limit-login-date"><?php echo date_i18n( 'F d, Y H:i', $date ); ?></td>
189
- <td class="limit-login-ip"><?php echo $user_info['ip']; ?></td>
 
 
190
  <td class="limit-login-max"><?php echo $user_info['username'] . ' (' . $user_info['counter'] .' lockouts)'; ?></td>
191
  <td class="limit-login-gateway"><?php echo $user_info['gateway']; ?></td>
192
  <td>
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;
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>
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 $user_info['ip']; ?>
201
+ </td>
202
  <td class="limit-login-max"><?php echo $user_info['username'] . ' (' . $user_info['counter'] .' lockouts)'; ?></td>
203
  <td class="limit-login-gateway"><?php echo $user_info['gateway']; ?></td>
204
  <td>