Login LockDown - Version 1.82

Version Description

Download this release

Release Info

Developer WebFactory
Plugin Icon 128x128 Login LockDown
Version 1.82
Comparing to
See all releases

Code changes from version 1.8.1 to 1.82

Files changed (4) hide show
  1. index.php +2 -0
  2. loginlockdown.php +485 -522
  3. readme.txt +28 -18
  4. version.txt +0 -1
index.php ADDED
@@ -0,0 +1,2 @@
 
 
1
+ <?php
2
+ // Silence is golden.
loginlockdown.php CHANGED
@@ -1,113 +1,45 @@
1
  <?php
2
- /*
3
- Plugin Name: Login LockDown
4
- Plugin URI: http://www.bad-neighborhood.com/
5
- Version: v1.8.1
6
- Author: Michael VanDeMar
7
- Description: Adds some extra security to WordPress by restricting the rate at which failed logins can be re-attempted from a given IP range. Distributed through <a href="http://www.bad-neighborhood.com/" target="_blank">Bad Neighborhood</a>.
8
- */
9
-
10
- /*
11
- == Change Log ==
12
- *
13
- * ver. 1.8.1 30-Sep-2019
14
- * - adding missing ./languages folder
15
- *
16
- * ver. 1.8 30-Sep-2019
17
- * - fixed issues with internationalization, added .pot file
18
- * - changed the credit link to default to not showing
19
- *
20
- * ver. 1.7.1 13-Sep-2016
21
- * - fixed bug causing all ipv6 addresses to get locked out if 1 was
22
- * - added in WordPress MultiSite functionality
23
- * - fixed bug where subnets could be overly matched, causing more IPs to be blocked than intended
24
- * - moved the report for locked out IP addresses to its own tab
25
- *
26
- * ver. 1.6.1 8-Mar-2014
27
- * - fixed html glitch preventing options from being saved
28
- *
29
- * ver. 1.6 7-Mar-2014
30
- * - cleaned up deprecated functions
31
- * - fixed bug with invalid property on a non-object when locking out invalid usernames
32
- * - fixed utilization of $wpdb->prepare
33
- * - added more descriptive help text to each of the options
34
- * - added the ability to remove the "Login form protected by Login LockDown." message from within the dashboard
35
- *
36
- * ver. 1.5 17-Sep-2009
37
- * - implemented wp_nonce security in the options and lockdown release forms in the admin screen
38
- * - fixed a security hole with an improperly escaped SQL query
39
- * - encoded certain outputs in the admin panel using esc_attr() to prevent XSS attacks
40
- * - fixed an issue with the 'Lockout Invalid Usernames' option not functioning as intended
41
- *
42
- * ver. 1.4 29-Aug-2009
43
- * - removed erroneous error affecting WP 2.8+
44
- * - fixed activation error caused by customizing the location of the wp-content folder
45
- * - added in the option to mask which specific login error (invalid username or invalid password) was generated
46
- * - added in the option to lock out failed login attempts even if the username doesn't exist
47
- *
48
- * ver. 1.3 23-Feb-2009
49
- * - adjusted positioning of plugin byline
50
- * - allowed for dynamic location of plugin files
51
- *
52
- * ver. 1.2 15-Jun-2008
53
- * - now compatible with WordPress 2.5 and up only
54
- *
55
- * ver. 1.1 01-Sep-2007
56
- * - revised time query to MySQL 4.0 compatability
57
- *
58
- * ver. 1.0 29-Aug-2007
59
- * - released
60
- */
61
-
62
- /*
63
- == Installation ==
64
-
65
- 1. Extract the zip file into your plugins directory into its own folder.
66
- 2. Activate the plugin in the Plugin options.
67
- 3. Customize the settings from the Options panel, if desired.
68
-
69
- */
70
-
71
  /*
72
- /--------------------------------------------------------------------\
73
- | |
74
- | License: GPL |
75
- | |
76
- | Login LockDown - added security measures to WordPress intended to |
77
- | inhibit or reduce brute force password discovery. |
78
- | Copyright (C) 2007 - 2014, Michael VanDeMar, |
79
- | http://www.bad-neighborhood.com |
80
- | All rights reserved. |
81
- | |
82
- | This program is free software; you can redistribute it and/or |
83
- | modify it under the terms of the GNU General Public License |
84
- | as published by the Free Software Foundation; either version 2 |
85
- | of the License, or (at your option) any later version. |
86
- | |
87
- | This program is distributed in the hope that it will be useful, |
88
- | but WITHOUT ANY WARRANTY; without even the implied warranty of |
89
- | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
90
- | GNU General Public License for more details. |
91
- | |
92
- | You should have received a copy of the GNU General Public License |
93
- | along with this program; if not, write to the |
94
- | Free Software Foundation, Inc. |
95
- | 51 Franklin Street, Fifth Floor |
96
- | Boston, MA 02110-1301, USA |
97
- | |
98
- \--------------------------------------------------------------------/
99
  */
100
 
101
  $loginlockdown_db_version = "1.0";
102
- $loginlockdownOptions = get_loginlockdownOptions();
103
 
104
- function loginLockdown_install() {
105
- global $wpdb;
106
- global $loginlockdown_db_version;
107
- $table_name = $wpdb->prefix . "login_fails";
 
108
 
109
- if( $wpdb->get_var("SHOW TABLES LIKE '$table_name'") != $table_name ) {
110
- $sql = "CREATE TABLE " . $table_name . " (
111
  `login_attempt_ID` bigint(20) NOT NULL AUTO_INCREMENT,
112
  `user_id` bigint(20) NOT NULL,
113
  `login_attempt_date` datetime NOT NULL default '0000-00-00 00:00:00',
@@ -115,14 +47,14 @@ function loginLockdown_install() {
115
  PRIMARY KEY (`login_attempt_ID`)
116
  );";
117
 
118
- require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
119
- dbDelta($sql);
120
- }
121
 
122
- $table_name = $wpdb->prefix . "lockdowns";
123
 
124
- if( $wpdb->get_var("SHOW TABLES LIKE '$table_name'") != $table_name ) {
125
- $sql = "CREATE TABLE " . $table_name . " (
126
  `lockdown_ID` bigint(20) NOT NULL AUTO_INCREMENT,
127
  `user_id` bigint(20) NOT NULL,
128
  `lockdown_date` datetime NOT NULL default '0000-00-00 00:00:00',
@@ -131,450 +63,481 @@ function loginLockdown_install() {
131
  PRIMARY KEY (`lockdown_ID`)
132
  );";
133
 
134
- require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
135
- dbDelta($sql);
136
- }
137
- add_option("loginlockdown_db_version", "1.0", "", "no");
138
- // added in 1.6, cleanup from previously improperly set db versions
139
- delete_option( "loginlockdown_db1_version" );
140
- delete_option( "loginlockdown_db2_version" );
141
  }
142
 
143
- function countFails($username = "") {
144
- global $wpdb;
145
- global $loginlockdownOptions;
146
- $table_name = $wpdb->prefix . "login_fails";
147
- $subnet = calc_subnet($_SERVER['REMOTE_ADDR']);
148
-
149
- $numFailsquery = "SELECT COUNT(login_attempt_ID) FROM $table_name " .
150
- "WHERE login_attempt_date + INTERVAL " .
151
- $loginlockdownOptions['retries_within'] . " MINUTE > now() AND " .
152
- "login_attempt_IP LIKE '%s'";
153
- $numFailsquery = $wpdb->prepare( $numFailsquery, $subnet[1] . "%");
154
-
155
- $numFails = $wpdb->get_var($numFailsquery);
156
- return $numFails;
 
157
  }
158
 
