-
Notifications
You must be signed in to change notification settings - Fork 13
Expand file tree
/
Copy pathIpCounter.php
More file actions
130 lines (115 loc) · 3.33 KB
/
IpCounter.php
File metadata and controls
130 lines (115 loc) · 3.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
<?php
namespace dokuwiki\plugin\captcha;
/**
* A simple mechanism to count login failures for IP addresses
*
* Counter files are stored in date-based directories for easy cleanup.
* Note: Counters reset at midnight when a new directory is used.
*/
class IpCounter
{
/** @var string The client IP address being tracked */
protected $ip;
/** @var string File path where the failure counter is stored */
protected $store;
/** @var int Base delay in seconds for exponential timeout calculation */
protected $base;
/** @var int Maximum delay in seconds (cap for exponential timeout) */
protected $max;
/**
* Initialize the counter
*/
public function __construct()
{
global $conf;
$this->ip = clientIP(true);
$this->store = $conf['tmpdir'] . '/captcha/ip/' . date('Y-m-d') . '/' . md5($this->ip) . '.ip';
io_makeFileDir($this->store);
$this->base = (int)($conf['plugin']['captcha']['logindenial'] ?? 0);
$this->max = (int)($conf['plugin']['captcha']['logindenial_max'] ?? 3600);
}
/**
* Increases the counter by adding a byte
*
* @return void
*/
public function increment()
{
io_saveFile($this->store, '1', true);
}
/**
* Return the current counter
*
* @return int
*/
public function get()
{
return (int)@filesize($this->store);
}
/**
* Reset the counter to zero
*
* @return void
*/
public function reset()
{
@unlink($this->store);
}
/**
* Get timestamp of last failed attempt
*
* @return int Unix timestamp, 0 if no attempts
*/
public function getLastAttempt()
{
return (int)@filemtime($this->store);
}
/**
* Calculate required timeout in seconds based on failure count
*
* Uses exponential backoff: base * 2^(count-1), capped at max.
* First failed attempt is okay, second requires base seconds wait.
*
* @return int Timeout in seconds (0 if no failures or feature disabled)
*/
public function calculateTimeout()
{
if ($this->base < 1) return 0;
$count = $this->get();
if ($count < 1) return 0;
$timeout = $this->base * pow(2, $count - 1); // -1 because first failure is free
return (int)min($timeout, $this->max);
}
/**
* Get remaining wait time in seconds
*
* @return int Seconds remaining (0 if no wait needed or feature disabled)
*/
public function getRemainingTime()
{
$timeout = $this->calculateTimeout();
if ($timeout === 0) return 0;
$elapsed = time() - $this->getLastAttempt();
return max(0, $timeout - $elapsed);
}
/**
* Remove all outdated IP counter directories
*
* Deletes counter directories older than today, similar to FileCookie::clean()
*
* @return void
*/
public static function clean()
{
global $conf;
$path = $conf['tmpdir'] . '/captcha/ip/';
$dirs = glob("$path/*", GLOB_ONLYDIR);
if (!$dirs) return;
$today = date('Y-m-d');
foreach ($dirs as $dir) {
if (basename($dir) === $today) continue;
if (!preg_match('/\/captcha\/ip\//', $dir)) continue; // safety net
io_rmdir($dir, true);
}
}
}