Version Description
Download this release
Release Info
Developer | johanee |
Plugin | Limit Login Attempts |
Version | 1.0 |
Comparing to | |
See all releases |
Version 1.0
- limit-login-attempts.php +714 -0
- readme.txt +38 -0
- screenshot-1.gif +0 -0
- screenshot-2.gif +0 -0
- screenshot-3.gif +0 -0
- screenshot-4.gif +0 -0
limit-login-attempts.php
ADDED
@@ -0,0 +1,714 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<?php
|
2 |
+
/*
|
3 |
+
Plugin Name: Limit Login Attempts
|
4 |
+
Plugin URI: http://devel.kostdoktorn.se/limit-login-attempts
|
5 |
+
Description: Limit rate of login attempts, including by way of cookies, for each IP.
|
6 |
+
Author: Johan Eenfeldt
|
7 |
+
Author URI: http://devel.kostdoktorn.se
|
8 |
+
Version: 1.0
|
9 |
+
|
10 |
+
Copyright 2008 Johan Eenfeldt
|
11 |
+
|
12 |
+
Licenced under the GNU GPL:
|
13 |
+
|
14 |
+
This program is free software; you can redistribute it and/or modify
|
15 |
+
it under the terms of the GNU General Public License as published by
|
16 |
+
the Free Software Foundation; either version 2 of the License, or
|
17 |
+
(at your option) any later version.
|
18 |
+
|
19 |
+
This program is distributed in the hope that it will be useful,
|
20 |
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
21 |
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
22 |
+
GNU General Public License for more details.
|
23 |
+
|
24 |
+
You should have received a copy of the GNU General Public License
|
25 |
+
along with this program; if not, write to the Free Software
|
26 |
+
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
27 |
+
*/
|
28 |
+
|
29 |
+
|
30 |
+
/*
|
31 |
+
* Variables
|
32 |
+
*
|
33 |
+
* Assignments are for default value -- change in admin page.
|
34 |
+
*/
|
35 |
+
|
36 |
+
/* Lock out after this many tries */
|
37 |
+
$limit_login_allowed_retries = 4;
|
38 |
+
|
39 |
+
/* Lock out for this many seconds */
|
40 |
+
$limit_login_lockout_duration = 1200; // 20 minutes
|
41 |
+
|
42 |
+
/* Long lock out after this many lockouts */
|
43 |
+
$limit_login_allowed_lockouts = 4;
|
44 |
+
|
45 |
+
/* Long lock out for this many seconds */
|
46 |
+
$limit_login_long_duration = 86400; // 24 hours
|
47 |
+
|
48 |
+
/* Reset failed attempts after this many seconds */
|
49 |
+
$limit_login_valid_duration = 86400; // 24 hours
|
50 |
+
|
51 |
+
/* Also limit malformed/forged cookies?
|
52 |
+
*
|
53 |
+
* NOTE1: Only works in WP 2.7+, as necessary actions were added then.
|
54 |
+
*
|
55 |
+
* NOTE2: Overrides the pluggable function wp_get_current_user(). Will not
|
56 |
+
* co-exist peacefully with anyone doing the same (know any such?).
|
57 |
+
*/
|
58 |
+
$limit_login_cookies = true;
|
59 |
+
|
60 |
+
/* Notify on lockout. Values: '', 'log', 'email', 'log,email' */
|
61 |
+
$limit_login_lockout_notify = 'log';
|
62 |
+
|
63 |
+
/* Notify value checked against these in limit_login_sanitize_variables() */
|
64 |
+
$limit_login_lockout_notify_allowed = 'log,email';
|
65 |
+
|
66 |
+
/* If notify by email, do so after this number of lockouts */
|
67 |
+
$limit_login_notify_email_after = 4;
|
68 |
+
|
69 |
+
$limit_login_my_error_shown = false; /* have we shown our stuff? */
|
70 |
+
$limit_login_error_fn_exist = false; /* error replacing function */
|
71 |
+
|
72 |
+
|
73 |
+
/*
|
74 |
+
* Startup
|
75 |
+
*/
|
76 |
+
|
77 |
+
limit_login_setup();
|
78 |
+
|
79 |
+
/* Replace wp_get_current_user() to handle login cookie lockout */
|
80 |
+
if ($limit_login_cookies && !function_exists('wp_get_current_user') ) {
|
81 |
+
/*
|
82 |
+
* NOTE: overrides wp_get_current_user() when activated
|
83 |
+
*
|
84 |
+
* Unfortunately there is no nice filter like wp_authenticate_user when
|
85 |
+
* handling the auth cookies.
|
86 |
+
*/
|
87 |
+
function wp_get_current_user() {
|
88 |
+
global $current_user;
|
89 |
+
|
90 |
+
if (is_limit_login_ok()) {
|
91 |
+
get_currentuserinfo();
|
92 |
+
} else {
|
93 |
+
if ($current_user > 0) {
|
94 |
+
wp_set_current_user(0);
|
95 |
+
}
|
96 |
+
}
|
97 |
+
|
98 |
+
return $current_user;
|
99 |
+
}
|
100 |
+
}
|
101 |
+
|
102 |
+
|
103 |
+
/*
|
104 |
+
* Functions start here
|
105 |
+
*/
|
106 |
+
|
107 |
+
/* Get options and setup filters & actions */
|
108 |
+
function limit_login_setup() {
|
109 |
+
global $limit_login_cookies;
|
110 |
+
|
111 |
+
limit_login_setup_options();
|
112 |
+
|
113 |
+
if ($limit_login_cookies && function_exists('wp_get_current_user') ) {
|
114 |
+
add_action('admin_notices', 'limit_login_pluggable_warning');
|
115 |
+
$limit_login_cookies = false;
|
116 |
+
$limit_login_error_fn_exist = true;
|
117 |
+
}
|
118 |
+
|
119 |
+
/* Filters and actions */
|
120 |
+
add_action('wp_login_failed', 'limit_login_failed');
|
121 |
+
if ($limit_login_cookies) {
|
122 |
+
/* These are WP2.7+ */
|
123 |
+
add_action('auth_cookie_bad_hash', 'limit_login_failed_cookie');
|
124 |
+
add_action('auth_cookie_bad_username', 'limit_login_failed_cookie');
|
125 |
+
}
|
126 |
+
add_filter('wp_authenticate_user', 'limit_login_wp_authenticate_user', 99999, 2);
|
127 |
+
add_action('login_head', 'limit_login_add_error_message');
|
128 |
+
add_action('admin_menu', 'limit_login_admin_menu');
|
129 |
+
}
|
130 |
+
|
131 |
+
|
132 |
+
/* Check if it is ok to login */
|
133 |
+
function is_limit_login_ok() {
|
134 |
+
$index = $_SERVER['REMOTE_ADDR'];
|
135 |
+
|
136 |
+
/* lockout active? */
|
137 |
+
$lockouts = get_option('limit_login_lockouts');
|
138 |
+
return (!is_array($lockouts) || !isset($lockouts[$index]) || time() >= $lockouts[$index]);
|
139 |
+
}
|
140 |
+
|
141 |
+
|
142 |
+
/* Filter: allow login attempt? (called from wp_authenticate()) */
|
143 |
+
function limit_login_wp_authenticate_user($user, $password) {
|
144 |
+
if (is_wp_error($user) || is_limit_login_ok() ) {
|
145 |
+
return $user;
|
146 |
+
}
|
147 |
+
|
148 |
+
global $limit_login_my_error_shown;
|
149 |
+
$limit_login_my_error_shown = true;
|
150 |
+
|
151 |
+
$error = new WP_Error();
|
152 |
+
$error->add('too_many_retries', limit_login_error_msg());
|
153 |
+
return $error;
|
154 |
+
}
|
155 |
+
|
156 |
+
|
157 |
+
/* Action: failed cookie login wrapper for limit_login_failed() */
|
158 |
+
function limit_login_failed_cookie($arg) {
|
159 |
+
limit_login_failed($arg);
|
160 |
+
wp_clear_auth_cookie();
|
161 |
+
}
|
162 |
+
|
163 |
+
/*
|
164 |
+
* Action when login attempt failed
|
165 |
+
*
|
166 |
+
* Increase nr of retries (if necessary). Reset valid value. Setup
|
167 |
+
* lockout if nr of retries are above threshold. And more!
|
168 |
+
*/
|
169 |
+
function limit_login_failed($arg) {
|
170 |
+
global $limit_login_allowed_retries, $limit_login_valid_duration, $limit_login_last_user, $limit_login_allowed_lockouts, $limit_login_long_duration;
|
171 |
+
|
172 |
+
$index = $_SERVER['REMOTE_ADDR'];
|
173 |
+
|
174 |
+
/* if currently locked-out, do not add to retries */
|
175 |
+
$lockouts = get_option('limit_login_lockouts');
|
176 |
+
if(is_array($lockouts) && isset($lockouts[$index]) && time() < $lockouts[$index]) {
|
177 |
+
return;
|
178 |
+
} elseif (!is_array($lockouts)) {
|
179 |
+
$lockouts = array();
|
180 |
+
}
|
181 |
+
|
182 |
+
/* Get the arrays with retries and retries-valid information */
|
183 |
+
$retries = get_option('limit_login_retries');
|
184 |
+
$valid = get_option('limit_login_retries_valid');
|
185 |
+
if ($retries === false) {
|
186 |
+
$retries = array();
|
187 |
+
add_option('limit_login_retries', $retries, '', 'no');
|
188 |
+
}
|
189 |
+
if ($valid === false) {
|
190 |
+
$valid = array();
|
191 |
+
add_option('limit_login_retries_valid', $valid, '', 'no');
|
192 |
+
}
|
193 |
+
|
194 |
+
/* Check validity and add one to retries */
|
195 |
+
if (isset($retries[$index]) && isset($valid[$index]) && time() < $valid[$index]) {
|
196 |
+
$retries[$index] ++;
|
197 |
+
} else {
|
198 |
+
$retries[$index] = 1;
|
199 |
+
}
|
200 |
+
$valid[$index] = time() + $limit_login_valid_duration;
|
201 |
+
|
202 |
+
/* lockout? */
|
203 |
+
if($retries[$index] % $limit_login_allowed_retries == 0) {
|
204 |
+
global $limit_login_lockout_duration;
|
205 |
+
|
206 |
+
/* setup lockout, reset retries as needed */
|
207 |
+
if ($retries[$index] >= $limit_login_allowed_retries * $limit_login_allowed_lockouts) {
|
208 |
+
/* long lockout */
|
209 |
+
$lockouts[$index] = time() + $limit_login_long_duration;
|
210 |
+
unset($retries[$index]);
|
211 |
+
unset($valid[$index]);
|
212 |
+
} else {
|
213 |
+
/* normal lockout */
|
214 |
+
$lockouts[$index] = time() + $limit_login_lockout_duration;
|
215 |
+
}
|
216 |
+
|
217 |
+
/* try to find username which failed */
|
218 |
+
$user = '';
|
219 |
+
if (is_string($arg)) {
|
220 |
+
/* action: wp_login_failed */
|
221 |
+
$user = $arg;
|
222 |
+
} elseif (is_array($arg) && array_key_exists('username', $arg)) {
|
223 |
+
/* action: auth_cookie_bad_* */
|
224 |
+
$user = $arg['username'];
|
225 |
+
}
|
226 |
+
|
227 |
+
/* do housecleaning and save values */
|
228 |
+
limit_login_cleanup($retries, $lockouts, $valid);
|
229 |
+
|
230 |
+
/* do any notification */
|
231 |
+
limit_login_notify($user);
|
232 |
+
|
233 |
+
/* increase statistics */
|
234 |
+
$total = get_option('limit_login_lockouts_total');
|
235 |
+
if ($total === false) {
|
236 |
+
add_option('limit_login_lockouts_total', 1, '', 'no');
|
237 |
+
} else {
|
238 |
+
update_option('limit_login_lockouts_total', $total + 1);
|
239 |
+
}
|
240 |
+
} else {
|
241 |
+
/* do not lockout (yet!) */
|
242 |
+
update_option('limit_login_retries', $retries);
|
243 |
+
update_option('limit_login_retries_valid', $valid);
|
244 |
+
}
|
245 |
+
}
|
246 |
+
|
247 |
+
|
248 |
+
/* Clean up any old lockouts and old retries */
|
249 |
+
function limit_login_cleanup($retries = null, $lockouts = null, $valid = null) {
|
250 |
+
global $limit_login_lockout_duration, $limit_login_allowed_retries, $limit_login_valid_duration;
|
251 |
+
|
252 |
+
$now = time();
|
253 |
+
|
254 |
+
$lockouts = !is_null($lockouts) ? $lockouts : get_option('limit_login_lockouts');
|
255 |
+
|
256 |
+
/* remove old lockouts */
|
257 |
+
if (is_array($lockouts)) {
|
258 |
+
foreach ($lockouts as $ip => $lockout) {
|
259 |
+
if ($lockout < $now) {
|
260 |
+
unset($lockouts[$ip]);
|
261 |
+
}
|
262 |
+
}
|
263 |
+
update_option('limit_login_lockouts', $lockouts);
|
264 |
+
}
|
265 |
+
|
266 |
+
/* remove retries that are no longer valid */
|
267 |
+
$valid = !is_null($valid) ? $valid : get_option('limit_login_retries_valid');
|
268 |
+
$retries = !is_null($retries) ? $retries : get_option('limit_login_retries');
|
269 |
+
if (is_array($valid) && is_array($retries)) {
|
270 |
+
foreach ($valid as $ip => $lockout) {
|
271 |
+
if ($lockout < $now) {
|
272 |
+
unset($valid[$ip]);
|
273 |
+
unset($retries[$ip]);
|
274 |
+
}
|
275 |
+
}
|
276 |
+
|
277 |
+
/* go through retries directly, if for some reason they've gone out of sync */
|
278 |
+
foreach ($retries as $ip => $retry) {
|
279 |
+
if (!isset($valid[$ip])) {
|
280 |
+
unset($retries[$ip]);
|
281 |
+
}
|
282 |
+
}
|
283 |
+
|
284 |
+
update_option('limit_login_retries', $retries);
|
285 |
+
update_option('limit_login_retries_valid', $valid);
|
286 |
+
}
|
287 |
+
}
|
288 |
+
|
289 |
+
|
290 |
+
/* Email notification of lockout to admin (if configured) */
|
291 |
+
function limit_login_notify_email($user) {
|
292 |
+
global $limit_login_allowed_retries, $limit_login_allowed_lockouts, $limit_login_lockout_duration, $limit_login_notify_email_after, $limit_login_long_duration;
|
293 |
+
|
294 |
+
$index = $_SERVER['REMOTE_ADDR'];
|
295 |
+
$retries = get_option('limit_login_retries');
|
296 |
+
|
297 |
+
if (!is_array($retries)) {
|
298 |
+
$retries = array();
|
299 |
+
}
|
300 |
+
|
301 |
+
if ( isset($retries[$index])
|
302 |
+
&& ( ($retries[$index] / $limit_login_allowed_retries)
|
303 |
+
% $limit_login_notify_email_after ) != 0 ) {
|
304 |
+
return;
|
305 |
+
}
|
306 |
+
|
307 |
+
if (!isset($retries[$index])) {
|
308 |
+
$count = $limit_login_allowed_retries * $limit_login_allowed_lockouts;
|
309 |
+
$lockouts = $limit_login_allowed_lockouts;
|
310 |
+
$time = round($limit_login_long_duration / 3600) . ' hours';
|
311 |
+
} else {
|
312 |
+
$count = $retries[$index];
|
313 |
+
$lockouts = floor($count / $limit_login_allowed_retries);
|
314 |
+
$time = round($limit_login_lockout_duration / 60) . ' minutes';
|
315 |
+
}
|
316 |
+
|
317 |
+
$subject = '[' . get_option('blogname') . '] Too many failed login attempts';
|
318 |
+
$message = $count . ' failed login attempts (' . $lockouts . ' lockout(s))'
|
319 |
+
. ' from IP: ' . $index . "\r\n\r\n";
|
320 |
+
if ($user != '') {
|
321 |
+
$message .= 'Last user attempted: ' . $user . "\r\n\r\n";
|
322 |
+
}
|
323 |
+
$message .= 'IP was blocked for ' . $time;
|
324 |
+
|
325 |
+
@wp_mail(get_option('admin_email'), $subject, $message);
|
326 |
+
}
|
327 |
+
|
328 |
+
|
329 |
+
/* Logging of lockout (if configured) */
|
330 |
+
function limit_login_notify_log($user) {
|
331 |
+
$log = get_option('limit_login_logged');
|
332 |
+
if ($log === false) {
|
333 |
+
$log = array($_SERVER['REMOTE_ADDR'] => array($user => 1));
|
334 |
+
add_option('limit_login_logged', $log, '', 'no'); /* no autoload */
|
335 |
+
} else {
|
336 |
+
if (isset($log[$_SERVER['REMOTE_ADDR']])) {
|
337 |
+
$log[$_SERVER['REMOTE_ADDR']][$user]++;
|
338 |
+
} else {
|
339 |
+
$log[$_SERVER['REMOTE_ADDR']] = array($user => 1);
|
340 |
+
}
|
341 |
+
update_option('limit_login_logged', $log);
|
342 |
+
}
|
343 |
+
}
|
344 |
+
|
345 |
+
|
346 |
+
/* Handle notification in event of lockout */
|
347 |
+
function limit_login_notify($user) {
|
348 |
+
global $limit_login_lockout_notify;
|
349 |
+
|
350 |
+
$args = explode(',', $limit_login_lockout_notify);
|
351 |
+
|
352 |
+
if (empty($args)) {
|
353 |
+
return;
|
354 |
+
}
|
355 |
+
|
356 |
+
foreach ($args as $mode) {
|
357 |
+
switch (trim($mode)) {
|
358 |
+
case 'email':
|
359 |
+
limit_login_notify_email($user);
|
360 |
+
break;
|
361 |
+
case 'log':
|
362 |
+
limit_login_notify_log($user);
|
363 |
+
break;
|
364 |
+
}
|
365 |
+
}
|
366 |
+
}
|
367 |
+
|
368 |
+
|
369 |
+
/* Construct informative error message */
|
370 |
+
function limit_login_error_msg() {
|
371 |
+
$index = $_SERVER['REMOTE_ADDR'];
|
372 |
+
|
373 |
+
$lockouts = get_option('limit_login_lockouts');
|
374 |
+
|
375 |
+
$msg = '<strong>ERROR</strong>: Too many failed login attempts. Please try again ';
|
376 |
+
|
377 |
+
if (!is_array($lockouts) || !isset($lockouts[$index]) || time() >= $lockouts[$index]) {
|
378 |
+
/* Huh? No timeout active? */
|
379 |
+
$msg .= 'later.';
|
380 |
+
} else {
|
381 |
+
$when = ceil(($lockouts[$index] - time()) / 60);
|
382 |
+
if ($when > 60) {
|
383 |
+
$when = ceil($when / 60);
|
384 |
+
$measure = ' hour';
|
385 |
+
} else {
|
386 |
+
$measure = ' minute';
|
387 |
+
}
|
388 |
+
|
389 |
+
$msg .= 'in ' . $when . $measure . ($when > 1 ? 's' : '');
|
390 |
+
}
|
391 |
+
|
392 |
+
return $msg;
|
393 |
+
}
|
394 |
+
|
395 |
+
|
396 |
+
/* Add a message to login page when necessary */
|
397 |
+
function limit_login_add_error_message() {
|
398 |
+
global $error, $limit_login_my_error_shown, $limit_login_allowed_retries;
|
399 |
+
|
400 |
+
if ($limit_login_my_error_shown) {
|
401 |
+
return;
|
402 |
+
}
|
403 |
+
|
404 |
+
if (!is_limit_login_ok()) {
|
405 |
+
$error .= limit_login_error_msg();
|
406 |
+
return;
|
407 |
+
}
|
408 |
+
|
409 |
+
$index = $_SERVER['REMOTE_ADDR'];
|
410 |
+
|
411 |
+
$retries = get_option('limit_login_retries');
|
412 |
+
$valid = get_option('limit_login_retries_valid');
|
413 |
+
|
414 |
+
if (!is_array($retries) || !is_array($valid)) {
|
415 |
+
return;
|
416 |
+
}
|
417 |
+
if (!isset($retries[$index]) || !isset($valid[$index]) || time() > $valid[$index]) {
|
418 |
+
/* no valid retries */
|
419 |
+
return;
|
420 |
+
}
|
421 |
+
if (($retries[$index] % $limit_login_allowed_retries) == 0 ) {
|
422 |
+
/* already been locked out for these retries */
|
423 |
+
return;
|
424 |
+
}
|
425 |
+
|
426 |
+
$remaining = max(($limit_login_allowed_retries - ($retries[$index] % $limit_login_allowed_retries)), 0);
|
427 |
+
$error .= "<strong>" . $remaining
|
428 |
+
. "</strong> attempts remaining.";
|
429 |
+
}
|
430 |
+
|
431 |
+
|
432 |
+
/*
|
433 |
+
* Admin stuff
|
434 |
+
*/
|
435 |
+
|
436 |
+
/* Does wordpress version support cookie option? */
|
437 |
+
function limit_login_support_cookie_option() {
|
438 |
+
global $wp_version;
|
439 |
+
return (version_compare($wp_version, '2.7', '>='));
|
440 |
+
}
|
441 |
+
|
442 |
+
|
443 |
+
/* Only change var if option exists */
|
444 |
+
function limit_login_get_option($option, &$var) {
|
445 |
+
$a = get_option($option);
|
446 |
+
|
447 |
+
if ($a !== false) {
|
448 |
+
$var = $a;
|
449 |
+
}
|
450 |
+
}
|
451 |
+
|
452 |
+
|
453 |
+
/* Setup global variables from options */
|
454 |
+
function limit_login_setup_options() {
|
455 |
+
global $limit_login_allowed_retries, $limit_login_lockout_duration, $limit_login_valid_duration, $limit_login_cookies, $limit_login_lockout_notify, $limit_login_allowed_lockouts, $limit_login_long_duration, $limit_login_notify_email_after;
|
456 |
+
|
457 |
+
limit_login_get_option('limit_login_allowed_retries', $limit_login_allowed_retries);
|
458 |
+
limit_login_get_option('limit_login_lockout_duration', $limit_login_lockout_duration);
|
459 |
+
limit_login_get_option('limit_login_valid_duration', $limit_login_valid_duration);
|
460 |
+
limit_login_get_option('limit_login_cookies', $limit_login_cookies);
|
461 |
+
limit_login_get_option('limit_login_lockout_notify', $limit_login_lockout_notify);
|
462 |
+
limit_login_get_option('limit_login_allowed_lockouts', $limit_login_allowed_lockouts);
|
463 |
+
limit_login_get_option('limit_login_long_duration', $limit_login_long_duration);
|
464 |
+
limit_login_get_option('limit_login_notify_email_after', $limit_login_notify_email_after);
|
465 |
+
|
466 |
+
limit_login_sanitize_variables();
|
467 |
+
}
|
468 |
+
|
469 |
+
|
470 |
+
/* Update options in db from global variables */
|
471 |
+
function limit_login_update_options() {
|
472 |
+
global $limit_login_allowed_retries, $limit_login_lockout_duration, $limit_login_valid_duration, $limit_login_cookies, $limit_login_lockout_notify, $limit_login_allowed_lockouts, $limit_login_long_duration, $limit_login_notify_email_after;
|
473 |
+
|
474 |
+
update_option('limit_login_allowed_retries', $limit_login_allowed_retries);
|
475 |
+
update_option('limit_login_lockout_duration', $limit_login_lockout_duration);
|
476 |
+
update_option('limit_login_allowed_lockouts', $limit_login_allowed_lockouts);
|
477 |
+
update_option('limit_login_long_duration', $limit_login_long_duration);
|
478 |
+
update_option('limit_login_valid_duration', $limit_login_valid_duration);
|
479 |
+
update_option('limit_login_lockout_notify', $limit_login_lockout_notify);
|
480 |
+
update_option('limit_login_notify_email_after', $limit_login_notify_email_after);
|
481 |
+
update_option('limit_login_cookies', $limit_login_cookies ? '1' : '0');
|
482 |
+
}
|
483 |
+
|
484 |
+
|
485 |
+
/* Make sure the variables make sense */
|
486 |
+
function limit_login_sanitize_variables() {
|
487 |
+
global $limit_login_allowed_retries, $limit_login_lockout_duration, $limit_login_valid_duration, $limit_login_cookies, $limit_login_lockout_notify, $limit_login_allowed_lockouts, $limit_login_long_duration, $limit_login_lockout_notify_allowed, $limit_login_notify_email_after;
|
488 |
+
|
489 |
+
$limit_login_allowed_retries = max(1, intval($limit_login_allowed_retries));
|
490 |
+
$limit_login_lockout_duration = max(1, intval($limit_login_lockout_duration));
|
491 |
+
$limit_login_valid_duration = max(1, intval($limit_login_valid_duration));
|
492 |
+
$limit_login_allowed_lockouts = max(1, intval($limit_login_allowed_lockouts));
|
493 |
+
$limit_login_long_duration = max(1, intval($limit_login_long_duration));
|
494 |
+
|
495 |
+
$limit_login_notify_email_after = max(1, intval($limit_login_notify_email_after));
|
496 |
+
$limit_login_notify_email_after = min($limit_login_allowed_lockouts, $limit_login_notify_email_after);
|
497 |
+
|
498 |
+
$args = explode(',', $limit_login_lockout_notify);
|
499 |
+
$args_allowed = explode(',', $limit_login_lockout_notify_allowed);
|
500 |
+
$new_args = array();
|
501 |
+
foreach ($args as $a) {
|
502 |
+
if (in_array($a, $args_allowed)) {
|
503 |
+
$new_args[] = $a;
|
504 |
+
}
|
505 |
+
}
|
506 |
+
$limit_login_lockout_notify = implode(',', $new_args);
|
507 |
+
|
508 |
+
$limit_login_cookies = $limit_login_cookies && limit_login_support_cookie_option() ? true : false;
|
509 |
+
}
|
510 |
+
|
511 |
+
|
512 |
+
/* Warning msg if unable to replace pluggable function (used by another plugin?) */
|
513 |
+
function limit_login_pluggable_warning() {
|
514 |
+
echo("<div id='message' class='error fade'><p>"
|
515 |
+
. "<a href=\"options-general.php?page=limit-login-attempts\">"
|
516 |
+
. __('Limit Login Attempts</a> is unable to replace function wp_get_current_user(). Disable plugin cookie login handling, or competing plugin.','limit-login')
|
517 |
+
. "</p></div>");
|
518 |
+
}
|
519 |
+
|
520 |
+
|
521 |
+
/* Add admin options page */
|
522 |
+
function limit_login_admin_menu() {
|
523 |
+
add_options_page('Limit Login Attempts', 'Limit Login Attempts', 8, 'limit-login-attempts', 'limit_login_option_page');
|
524 |
+
}
|
525 |
+
|
526 |
+
|
527 |
+
/* Show log on admin page */
|
528 |
+
function limit_login_show_log($log) {
|
529 |
+
if (!is_array($log) || count($log) == 0) {
|
530 |
+
return;
|
531 |
+
}
|
532 |
+
|
533 |
+
echo('<tr><th scope="col">IP</th><th scope="col">Tried to log in as</th></tr>');
|
534 |
+
foreach ($log as $ip => $arr) {
|
535 |
+
echo('<tr><td class="limit-login-ip">' . $ip . '</td><td class="limit-login-max">');
|
536 |
+
$first = true;
|
537 |
+
foreach($arr as $user => $count) {
|
538 |
+
if (!$first) {
|
539 |
+
echo(', ' . $user . ' (' . $count . ' lockouts)');
|
540 |
+
} else {
|
541 |
+
echo($user . ' (' . $count . ' lockouts)');
|
542 |
+
}
|
543 |
+
$first = false;
|
544 |
+
}
|
545 |
+
echo('</td></tr>');
|
546 |
+
}
|
547 |
+
}
|
548 |
+
|
549 |
+
/* Actual admin page */
|
550 |
+
function limit_login_option_page() {
|
551 |
+
global $limit_login_allowed_retries, $limit_login_lockout_duration, $limit_login_valid_duration, $limit_login_cookies, $limit_login_lockout_notify, $limit_login_allowed_lockouts, $limit_login_long_duration, $limit_login_lockout_notify_allowed, $limit_login_notify_email_after;
|
552 |
+
|
553 |
+
limit_login_cleanup();
|
554 |
+
|
555 |
+
if (!current_user_can('manage_options')) {
|
556 |
+
wp_die('Sorry, but you do not have permissions to change settings.');
|
557 |
+
}
|
558 |
+
|
559 |
+
/* Should we clear log? */
|
560 |
+
if ($_POST['clear_log']) {
|
561 |
+
update_option('limit_login_logged', '');
|
562 |
+
echo "<div id='message' class='updated fade'><p>Log cleared</p></div>";
|
563 |
+
}
|
564 |
+
|
565 |
+
/* Should we reset counter? */
|
566 |
+
if ($_POST['reset_total']) {
|
567 |
+
update_option('limit_login_lockouts_total', 0);
|
568 |
+
echo "<div id='message' class='updated fade'><p>Counter reset</p></div>";
|
569 |
+
}
|
570 |
+
|
571 |
+
/* Should we restore current lockouts? */
|
572 |
+
if ($_POST['reset_current']) {
|
573 |
+
update_option('limit_login_lockouts', array());
|
574 |
+
echo "<div id='message' class='updated fade'><p>Current lockouts restored</p></div>";
|
575 |
+
}
|
576 |
+
|
577 |
+
/* Should we update options */
|
578 |
+
if (($_POST['update_options'])) {
|
579 |
+
$limit_login_allowed_retries = $_POST['allowed_retries'];
|
580 |
+
$limit_login_lockout_duration = $_POST['lockout_duration'] * 60;
|
581 |
+
$limit_login_valid_duration = $_POST['valid_duration'] * 3600;
|
582 |
+
$limit_login_cookies = $_POST['cookies'] == '1' ? true : false;
|
583 |
+
$limit_login_allowed_lockouts = $_POST['allowed_lockouts'];
|
584 |
+
$limit_login_long_duration = $_POST['long_duration'] * 3600;
|
585 |
+
$limit_login_notify_email_after = $_POST['email_after'];
|
586 |
+
|
587 |
+
$v = array();
|
588 |
+
if ($_POST['lockout_notify_log'])
|
589 |
+
$v[] = 'log';
|
590 |
+
if ($_POST['lockout_notify_email'])
|
591 |
+
$v[] = 'email';
|
592 |
+
$limit_login_lockout_notify = implode(',', $v);
|
593 |
+
|
594 |
+
limit_login_sanitize_variables();
|
595 |
+
limit_login_update_options();
|
596 |
+
echo "<div id='message' class='updated fade'><p>Options changed</p></div>";
|
597 |
+
}
|
598 |
+
|
599 |
+
$lockouts_total = get_option('limit_login_lockouts_total', 0);
|
600 |
+
$lockouts = get_option('limit_login_lockouts');
|
601 |
+
$lockouts_now = is_array($lockouts) ? count($lockouts) : 0;
|
602 |
+
|
603 |
+
if (!limit_login_support_cookie_option()) {
|
604 |
+
$cookies_disabled = ' DISABLED ';
|
605 |
+
$cookies_note = ' <br /> <strong>NOTE:</strong> Only works on Wordpress 2.7 or later ';
|
606 |
+
} else {
|
607 |
+
$cookies_disabled = '';
|
608 |
+
$cookies_note = '';
|
609 |
+
}
|
610 |
+
$cookies_yes = $limit_login_cookies ? ' checked ' : '';
|
611 |
+
$cookies_no = $limit_login_cookies ? '' : ' checked ';
|
612 |
+
|
613 |
+
$v = explode(',', $limit_login_lockout_notify);
|
614 |
+
$log_checked = in_array('log', $v) ? ' checked ' : '';
|
615 |
+
$email_checked = in_array('email', $v) ? ' checked ' : '';
|
616 |
+
?>
|
617 |
+
<div class="wrap">
|
618 |
+
<h2>Limit Login Attempts Settings</h2>
|
619 |
+
<h3>Statistics</h3>
|
620 |
+
<form action="options-general.php?page=limit-login-attempts" method="post">
|
621 |
+
<table class="form-table">
|
622 |
+
<tr>
|
623 |
+
<th scope="row" valign="top">Total lockouts</th>
|
624 |
+
<td>
|
625 |
+
<?php if ($lockouts_total > 0) { ?>
|
626 |
+
<input name="reset_total" value="Reset Counter" type="submit" />
|
627 |
+
<?php echo($lockouts_total); ?> lockouts since last reset
|
628 |
+
<?php } else { ?>
|
629 |
+
No lockouts yet
|
630 |
+
<?php } ?>
|
631 |
+
</td>
|
632 |
+
</tr>
|
633 |
+
<?php if ($lockouts_now > 0) { ?>
|
634 |
+
<tr>
|
635 |
+
<th scope="row" valign="top">Active lockouts</th>
|
636 |
+
<td>
|
637 |
+
<input name="reset_current" value="Restore Lockouts" type="submit" />
|
638 |
+
<?php echo($lockouts_now); ?> IP is currently blocked from trying to log in
|
639 |
+
</td>
|
640 |
+
</tr>
|
641 |
+
<?php } ?>
|
642 |
+
</table>
|
643 |
+
</form>
|
644 |
+
<h3>Options</h3>
|
645 |
+
<form action="options-general.php?page=limit-login-attempts" method="post">
|
646 |
+
<table class="form-table">
|
647 |
+
<tr>
|
648 |
+
<th scope="row" valign="top">Lockout</th>
|
649 |
+
<td>
|
650 |
+
<input type="text" size="3" maxlength="4" value="<?php echo($limit_login_allowed_retries); ?>" name="allowed_retries" /> allowed retries <br />
|
651 |
+
<input type="text" size="3" maxlength="4" value="<?php echo($limit_login_lockout_duration/60); ?>" name="lockout_duration" /> minutes lockout <br />
|
652 |
+
<input type="text" size="3" maxlength="4" value="<?php echo($limit_login_allowed_lockouts); ?>" name="allowed_lockouts" /> lockouts increase lockout time to <input type="text" size="3" maxlength="4" value="<?php echo($limit_login_long_duration/3600); ?>" name="long_duration" /> hours <br />
|
653 |
+
<input type="text" size="3" maxlength="4" value="<?php echo($limit_login_valid_duration/3600); ?>" name="valid_duration" /> hours until retries are reset
|
654 |
+
</td>
|
655 |
+
</tr>
|
656 |
+
<tr>
|
657 |
+
<th scope="row" valign="top">Handle cookie login</th>
|
658 |
+
<td>
|
659 |
+
<input type="radio" name="cookies" <?php echo $cookies_disabled . $cookies_yes; ?> value="1" /> Yes <input type="radio" name="cookies" <?php echo $cookies_disabled . $cookies_no; ?> value="0" /> No
|
660 |
+
<?php echo $cookies_note ?>
|
661 |
+
</td>
|
662 |
+
</tr>
|
663 |
+
<tr>
|
664 |
+
<th scope="row" valign="top">Notify on lockout</th>
|
665 |
+
<td>
|
666 |
+
<input type="checkbox" name="lockout_notify_log" <?php echo $log_checked; ?> value="log" /> Log IP<br />
|
667 |
+
<input type="checkbox" name="lockout_notify_email" <?php echo $email_checked; ?> value="email" /> Email to admin after <input type="text" size="3" maxlength="4" value="<?php echo($limit_login_notify_email_after); ?>" name="email_after" /> lockouts
|
668 |
+
</td>
|
669 |
+
</tr>
|
670 |
+
</table>
|
671 |
+
<p class="submit">
|
672 |
+
<input name="update_options" value="Change Options" type="submit" />
|
673 |
+
</p>
|
674 |
+
</form>
|
675 |
+
<?php
|
676 |
+
$log = get_option('limit_login_logged');
|
677 |
+
|
678 |
+
if (is_array($log) && count($log) > 0) {
|
679 |
+
?>
|
680 |
+
<h3>Lockout log</h3>
|
681 |
+
<form action="options-general.php?page=limit-login-attempts" method="post">
|
682 |
+
<input type="hidden" value="true" name="clear_log" />
|
683 |
+
<p class="submit">
|
684 |
+
<input name="submit" value="Clear Log" type="submit" />
|
685 |
+
</p>
|
686 |
+
</form>
|
687 |
+
<style type="text/css" media="screen">
|
688 |
+
.limit-login-log th {
|
689 |
+
font-weight: bold;
|
690 |
+
}
|
691 |
+
.limit-login-log td, .limit-login-log th {
|
692 |
+
padding: 1px 5px 1px 5px;
|
693 |
+
}
|
694 |
+
td.limit-login-ip {
|
695 |
+
font-family: "Courier New", Courier, monospace;
|
696 |
+
vertical-align: top;
|
697 |
+
}
|
698 |
+
td.limit-login-max {
|
699 |
+
width: 100%;
|
700 |
+
}
|
701 |
+
</style>
|
702 |
+
<div class="limit-login-log">
|
703 |
+
<table class="form-table">
|
704 |
+
<?php limit_login_show_log($log); ?>
|
705 |
+
</table>
|
706 |
+
</div>
|
707 |
+
<?php
|
708 |
+
}
|
709 |
+
?>
|
710 |
+
|
711 |
+
</div>
|
712 |
+
<?php
|
713 |
+
}
|
714 |
+
?>
|
readme.txt
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
=== Limit Login Attempts ===
|
2 |
+
Contributors: johanee
|
3 |
+
Tags: login, security
|
4 |
+
Requires at least: 2.5
|
5 |
+
Tested up to: 2.7
|
6 |
+
Stable tag: 1.0
|
7 |
+
|
8 |
+
Limit rate of login attempts, including by way of cookies, for each IP.
|
9 |
+
|
10 |
+
== Description ==
|
11 |
+
|
12 |
+
Limit the number of login attempts possible both through normal login as well as (WordPress 2.7+) using auth cookies.
|
13 |
+
|
14 |
+
By default WordPress allows unlimited login attempts either through the login page or by sending special cookies. This allows passwords (or hashes) to be brute-force cracked with relative ease.
|
15 |
+
|
16 |
+
Limit Login Attempts blocks an Internet address from making further attempts after a specified limit on retries is reached, making a brute-force attack difficult or impossible.
|
17 |
+
|
18 |
+
Features
|
19 |
+
|
20 |
+
* Limit the number of retry attempts when logging in (for each IP). Fully customizable
|
21 |
+
* (WordPress 2.7+) Limit the number of attempts to log in using auth cookies in same way
|
22 |
+
* Informs user about remaining retries or lockout time on login page
|
23 |
+
* Optional logging, optional email notification
|
24 |
+
|
25 |
+
Of possible note: when cookie login handling is activated plugin overrides the pluggable function wp_get_current_user, which might collide with others wanting to do the same. If you know of any such plugins please contact me.
|
26 |
+
|
27 |
+
== Installation ==
|
28 |
+
|
29 |
+
1. Download and extract plugin files to a folder in your wp-content/plugin directory.
|
30 |
+
2. Activate the plugin through the WordPress admin interface.
|
31 |
+
3. Customize the settings from the options page, if desired.
|
32 |
+
|
33 |
+
== Screenshots ==
|
34 |
+
|
35 |
+
1. Loginscreen after failed login with retries remaining
|
36 |
+
2. Loginscreen after failed login during lockout
|
37 |
+
3. Administration interface in WordPress 2.7
|
38 |
+
4. Administration interface in WordPress 2.5
|
screenshot-1.gif
ADDED
Binary file
|
screenshot-2.gif
ADDED
Binary file
|
screenshot-3.gif
ADDED
Binary file
|
screenshot-4.gif
ADDED
Binary file
|