159
- function incrementFails($username = "") {
160
- global $wpdb;
161
- global $loginlockdownOptions;
162
- $table_name = $wpdb->prefix . "login_fails";
163
- $subnet = calc_subnet($_SERVER['REMOTE_ADDR']);
164
-
165
- $username = sanitize_user($username);
166
- $user = get_user_by('login',$username);
167
- if ( $user || "yes" == $loginlockdownOptions['lockout_invalid_usernames'] ) {
168
- if ( $user === false ) {
169
- $user_id = -1;
170
- } else {
171
- $user_id = $user->ID;
172
- }
173
- $insert = "INSERT INTO " . $table_name . " (user_id, login_attempt_date, login_attempt_IP) " .
174
- "VALUES ('" . $user_id . "', now(), '%s')";
175
- $insert = $wpdb->prepare( $insert, $subnet[0] );
176
- $results = $wpdb->query($insert);
177
- }
 
 
 
 
 
 
178
  }
179
 
180
- function lockDown($username = "") {
181
- global $wpdb;
182
- global $loginlockdownOptions;
183
- $table_name = $wpdb->prefix . "lockdowns";
184
- $subnet = calc_subnet($_SERVER['REMOTE_ADDR']);
185
-
186
- $username = sanitize_user($username);
187
- $user = get_user_by('login',$username);
188
- if ( $user || "yes" == $loginlockdownOptions['lockout_invalid_usernames'] ) {
189
- if ( $user === false ) {
190
- $user_id = -1;
191
- } else {
192
- $user_id = $user->ID;
193
- }
194
- $insert = "INSERT INTO " . $table_name . " (user_id, lockdown_date, release_date, lockdown_IP) " .
195
- "VALUES ('" . $user_id . "', now(), date_add(now(), INTERVAL " .
196
- $loginlockdownOptions['lockout_length'] . " MINUTE), '%s')";
197
- $insert = $wpdb->prepare( $insert, $subnet[0] );
198
- $results = $wpdb->query($insert);
199
- }
 
 
 
 
 
 
200
  }
201
 
202
- function isLockedDown() {
203
- global $wpdb;
204
- $table_name = $wpdb->prefix . "lockdowns";
205
- $subnet = calc_subnet($_SERVER['REMOTE_ADDR']);
206
-
207
- $stillLockedquery = "SELECT user_id FROM $table_name " .
208
- "WHERE release_date > now() AND " .
209
- "lockdown_IP LIKE %s";
210
- $stillLockedquery = $wpdb->prepare($stillLockedquery,$subnet[1] . "%");
211
 
212
- $stillLocked = $wpdb->get_var($stillLockedquery);
213
 
214
- return $stillLocked;
215
  }
216
 
217
- function listLockedDown() {
218
- global $wpdb;
219
- $table_name = $wpdb->prefix . "lockdowns";
 
220
 
221
- $listLocked = $wpdb->get_results("SELECT lockdown_ID, floor((UNIX_TIMESTAMP(release_date)-UNIX_TIMESTAMP(now()))/60) AS minutes_left, ".
222
- "lockdown_IP FROM $table_name WHERE release_date > now()", ARRAY_A);
 
 
 
 
223
 
224
- return $listLocked;
225
  }
226
 
227
- function get_loginlockdownOptions() {
228
- $loginlockdownAdminOptions = array(
229
- 'max_login_retries' => 3,
230
- 'retries_within' => 5,
231
- 'lockout_length' => 60,
232
- 'lockout_invalid_usernames' => 'no',
233
- 'mask_login_errors' => 'no',
234
- 'show_credit_link' => 'no'
235
- );
236
- $loginlockdownOptions = get_option("loginlockdownAdminOptions");
237
- if ( !empty($loginlockdownOptions) ) {
238
- foreach ( $loginlockdownOptions as $key => $option ) {
239
- $loginlockdownAdminOptions[$key] = $option;
240
- }
241
- }
242
- update_option("loginlockdownAdminOptions", $loginlockdownAdminOptions);
243
- return $loginlockdownAdminOptions;
 
244
  }
245
 
246
- function calc_subnet($ip) {
247
- $subnet[0] = $ip;
248
- if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false) {
249
- $ip = expandipv6($ip);
250
- preg_match("/^([0-9abcdef]{1,4}:){4}/", $ip, $matches);
251
- $subnet[0] = $ip;
252
- $subnet[1] = $matches[0];
253
- } else {
254
- $subnet[1] = substr ($ip, 0 , strrpos ( $ip, "." ) + 1);
255
- }
256
- return $subnet;
 
257
  }
258
 
259
- function expandipv6($ip){
260
- $hex = unpack("H*hex", inet_pton($ip));
261
- $ip = substr(preg_replace("/([A-f0-9]{4})/", "$1:", $hex['hex']), 0, -1);
 
262
 
263
- return $ip;
264
  }
265
 
266
 
267
- function print_loginlockdownAdminPage() {
268
- global $wpdb;
269
- $table_name = $wpdb->prefix . "lockdowns";
270
- $loginlockdownAdminOptions = get_loginlockdownOptions();
271
-
272
- if (isset($_POST['update_loginlockdownSettings'])) {
273
-
274
- //wp_nonce check
275
- check_admin_referer('login-lockdown_update-options');
276
-
277
- if (isset($_POST['ll_max_login_retries'])) {
278
- $loginlockdownAdminOptions['max_login_retries'] = $_POST['ll_max_login_retries'];
279
- }
280
- if (isset($_POST['ll_retries_within'])) {
281
- $loginlockdownAdminOptions['retries_within'] = $_POST['ll_retries_within'];
282
- }
283
- if (isset($_POST['ll_lockout_length'])) {
284
- $loginlockdownAdminOptions['lockout_length'] = $_POST['ll_lockout_length'];
285
- }
286
- if (isset($_POST['ll_lockout_invalid_usernames'])) {
287
- $loginlockdownAdminOptions['lockout_invalid_usernames'] = $_POST['ll_lockout_invalid_usernames'];
288
- }
289
- if (isset($_POST['ll_mask_login_errors'])) {
290
- $loginlockdownAdminOptions['mask_login_errors'] = $_POST['ll_mask_login_errors'];
291
- }
292
- if (isset($_POST['ll_show_credit_link'])) {
293
- $loginlockdownAdminOptions['show_credit_link'] = $_POST['ll_show_credit_link'];
294
- }
295
- update_option("loginlockdownAdminOptions", $loginlockdownAdminOptions);
296
- ?>
297
- <div class="updated"><p><strong><?php _e("Settings Updated.", "loginlockdown");?></strong></p></div>
298
- <?php
299
- }
300
- if (isset($_POST['release_lockdowns'])) {
301
-
302
- //wp_nonce check
303
- check_admin_referer('login-lockdown_release-lockdowns');
304
-
305
- if (isset($_POST['releaseme'])) {
306
- $released = $_POST['releaseme'];
307
- foreach ( $released as $release_id ) {
308
- $releasequery = "UPDATE $table_name SET release_date = now() " .
309
- "WHERE lockdown_ID = '%d'";
310
- $releasequery = $wpdb->prepare($releasequery,$release_id);
311
- $results = $wpdb->query($releasequery);
312
- }
313
- }
314
- update_option("loginlockdownAdminOptions", $loginlockdownAdminOptions);
315
- ?>
316
- <div class="updated"><p><strong><?php _e("Lockdowns Released.", "loginlockdown");?></strong></p></div>
317
- <?php
318
- }
319
- $dalist = listLockedDown();
320
- ?>
321
- <div class="wrap">
322
- <?php
323
-
324
- $active_tab = isset( $_GET[ 'tab' ] ) ? $_GET[ 'tab' ] : 'settings';
325
-
326
- ?>
327
- <h2><?php _e('Login LockDown Options', 'loginlockdown') ?></h2>
328
-
329
- <h2 class="nav-tab-wrapper">
330
- <a href="?page=loginlockdown.php&tab=settings" class="nav-tab <?php echo $active_tab == 'settings' ? 'nav-tab-active' : ''; ?>"><?php _e('Settings', 'loginlockdown') ?></a>
331
- <a href="?page=loginlockdown.php&tab=activity" class="nav-tab <?php echo $active_tab == 'activity' ? 'nav-tab-active' : ''; ?>"><?php _e('Activity', 'loginlockdown') ?> (<?php echo count($dalist); ?>)</a>
332
- </h2>
333
- <?php if ( $active_tab == 'settings' ) { ?>
334
- <form method="post" action="<?php echo esc_attr($_SERVER["REQUEST_URI"]); ?>">
335
- <?php
336
- if ( function_exists('wp_nonce_field') )
337
- wp_nonce_field('login-lockdown_update-options');
338
- ?>
339
-
340
- <h3><?php _e('Max Login Retries', 'loginlockdown') ?></h3>
341
- <p><?php _e('Number of failed login attempts within the "Retry Time Period Restriction" (defined below) needed to trigger a LockDown.', 'loginlockdown') ?></p>
342
- <p><input type="text" name="ll_max_login_retries" size="8" value="<?php echo esc_attr($loginlockdownAdminOptions['max_login_retries']); ?>"></p>
343
- <h3><?php _e('Retry Time Period Restriction (minutes)', 'loginlockdown') ?></h3>
344
- <p><?php _e('Amount of time that determines the rate at which failed login attempts are allowed before a LockDown occurs.', 'loginlockdown') ?></p>
345
- <p><input type="text" name="ll_retries_within" size="8" value="<?php echo esc_attr($loginlockdownAdminOptions['retries_within']); ?>"></p>
346
- <h3><?php _e('Lockout Length (minutes)', 'loginlockdown') ?></h3>
347
- <p><?php _e('How long a particular IP block will be locked out for once a LockDown has been triggered.', 'loginlockdown') ?></p>
348
- <p><input type="text" name="ll_lockout_length" size="8" value="<?php echo esc_attr($loginlockdownAdminOptions['lockout_length']); ?>"></p>
349
- <h3><?php _e('Lockout Invalid Usernames?', 'loginlockdown') ?></h3>
350
- <p><?php _e('By default Login LockDown will not trigger if an attempt is made to log in using a username that does not exist. You can override this behavior here.', 'loginlockdown') ?></p>
351
- <p><input type="radio" name="ll_lockout_invalid_usernames" value="yes" <?php if( $loginlockdownAdminOptions['lockout_invalid_usernames'] == "yes" ) echo "checked"; ?>>&nbsp;<?php _e('Yes', 'loginlockdown') ?>&nbsp;&nbsp;&nbsp;<input type="radio" name="ll_lockout_invalid_usernames" value="no" <?php if( $loginlockdownAdminOptions['lockout_invalid_usernames'] == "no" ) echo "checked"; ?>>&nbsp;<?php _e('No', 'loginlockdown') ?></p>
352
- <h3><?php _e('Mask Login Errors?', 'loginlockdown') ?></h3>
353
- <p><?php _e('WordPress will normally display distinct messages to the user depending on whether they try and log in with an invalid username, or with a valid username but the incorrect password. Toggling this option will hide why the login failed.', 'loginlockdown') ?></p>
354
- <p><input type="radio" name="ll_mask_login_errors" value="yes" <?php if( $loginlockdownAdminOptions['mask_login_errors'] == "yes" ) echo "checked"; ?>>&nbsp;<?php _e('Yes', 'loginlockdown') ?>&nbsp;&nbsp;&nbsp;<input type="radio" name="ll_mask_login_errors" value="no" <?php if( $loginlockdownAdminOptions['mask_login_errors'] == "no" ) echo "checked"; ?>>&nbsp;<?php _e('No', 'loginlockdown') ?></p>
355
- <h3><?php _e('Show Credit Link?', 'loginlockdown') ?></h3>
356
- <p><?php _e('If enabled, Login LockDown will display the following message on the login form', 'loginlockdown') ?>:<br />
357
- <blockquote><?php _e('Login form protected by', 'loginlockdown') ?> <a href='http://www.bad-neighborhood.com/login-lockdown.html'>Login LockDown</a>.</blockquote>
358
- <?php _e('This helps others know about the plugin so they can protect their blogs as well if they like. You can enable or disable this message below', 'loginlockdown') ?>:</p>
359
- <input type="radio" name="ll_show_credit_link" value="yes" <?php if( $loginlockdownAdminOptions['show_credit_link'] == "yes" || $loginlockdownAdminOptions['show_credit_link'] == "" ) echo "checked"; ?>>&nbsp;<?php _e('Yes, display the credit link.', 'loginlockdown') ?><br />
360
- <input type="radio" name="ll_show_credit_link" value="shownofollow" <?php if( $loginlockdownAdminOptions['show_credit_link'] == "shownofollow" ) echo "checked"; ?>>&nbsp;<?php _e('Display the credit link, but add "rel=\'nofollow\'" (ie. do not pass any link juice).', 'loginlockdown') ?><br />
361
- <input type="radio" name="ll_show_credit_link" value="no" <?php if( $loginlockdownAdminOptions['show_credit_link'] == "no" ) echo "checked"; ?>>&nbsp;<?php _e('No, do not display the credit link.', 'loginlockdown') ?><br />
362
- <div class="submit">
363
- <input type="submit" class="button button-primary" name="update_loginlockdownSettings" value="<?php _e('Update Settings', 'loginlockdown') ?>" /></div>
364
- </form>
365
- <?php } else { ?>
366
- <form method="post" action="<?php echo esc_attr($_SERVER["REQUEST_URI"]); ?>">
367
- <?php
368
- if ( function_exists('wp_nonce_field') )
369
- wp_nonce_field('login-lockdown_release-lockdowns');
370
- ?>
371
- <h3><?php
372
- if( count($dalist) == 1 ) {
373
- printf( esc_html__( 'There is currently %d locked out IP address.', 'loginlockdown' ), count($dalist) );
374
-
375
- } else {
376
- printf( esc_html__( 'There are currently %d locked out IP addresses.', 'loginlockdown' ), count($dalist) );
377
- } ?></h3>
378
-
379
- <?php
380
- $num_lockedout = count($dalist);
381
- if( 0 == $num_lockedout ) {
382
- echo "<p>No IP blocks currently locked out.</p>";
383
- } else {
384
- foreach ( $dalist as $key => $option ) {
385
- ?>
386
- <li><input type="checkbox" name="releaseme[]" value="<?php echo esc_attr($option['lockdown_ID']); ?>"> <?php echo esc_attr($option['lockdown_IP']); ?> (<?php echo esc_attr($option['minutes_left']); ?> <?php _e('minutes left', 'loginlockdown') ?>)</li>
387
- <?php
388
- }
389
- }
390
- ?>
391
- <div class="submit">
392
- <input type="submit" class="button button-primary" name="release_lockdowns" value="<?php _e('Release Selected', 'loginlockdown') ?>" /></div>
393
- </form>
394
- <?php } ?>
395
- </div>
 
 
 
 
 
 
396
  <?php
397
- }//End function print_loginlockdownAdminPage()
398
 
399
- function loginlockdown_ap() {
400
- if ( function_exists('add_options_page') ) {
401
- add_options_page('Login LockDown', 'Login LockDown', 'manage_options', basename(__FILE__), 'print_loginlockdownAdminPage');
402
- }
 
403
  }
404
 
405
- function ll_credit_link(){
406
- global $loginlockdownOptions;
407
- $thispage = "http://" . $_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"];
408
- $homepage = get_option( "home" );
409
- $showcreditlink = $loginlockdownOptions['show_credit_link'];
410
- $relnofollow = "rel='nofollow'";
411
- if ( $showcreditlink != "shownofollow" && ($thispage == $homepage || $thispage == $homepage . "/" || substr($_SERVER["REQUEST_URI"], strlen($_SERVER["REQUEST_URI"]) - 12) == "wp-login.php") ) {
412
- $relnofollow = "";
413
- }
414
- if ( $showcreditlink != "no" ) {
415
- echo "<p>";
416
- _e('Login form protected by', 'loginlockdown');
417
- echo " <a href='http://www.bad-neighborhood.com/login-lockdown.html' $relnofollow>Login LockDown</a>.<br /><br /><br /></p>";
418
- }
 
419
  }
420
 
421
- //Actions and Filters
422
- if ( isset($loginlockdown_db_version) ) {
423
- //Actions
424
- add_action('admin_menu', 'loginlockdown_ap');
425
- if(!defined('WP_PLUGIN_DIR')){
426
- define('WP_PLUGIN_DIR', ABSPATH . 'wp-content/plugins');
427
- }
428
- $activatestr = str_replace(WP_PLUGIN_DIR . "/", "activate_", __FILE__);
429
- add_action($activatestr, 'loginLockdown_install');
430
- add_action('login_form', 'll_credit_link');
431
-
432
- remove_filter('authenticate', 'wp_authenticate_username_password', 20, 3);
433
- add_filter('authenticate', 'll_wp_authenticate_username_password', 20, 3);
434
- //Filters
435
- //Functions
436
- function ll_wp_authenticate_username_password($user, $username, $password) {
437
- if ( is_a($user, 'WP_User') ) { return $user; }
438
-
439
- if ( empty($username) || empty($password) ) {
440
- $error = new WP_Error();
441
-
442
- if ( empty($username) )
443
- $error->add('empty_username', __('<strong>ERROR</strong>: The username field is empty.', 'loginlockdown'));
444
-
445
- if ( empty($password) )
446
- $error->add('empty_password', __('<strong>ERROR</strong>: The password field is empty.', 'loginlockdown'));
447
-
448
- return $error;
449
- }
450
-
451
- $userdata = get_user_by('login',$username);
452
-
453
- if ( !$userdata ) {
454
- return new WP_Error('invalid_username', sprintf(__('<strong>ERROR</strong>: Invalid username. <a href="%s" title="Password Lost and Found">Lost your password</a>?', 'loginlockdown'), site_url('wp-login.php?action=lostpassword', 'login')));
455
- }
456
-
457
- $userdata = apply_filters('wp_authenticate_user', $userdata, $password);
458
- if ( is_wp_error($userdata) ) {
459
- return $userdata;
460
- }
461
-
462
- if ( !wp_check_password($password, $userdata->user_pass, $userdata->ID) ) {
463
- return new WP_Error('incorrect_password', sprintf(__('<strong>ERROR</strong>: Incorrect password. <a href="%s" title="Password Lost and Found">Lost your password</a>?', 'loginlockdown'), site_url('wp-login.php?action=lostpassword', 'login')));
464
- }
465
-
466
- $user = new WP_User($userdata->ID);
467
- return $user;
468
- }
469
-
470
-
471
- if ( !function_exists('wp_authenticate') ) :
472
- function wp_authenticate($username, $password) {
473
- global $wpdb, $error;
474
- global $loginlockdownOptions;
475
-
476
- $username = sanitize_user($username);
477
- $password = trim($password);
478
-
479
- if ( "" != isLockedDown() ) {
480
- return new WP_Error('incorrect_password', __("<strong>ERROR</strong>: We're sorry, but this IP range has been blocked due to too many recent failed login attempts.<br /><br />Please try again later.", 'loginlockdown'));
481
- }
482
-
483
- $user = apply_filters('authenticate', null, $username, $password);
484
-
485
- if ( $user == null ) {
486
- // TODO what should the error message be? (Or would these even happen?)
487
- // Only needed if all authentication handlers fail to return anything.
488
- $user = new WP_Error('authentication_failed', __('<strong>ERROR</strong>: Invalid username or incorrect password.', 'loginlockdown'));
489
- }
490
-
491
- $ignore_codes = array('empty_username', 'empty_password');
492
-
493
- if (is_wp_error($user) && !in_array($user->get_error_code(), $ignore_codes) ) {
494
- incrementFails($username);
495
- if ( $loginlockdownOptions['max_login_retries'] <= countFails($username) ) {
496
- lockDown($username);
497
- return new WP_Error('incorrect_password', __("<strong>ERROR</strong>: We're sorry, but this IP range has been blocked due to too many recent failed login attempts.<br /><br />Please try again later.", 'loginlockdown'));
498
- }
499
- if ( 'yes' == $loginlockdownOptions['mask_login_errors'] ) {
500
- return new WP_Error('authentication_failed', sprintf(__('<strong>ERROR</strong>: Invalid username or incorrect password. <a href="%s" title="Password Lost and Found">Lost your password</a>?', 'loginlockdown'), site_url('wp-login.php?action=lostpassword', 'login')));
501
- } else {
502
- do_action('wp_login_failed', $username);
503
- }
504
- }
505
-
506
- return $user;
507
- }
508
- endif;
509
- // multisite network-wide activation
510
- register_activation_hook( __FILE__, 'loginlockdown_multisite_activate' );
511
- function loginlockdown_multisite_activate($networkwide) {
512
- global $wpdb;
513
-
514
- if (function_exists('is_multisite') && is_multisite()) {
515
- // check if it is a network activation - if so, run the activation function for each blog id
516
- if ($networkwide) {
517
- $old_blog = $wpdb->blogid;
518
- // Get all blog ids
519
- $blogids = $wpdb->get_col("SELECT blog_id FROM $wpdb->blogs");
520
- foreach ($blogids as $blog_id) {
521
- switch_to_blog($blog_id);
522
- loginLockdown_install();
523
- }
524
- switch_to_blog($old_blog);
525
- return;
526
- }
527
- }
528
- }
529
-
530
- // multisite new site activation
531
- add_action( 'wpmu_new_blog', 'loginlockdown_multisite_newsite', 10, 6);
532
- function loginlockdown_multisite_newsite($blog_id, $user_id, $domain, $path, $site_id, $meta ) {
533
- global $wpdb;
534
-
535
- if (is_plugin_active_for_network('loginlockdown/loginlockdown.php')) {
536
- $old_blog = $wpdb->blogid;
537
- switch_to_blog($blog_id);
538
- loginLockdown_install();
539
- switch_to_blog($old_blog);
540
- }
541
- }
542
-
543
- // multisite old sites check
544
-
545
- add_action('admin_init','loginlockdown_multisite_legacy');
546
- function loginlockdown_multisite_legacy() {
547
- $loginlockdownMSRunOnce = get_option("loginlockdownmsrunonce");
548
- if ( empty($loginlockdownMSRunOnce) ) {
549
- global $wpdb;
550
-
551
- if (function_exists('is_multisite') && is_multisite()) {
552
-
553
- $old_blog = $wpdb->blogid;
554
-
555
- // Get all blog ids
556
- $blogids = $wpdb->get_col("SELECT blog_id FROM $wpdb->blogs");
557
- foreach ($blogids as $blog_id) {
558
-
559
- // check if already exists
560
- $bed_check = $wpdb->query("SHOW TABLES LIKE '{$wpdb->base_prefix}{$blog_id}_login_fails'");
561
- if (!$bed_check) {
562
-
563
- switch_to_blog($blog_id);
564
- loginLockdown_install();
565
-
566
- }
567
- }
568
- switch_to_blog($old_blog);
569
- }
570
- add_option("loginlockdownmsrunonce", "done", "", "no");
571
- return;
572
- }
573
- }
 
 
 
 
 
574
  }
575
 
576
- add_action('plugins_loaded', 'loginlockdown_init', 10);
577
 
578
- function loginlockdown_init() {
579
- load_plugin_textdomain( 'loginlockdown', false, dirname(plugin_basename(__FILE__)).'/languages/' );
 
580
  }
1
  <?php
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  /*
3
+ Plugin Name: Login LockDown
4
+ Plugin URI: https://wploginlockdown.com/
5
+ Description: Protect login form by banning IP after multiple failed login attempts.
6
+ Version: 1.82
7
+ Author: WebFactory Ltd
8
+ Author URI: https://www.webfactoryltd.com/
9
+ License: GNU General Public License v3.0
10
+ Text Domain: login-lockdown
11
+ Requires at least: 4.0
12
+ Tested up to: 6.0
13
+ Requires PHP: 5.2
14
+
15
+ Copyright 2022 - 2023 WebFactory Ltd (email: support@webfactoryltd.com)
16
+ Copyright 2007 - 2022 Michael VanDeMar
17
+
18
+ This program is free software; you can redistribute it and/or modify
19
+ it under the terms of the GNU General Public License, version 2, as
20
+ published by the Free Software Foundation.
21
+
22
+ This program is distributed in the hope that it will be useful,
23
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
24
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25
+ GNU General Public License for more details.
26
+
27
+ You should have received a copy of the GNU General Public License
28
+ along with this program; if not, write to the Free Software
29
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
30
  */
31
 
32
  $loginlockdown_db_version = "1.0";
33
+ $loginlockdownOptions = loginLockdown_get_options();
34
 
35
+ function loginLockdown_install()
36
+ {
37
+ global $wpdb;
38
+ global $loginlockdown_db_version;
39
+ $table_name = $wpdb->prefix . "login_fails";
40
 
41
+ if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") != $table_name) {
42
+ $sql = "CREATE TABLE " . $table_name . " (
43
  `login_attempt_ID` bigint(20) NOT NULL AUTO_INCREMENT,
44
  `user_id` bigint(20) NOT NULL,
45
  `login_attempt_date` datetime NOT NULL default '0000-00-00 00:00:00',
47
  PRIMARY KEY (`login_attempt_ID`)
48
  );";
49
 
50
+ require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
51
+ dbDelta($sql);
52
+ }
53
 
54
+ $table_name = $wpdb->prefix . "lockdowns";
55
 
56
+ if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") != $table_name) {
57
+ $sql = "CREATE TABLE " . $table_name . " (
58
  `lockdown_ID` bigint(20) NOT NULL AUTO_INCREMENT,
59
  `user_id` bigint(20) NOT NULL,
60
  `lockdown_date` datetime NOT NULL default '0000-00-00 00:00:00',
63
  PRIMARY KEY (`lockdown_ID`)
64
  );";
65
 
66
+ require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
67
+ dbDelta($sql);
68
+ }
69
+ add_option("loginlockdown_db_version", "1.0", "", "no");
70
+ // added in 1.6, cleanup from previously improperly set db versions
71
+ delete_option("loginlockdown_db1_version");
72
+ delete_option("loginlockdown_db2_version");
73
  }
74
 
75
+ function loginLockdown_countFails($username = "")
76
+ {
77
+ global $wpdb;
78
+ global $loginlockdownOptions;
79
+ $table_name = $wpdb->prefix . "login_fails";
80
+ $subnet = loginLockdown_calc_subnet($_SERVER['REMOTE_ADDR']);
81
+
82
+ $numFails = $wpdb->get_var(
83
+ $wpdb->prepare(
84
+ "SELECT COUNT(login_attempt_ID) FROM " . $table_name . " WHERE login_attempt_date + INTERVAL %d MINUTE > %s AND login_attempt_IP LIKE %s",
85
+ array($loginlockdownOptions['retries_within'], current_time('mysql', true), $subnet[1] . "%")
86
+ )
87
+ );
88
+
89
+ return $numFails;
90
  }
91
 
92
+ function loginLockdown_incrementFails($username = "")
93
+ {
94
+ global $wpdb;
95
+ global $loginlockdownOptions;
96
+ $table_name = $wpdb->prefix . "login_fails";
97
+ $subnet = loginLockdown_calc_subnet($_SERVER['REMOTE_ADDR']);
98
+
99
+ $username = sanitize_user($username);
100
+ $user = get_user_by('login', $username);
101
+ if ($user || "yes" == $loginlockdownOptions['lockout_invalid_usernames']) {
102
+ if ($user === false) {
103
+ $user_id = -1;
104
+ } else {
105
+ $user_id = $user->ID;
106
+ }
107
+
108
+ $wpdb->insert(
109
+ $table_name,
110
+ array(
111
+ 'user_id' => $user_id,
112
+ 'login_attempt_date' => current_time('mysql', false),
113
+ 'login_attempt_IP' => $subnet[0]
114
+ )
115
+ );
116
+ }
117
  }
118
 
119
+ function loginLockdown_lockDown($username = "")
120
+ {
121
+ global $wpdb;
122
+ global $loginlockdownOptions;
123
+ $table_name = $wpdb->prefix . "lockdowns";
124
+ $subnet = loginLockdown_calc_subnet($_SERVER['REMOTE_ADDR']);
125
+
126
+ $username = sanitize_user($username);
127
+ $user = get_user_by('login', $username);
128
+ if ($user || "yes" == $loginlockdownOptions['lockout_invalid_usernames']) {
129
+ if ($user === false) {
130
+ $user_id = -1;
131
+ } else {
132
+ $user_id = $user->ID;
133
+ }
134
+
135
+ $wpdb->insert(
136
+ $table_name,
137
+ array(
138
+ 'user_id' => $user_id,
139
+ 'lockdown_date' => current_time('mysql', false),
140
+ 'release_date' => date('Y-m-d H:i:s', strtotime(current_time('mysql', false)) + $loginlockdownOptions['lockout_length'] * 60),
141
+ 'lockdown_IP' => $subnet[0]
142
+ )
143
+ );
144
+ }
145
  }
146
 
147
+ function loginLockdown_isLockedDown()
148
+ {
149
+ global $wpdb;
150
+ $table_name = $wpdb->prefix . "lockdowns";
151
+ $subnet = loginLockdown_calc_subnet($_SERVER['REMOTE_ADDR']);
 
 
 
 
152
 
153
+ $stillLocked = $wpdb->get_var($wpdb->prepare("SELECT user_id FROM " . $table_name . " WHERE release_date > %s AND lockdown_IP LIKE %s", array(current_time('mysql', true), $subnet[1] . "%")));
154
 
155
+ return $stillLocked;
156
  }
157
 
158
+ function loginLockdown_listLockedDown()
159
+ {
160
+ global $wpdb;
161
+ $table_name = $wpdb->prefix . "lockdowns";
162
 
163
+ $listLocked = $wpdb->get_results(
164
+ $wpdb->prepare(
165
+ "SELECT lockdown_ID, floor((UNIX_TIMESTAMP(release_date)-UNIX_TIMESTAMP(%s))/60) AS minutes_left, lockdown_IP FROM $table_name WHERE release_date > %s",
166
+ array(current_time('mysql', true), current_time('mysql', true))
167
+ ),
168
+ ARRAY_A);
169
 
170
+ return $listLocked;
171
  }
172
 
173
+ function loginLockdown_get_options()
174
+ {
175
+ $loginlockdownAdminOptions = array(
176
+ 'max_login_retries' => 3,
177
+ 'retries_within' => 5,
178
+ 'lockout_length' => 60,
179
+ 'lockout_invalid_usernames' => 'no',
180
+ 'mask_login_errors' => 'no',
181
+ 'show_credit_link' => 'no'
182
+ );
183
+ $loginlockdownOptions = get_option("loginlockdownAdminOptions");
184
+ if (!empty($loginlockdownOptions)) {
185
+ foreach ($loginlockdownOptions as $key => $option) {
186
+ $loginlockdownAdminOptions[$key] = $option;
187
+ }
188
+ }
189
+ update_option("loginlockdownAdminOptions", $loginlockdownAdminOptions);
190
+ return $loginlockdownAdminOptions;
191
  }
192
 
193
+ function loginLockdown_calc_subnet($ip)
194
+ {
195
+ $subnet[0] = $ip;
196
+ if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) === false) {
197
+ $ip = loginLockdown_expandipv6($ip);
198
+ preg_match("/^([0-9abcdef]{1,4}:){4}/", $ip, $matches);
199
+ $subnet[0] = $ip;
200
+ $subnet[1] = $matches[0];
201
+ } else {
202
+ $subnet[1] = substr($ip, 0, strrpos($ip, ".") + 1);
203
+ }
204
+ return $subnet;
205
  }
206
 
207
+ function loginLockdown_expandipv6($ip)
208
+ {
209
+ $hex = unpack("H*hex", inet_pton($ip));
210
+ $ip = substr(preg_replace("/([A-f0-9]{4})/", "$1:", $hex['hex']), 0, -1);
211
 
212
+ return $ip;
213
  }
214
 
215
 
216
+ function loginLockdown_print_admin_page()
217
+ {
218
+ global $wpdb;
219
+ $table_name = $wpdb->prefix . "lockdowns";
220
+ $loginlockdownAdminOptions = loginLockdown_get_options();
221
+
222
+ if (isset($_POST['update_loginlockdownSettings'])) {
223
+
224
+ //wp_nonce check
225
+ check_admin_referer('login-lockdown_update-options');
226
+
227
+ if (isset($_POST['ll_max_login_retries'])) {
228
+ $loginlockdownAdminOptions['max_login_retries'] = sanitize_text_field($_POST['ll_max_login_retries']);
229
+ }
230
+ if (isset($_POST['ll_retries_within'])) {
231
+ $loginlockdownAdminOptions['retries_within'] = sanitize_text_field($_POST['ll_retries_within']);
232
+ }
233
+ if (isset($_POST['ll_lockout_length'])) {
234
+ $loginlockdownAdminOptions['lockout_length'] = sanitize_text_field($_POST['ll_lockout_length']);
235
+ }
236
+ if (isset($_POST['ll_lockout_invalid_usernames'])) {
237
+ $loginlockdownAdminOptions['lockout_invalid_usernames'] = sanitize_text_field($_POST['ll_lockout_invalid_usernames']);
238
+ }
239
+ if (isset($_POST['ll_mask_login_errors'])) {
240
+ $loginlockdownAdminOptions['mask_login_errors'] = sanitize_text_field($_POST['ll_mask_login_errors']);
241
+ }
242
+ if (isset($_POST['ll_show_credit_link'])) {
243
+ $loginlockdownAdminOptions['show_credit_link'] = sanitize_text_field($_POST['ll_show_credit_link']);
244
+ }
245
+ update_option("loginlockdownAdminOptions", $loginlockdownAdminOptions);
246
+ ?>
247
+ <div class="updated">
248
+ <p><strong><?php esc_html_e("Settings Updated.", "loginlockdown"); ?></strong></p>
249
+ </div>
250
+ <?php
251
+ }
252
+ if (isset($_POST['release_lockdowns'])) {
253
+
254
+ //wp_nonce check
255
+ check_admin_referer('login-lockdown_release-lockdowns');
256
+
257
+ if (isset($_POST['releaseme'])) {
258
+ $released = array_map( 'intval', $_POST['releaseme'] );
259
+
260
+ foreach ($released as $release_id) {
261
+ $wpdb->query(
262
+ $wpdb->prepare("UPDATE $table_name SET release_date = %s WHERE lockdown_ID = %d", array(current_time('mysql', true), $release_id))
263
+ );
264
+ }
265
+ }
266
+ update_option("loginlockdownAdminOptions", $loginlockdownAdminOptions);
267
+ ?>
268
+ <div class="updated">
269
+ <p><strong><?php esc_html_e("Lockdowns Released.", "loginlockdown"); ?></strong></p>
270
+ </div>
271
+ <?php
272
+ }
273
+ $dalist = loginLockdown_listLockedDown();
274
+ ?>
275
+ <div class="wrap">
276
+ <?php
277
+
278
+ $active_tab = isset($_GET['tab']) ? $_GET['tab'] : 'settings';
279
+
280
+ ?>
281
+ <h2><?php esc_html_e('Login LockDown Options', 'loginlockdown') ?></h2>
282
+
283
+ <h2 class="nav-tab-wrapper">
284
+ <a href="?page=loginlockdown.php&tab=settings" class="nav-tab <?php echo $active_tab == 'settings' ? 'nav-tab-active' : ''; ?>"><?php esc_html_e('Settings', 'loginlockdown') ?></a>
285
+ <a href="?page=loginlockdown.php&tab=activity" class="nav-tab <?php echo $active_tab == 'activity' ? 'nav-tab-active' : ''; ?>"><?php esc_html_e('Activity', 'loginlockdown') ?> (<?php echo count($dalist); ?>)</a>
286
+ </h2>
287
+ <?php if ($active_tab == 'settings') { ?>
288
+ <form method="post" action="<?php echo esc_attr($_SERVER["REQUEST_URI"]); ?>">
289
+ <?php
290
+ if (function_exists('wp_nonce_field'))
291
+ wp_nonce_field('login-lockdown_update-options');
292
+ ?>
293
+
294
+ <h3><?php esc_html_e('Max Login Retries', 'loginlockdown') ?></h3>
295
+ <p><?php esc_html_e('Number of failed login attempts within the "Retry Time Period Restriction" (defined below) needed to trigger a LockDown.', 'loginlockdown') ?></p>
296
+ <p><input type="text" name="ll_max_login_retries" size="8" value="<?php echo esc_attr($loginlockdownAdminOptions['max_login_retries']); ?>"></p>
297
+ <h3><?php esc_html_e('Retry Time Period Restriction (minutes)', 'loginlockdown') ?></h3>
298
+ <p><?php esc_html_e('Amount of time that determines the rate at which failed login attempts are allowed before a LockDown occurs.', 'loginlockdown') ?></p>
299
+ <p><input type="text" name="ll_retries_within" size="8" value="<?php echo esc_attr($loginlockdownAdminOptions['retries_within']); ?>"></p>
300
+ <h3><?php esc_html_e('Lockout Length (minutes)', 'loginlockdown') ?></h3>
301
+ <p><?php esc_html_e('How long a particular IP block will be locked out for once a LockDown has been triggered.', 'loginlockdown') ?></p>
302
+ <p><input type="text" name="ll_lockout_length" size="8" value="<?php echo esc_attr($loginlockdownAdminOptions['lockout_length']); ?>"></p>
303
+ <h3><?php esc_html_e('Lockout Invalid Usernames?', 'loginlockdown') ?></h3>
304
+ <p><?php esc_html_e('By default Login LockDown will not trigger if an attempt is made to log in using a username that does not exist. You can override this behavior here.', 'loginlockdown') ?></p>
305
+ <p><input type="radio" name="ll_lockout_invalid_usernames" value="yes" <?php if ($loginlockdownAdminOptions['lockout_invalid_usernames'] == "yes") echo "checked"; ?>>&nbsp;<?php esc_html_e('Yes', 'loginlockdown') ?>&nbsp;&nbsp;&nbsp;<input type="radio" name="ll_lockout_invalid_usernames" value="no" <?php if ($loginlockdownAdminOptions['lockout_invalid_usernames'] == "no") echo "checked"; ?>>&nbsp;<?php esc_html_e('No', 'loginlockdown') ?></p>
306
+ <h3><?php esc_html_e('Mask Login Errors?', 'loginlockdown') ?></h3>
307
+ <p><?php esc_html_e('WordPress will normally display distinct messages to the user depending on whether they try and log in with an invalid username, or with a valid username but the incorrect password. Toggling this option will hide why the login failed.', 'loginlockdown') ?></p>
308
+ <p><input type="radio" name="ll_mask_login_errors" value="yes" <?php if ($loginlockdownAdminOptions['mask_login_errors'] == "yes") echo "checked"; ?>>&nbsp;<?php esc_html_e('Yes', 'loginlockdown') ?>&nbsp;&nbsp;&nbsp;<input type="radio" name="ll_mask_login_errors" value="no" <?php if ($loginlockdownAdminOptions['mask_login_errors'] == "no") echo "checked"; ?>>&nbsp;<?php esc_html_e('No', 'loginlockdown') ?></p>
309
+ <h3><?php esc_html_e('Show Credit Link?', 'loginlockdown') ?></h3>
310
+ <p><?php esc_html_e('If enabled, Login LockDown will display the following message on the login form', 'loginlockdown') ?>:<br />
311
+ <blockquote><?php esc_html_e('Login form protected by', 'loginlockdown') ?> <a href='https://wploginlockdown.com/'>Login LockDown</a>.</blockquote>
312
+ <?php esc_html_e('This helps others know about the plugin so they can protect their blogs as well if they like. You can enable or disable this message below', 'loginlockdown') ?>:</p>
313
+ <input type="radio" name="ll_show_credit_link" value="yes" <?php if ($loginlockdownAdminOptions['show_credit_link'] == "yes" || $loginlockdownAdminOptions['show_credit_link'] == "") echo "checked"; ?>>&nbsp;<?php esc_html_e('Yes, display the credit link.', 'loginlockdown') ?><br />
314
+ <input type="radio" name="ll_show_credit_link" value="shownofollow" <?php if ($loginlockdownAdminOptions['show_credit_link'] == "shownofollow") echo "checked"; ?>>&nbsp;<?php esc_html_e('Display the credit link, but add "rel=\'nofollow\'" (ie. do not pass any link juice).', 'loginlockdown') ?><br />
315
+ <input type="radio" name="ll_show_credit_link" value="no" <?php if ($loginlockdownAdminOptions['show_credit_link'] == "no") echo "checked"; ?>>&nbsp;<?php esc_html_e('No, do not display the credit link.', 'loginlockdown') ?><br />
316
+ <div class="submit">
317
+ <input type="submit" class="button button-primary" name="update_loginlockdownSettings" value="<?php esc_html_e('Update Settings', 'loginlockdown') ?>" />
318
+ </div>
319
+ </form>
320
+ <?php } else { ?>
321
+ <form method="post" action="<?php echo esc_attr($_SERVER["REQUEST_URI"]); ?>">
322
+ <?php
323
+ if (function_exists('wp_nonce_field'))
324
+ wp_nonce_field('login-lockdown_release-lockdowns');
325
+ ?>
326
+ <h3><?php
327
+ if (count($dalist) == 1) {
328
+ printf(esc_html__('There is currently %d locked out IP address.', 'loginlockdown'), count($dalist));
329
+ } else {
330
+ printf(esc_html__('There are currently %d locked out IP addresses.', 'loginlockdown'), count($dalist));
331
+ } ?></h3>
332
+
333
+ <?php
334
+ $num_lockedout = count($dalist);
335
+ if (0 == $num_lockedout) {
336
+ echo "<p>No IP blocks currently locked out.</p>";
337
+ } else {
338
+ foreach ($dalist as $key => $option) {
339
+ ?>
340
+ <li><input type="checkbox" name="releaseme[]" value="<?php echo esc_attr($option['lockdown_ID']); ?>"> <?php echo esc_attr($option['lockdown_IP']); ?> (<?php echo esc_attr($option['minutes_left']); ?> <?php esc_html_e('minutes left', 'loginlockdown') ?>)</li>
341
+ <?php
342
+ }
343
+ }
344
+ ?>
345
+ <div class="submit">
346
+ <input type="submit" class="button button-primary" name="release_lockdowns" value="<?php esc_html_e('Release Selected', 'loginlockdown') ?>" />
347
+ </div>
348
+ </form>
349
+ <?php } ?>
350
+ </div>
351
  <?php
352
+ } //End function loginLockdown_print_admin_page()
353
 
354
+ function loginLockdown_ap()
355
+ {
356
+ if (function_exists('add_options_page')) {
357
+ add_options_page('Login LockDown', 'Login LockDown', 'manage_options', basename(__FILE__), 'loginLockdown_print_admin_page');
358
+ }
359
  }
360
 
361
+ function loginLockdown_credit_link()
362
+ {
363
+ global $loginlockdownOptions;
364
+ $thispage = "http://" . $_SERVER["HTTP_HOST"] . $_SERVER["REQUEST_URI"];
365
+ $homepage = get_option("home");
366
+ $showcreditlink = $loginlockdownOptions['show_credit_link'];
367
+ $relnofollow = true;
368
+ if ($showcreditlink != "shownofollow" && ($thispage == $homepage || $thispage == $homepage . "/" || substr($_SERVER["REQUEST_URI"], strlen($_SERVER["REQUEST_URI"]) - 12) == "wp-login.php")) {
369
+ $relnofollow = false;
370
+ }
371
+ if ($showcreditlink != "no") {
372
+ echo "<p>";
373
+ esc_html_e('Login form protected by', 'loginlockdown');
374
+ echo " <a href='" . esc_url('https://wploginlockdown.com/') . "' " . ($relnofollow ? "rel='nofollow'" : "") . ">Login LockDown</a>.<br /><br /><br /></p>";
375
+ }
376
  }
377
 
378
+ //Actions and Filters
379
+ if (isset($loginlockdown_db_version)) {
380
+ //Actions
381
+ add_action('admin_menu', 'loginLockdown_ap');
382
+ register_activation_hook(__FILE__, 'loginLockdown_install');
383
+ add_action('login_form', 'loginLockdown_credit_link');
384
+
385
+ remove_filter('authenticate', 'wp_authenticate_username_password', 20, 3);
386
+ add_filter('authenticate', 'loginLockdown_wp_authenticate_username_password', 20, 3);
387
+ //Filters
388
+
389
+ //Functions
390
+ function loginLockdown_wp_authenticate_username_password($user, $username, $password)
391
+ {
392
+ if (is_a($user, 'WP_User')) {
393
+ return $user;
394
+ }
395
+
396
+ if (empty($username) || empty($password)) {
397
+ $error = new WP_Error();
398
+
399
+ if (empty($username))
400
+ $error->add('empty_username', __('<strong>ERROR</strong>: The username field is empty.', 'loginlockdown'));
401
+
402
+ if (empty($password))
403
+ $error->add('empty_password', __('<strong>ERROR</strong>: The password field is empty.', 'loginlockdown'));
404
+
405
+ return $error;
406
+ }
407
+
408
+ $userdata = get_user_by('login', $username);
409
+
410
+ if (!$userdata) {
411
+ return new WP_Error('invalid_username', sprintf(__('<strong>ERROR</strong>: Invalid username. <a href="%s" title="Password Lost and Found">Lost your password</a>?', 'loginlockdown'), site_url('wp-login.php?action=lostpassword', 'login')));
412
+ }
413
+
414
+ $userdata = apply_filters('wp_authenticate_user', $userdata, $password);
415
+ if (is_wp_error($userdata)) {
416
+ return $userdata;
417
+ }
418
+
419
+ if (!wp_check_password($password, $userdata->user_pass, $userdata->ID)) {
420
+ return new WP_Error('incorrect_password', sprintf(__('<strong>ERROR</strong>: Incorrect password. <a href="%s" title="Password Lost and Found">Lost your password</a>?', 'loginlockdown'), site_url('wp-login.php?action=lostpassword', 'login')));
421
+ }
422
+
423
+ $user = new WP_User($userdata->ID);
424
+ return $user;
425
+ }
426
+
427
+
428
+ if (!function_exists('wp_authenticate')) {
429
+ function wp_authenticate($username, $password)
430
+ {
431
+ global $wpdb, $error;
432
+ global $loginlockdownOptions;
433
+
434
+ $username = sanitize_user($username);
435
+ $password = trim($password);
436
+
437
+ if ("" != loginLockdown_isLockedDown()) {
438
+ return new WP_Error('incorrect_password', __("<strong>ERROR</strong>: We're sorry, but this IP range has been blocked due to too many recent failed login attempts.<br /><br />Please try again later.", 'loginlockdown'));
439
+ }
440
+
441
+ $user = apply_filters('authenticate', null, $username, $password);
442
+
443
+ if ($user == null) {
444
+ // TODO what should the error message be? (Or would these even happen?)
445
+ // Only needed if all authentication handlers fail to return anything.
446
+ $user = new WP_Error('authentication_failed', __('<strong>ERROR</strong>: Invalid username or incorrect password.', 'loginlockdown'));
447
+ }
448
+
449
+ $ignore_codes = array('empty_username', 'empty_password');
450
+
451
+ if (is_wp_error($user) && !in_array($user->get_error_code(), $ignore_codes)) {
452
+ loginLockdown_incrementFails($username);
453
+
454
+ if ($loginlockdownOptions['max_login_retries'] <= loginLockdown_countFails($username)) {
455
+ loginLockdown_lockDown($username);
456
+ return new WP_Error('incorrect_password', __("<strong>ERROR</strong>: We're sorry, but this IP range has been blocked due to too many recent failed login attempts.<br /><br />Please try again later.", 'loginlockdown'));
457
+ }
458
+ if ('yes' == $loginlockdownOptions['mask_login_errors']) {
459
+ return new WP_Error('authentication_failed', sprintf(__('<strong>ERROR</strong>: Invalid username or incorrect password. <a href="%s" title="Password Lost and Found">Lost your password</a>?', 'loginlockdown'), site_url('wp-login.php?action=lostpassword', 'login')));
460
+ } else {
461
+ do_action('wp_login_failed', $username);
462
+ }
463
+ }
464
+
465
+ return $user;
466
+ }
467
+ }
468
+
469
+ // multisite network-wide activation
470
+ register_activation_hook(__FILE__, 'loginLockdown_multisite_activate');
471
+ function loginLockdown_multisite_activate($networkwide)
472
+ {
473
+ global $wpdb;
474
+
475
+ if (function_exists('is_multisite') && is_multisite()) {
476
+ // check if it is a network activation - if so, run the activation function for each blog id
477
+ if ($networkwide) {
478
+ $old_blog = $wpdb->blogid;
479
+ // Get all blog ids
480
+ $blogids = $wpdb->get_col("SELECT blog_id FROM $wpdb->blogs");
481
+ foreach ($blogids as $blog_id) {
482
+ switch_to_blog($blog_id);
483
+ loginLockdown_install();
484
+ }
485
+ switch_to_blog($old_blog);
486
+ return;
487
+ }
488
+ }
489
+ }
490
+
491
+ // multisite new site activation
492
+ add_action('wpmu_new_blog', 'loginLockdown_multisite_newsite', 10, 6);
493
+ function loginLockdown_multisite_newsite($blog_id, $user_id, $domain, $path, $site_id, $meta)
494
+ {
495
+ global $wpdb;
496
+
497
+ if (is_plugin_active_for_network('loginlockdown/loginlockdown.php')) {
498
+ $old_blog = $wpdb->blogid;
499
+ switch_to_blog($blog_id);
500
+ loginLockdown_install();
501
+ switch_to_blog($old_blog);
502
+ }
503
+ }
504
+
505
+ // multisite old sites check
506
+
507
+ add_action('admin_init', 'loginLockdown_multisite_legacy');
508
+ function loginLockdown_multisite_legacy()
509
+ {
510
+ $loginlockdownMSRunOnce = get_option("loginlockdownmsrunonce");
511
+ if (empty($loginlockdownMSRunOnce)) {
512
+ global $wpdb;
513
+
514
+ if (function_exists('is_multisite') && is_multisite()) {
515
+
516
+ $old_blog = $wpdb->blogid;
517
+
518
+ // Get all blog ids
519
+ $blogids = $wpdb->get_col("SELECT blog_id FROM $wpdb->blogs");
520
+ foreach ($blogids as $blog_id) {
521
+
522
+ // check if already exists
523
+ $bed_check = $wpdb->query($wpdb->prepare("SHOW TABLES LIKE %s", array($wpdb->base_prefix . $blog_id . '_login_fails')));
524
+ if (!$bed_check) {
525
+
526
+ switch_to_blog($blog_id);
527
+ loginLockdown_install();
528
+ }
529
+ }
530
+ switch_to_blog($old_blog);
531
+ }
532
+ add_option("loginlockdownmsrunonce", "done", "", "no");
533
+ return;
534
+ }
535
+ }
536
  }
537
 
538
+ add_action('plugins_loaded', 'loginLockdown_init', 10);
539
 
540
+ function loginLockdown_init()
541
+ {
542
+ load_plugin_textdomain('loginlockdown', false, dirname(plugin_basename(__FILE__)) . '/languages/');
543
  }
readme.txt CHANGED
@@ -1,31 +1,42 @@
1
- === Login LockDown ===
2
- Developer: Michael VanDeMar (michael@endlesspoetry.com)
3
- Tags: security, login, login form
4
- Requires at least: 3.6
5
- Tested up to: 5.2.3
6
- Stable Tag: 1.8.1
7
-
8
- Limits the number of login attempts from a given IP range within a certain time period.
 
 
 
9
 
10
  == Description ==
11
 
12
- Login LockDown records the IP address and timestamp of every failed login attempt. If more than a
13
- certain number of attempts are detected within a short period of time from the same
14
- IP range, then the login function is disabled for all requests from that range.
15
- This helps to prevent brute force password discovery. Currently the plugin defaults
16
- to a 1 hour lock out of an IP block after 3 failed login attempts within 5 minutes. This can be modified
17
- via the Options panel. Administrators can release locked out IP ranges manually from the panel.
18
 
19
  == Installation ==
20
 
21
  1. Extract the zip file into your plugins directory into its own folder.
22
  2. Activate the plugin in the Plugin options.
23
- 3. Customize the settings from the Options panel, if desired.
 
 
24
 
25
- Enjoy.
26
 
27
  == Change Log ==
28
 
 
 
 
 
 
 
 
 
29
  ver. 1.8.1 30-Sep-2019
30
 
31
  - adding missing ./languages folder
@@ -78,9 +89,8 @@ Enjoy.
78
 
79
  ver. 1.1 01-Sep-2007
80
 
81
- - revised time query to MySQL 4.0 compatability
82
 
83
  ver. 1.0 29-Aug-2007
84
 
85
  - released
86
-
1
+ === Login LockDown - Protect Login Form ===
2
+ Contributors: WebFactory
3
+ Tags: security, login, login form, protect login, login control, login blocking, lockdown, ban ip
4
+ Requires at least: 4.0
5
+ Tested up to: 6.0
6
+ Stable Tag: 1.82
7
+ Requires PHP: 5.2
8
+ License: GPLv2 or later
9
+ License URI: http://www.gnu.org/licenses/gpl-2.0.html
10
+
11
+ Protect login form by limiting the number of login attempts from the same IP and banning IPs.
12
 
13
  == Description ==
14
 
15
+ <a href="https://wploginlockdown.com/">Login LockDown</a> records the IP address and timestamp of every failed login attempt. If more than a certain number of attempts are detected within a short period of time from the same IP range, then the login function is disabled for all requests from that IP address. This helps to prevent brute force password discovery and attacks.
16
+ The plugin defaults to a 1 hour lock out of an IP block after 3 failed login attempts within 5 minutes. This can be modified in options. Administrators can release locked out IP ranges manually from the panel.
17
+
18
+ Configure the plugin from Settings - Login LockDown
 
 
19
 
20
  == Installation ==
21
 
22
  1. Extract the zip file into your plugins directory into its own folder.
23
  2. Activate the plugin in the Plugin options.
24
+ 3. Customize the settings from Settings - Login LockDown panel.
25
+
26
+ == Screenshots ==
27
 
28
+ 1. Protect your login form by banning IPs with multiple failed login attempts
29
 
30
  == Change Log ==
31
 
32
+ = v1.82 =
33
+ * 2022/09/23
34
+ * WebFactory took over development
35
+ * a full rewrite will follow soon, for now we patched some urgent things
36
+ * prefixed function names that are in global namespace
37
+ * properly escaped all inputs
38
+
39
+ = Old changelog =
40
  ver. 1.8.1 30-Sep-2019
41
 
42
  - adding missing ./languages folder
89
 
90
  ver. 1.1 01-Sep-2007
91
 
92
+ - revised time query to MySQL 4.0 compatibility
93
 
94
  ver. 1.0 29-Aug-2007
95
 
96
  - released
 
version.txt DELETED
@@ -1 +0,0 @@
1
- 1.7.1