28 foreach($this->checks as $name => $check) $config[$name] = $check->clientConfig();
29 return array_filter($config);
33 return $this->path . str_replace([
'.',
':'],
'-',$addr) .
$this->ext;
45 return \Rsi\File::write($this->
filename,
"$time:$reason\n",0666,
true);
53 public function addBan($reason,$delay = null){
54 if($this->remoteAddr)
try{
55 foreach($this->allowlist as $subnets => $reasons)
56 if(in_array($reason,$reasons) && \
Rsi\Http::inSubnet(explode(
';',$subnets),$this->remoteAddr))
return null;
57 if(($this->
countBan() < 10 * $this->banCount) && $this->
writeBan($reason,time() + ($delay ?: $this->defaultDelay)))
58 $this->session->banned = $this->_banned = null;
61 if($this->_fred->debug)
throw $e;
76 if($this->
hasBan()) sleep($this->bruteForceDelay * ($result ? pow(rand(0,100),3) / 1000000 : 1));
85 foreach($this->checks as $name=> $check)
if(!$check->unBan($addr)) $result =
false;
86 return \Rsi\File::unlink($this->
filename($addr)) && $result;
94 $config = $this->
server[$name] ?? [];
95 $class_name = \Rsi\Record::get($config,
'className',__CLASS__ .
'\\Server\\' . ucfirst($name));
96 $check =
new $class_name($this->_fred,$config);
97 return $check->check();
104 public function purge($days = null){
105 $time = time() - 86400 * ($days === null ? $this->defaultPurgeDays : $days);
107 foreach((
new \GlobIterator($this->path .
'*' . $this->ext)) as $filename => $file)
108 if($file->getMTime() < $time) $count += \
Rsi\File::unlink($filename);
117 public function check($ignore = null,$expected =
false){
120 if($blocked = $this->session->blocked) $this->_banned =
true;
121 elseif($blocked === null) $this->session->blocked = $this->_banned = \Rsi\Http::inSubnet($this->blocklist,$this->remoteAddr);
122 if(!$this->_banned && ($ignore !==
true)){
123 if(($allowed = $this->session->allowed) === null){
125 foreach($this->allowlist as $subnets => $checks)
if(\
Rsi\Http::inSubnet(explode(
';',$subnets),$this->remoteAddr))
126 $allowed = array_merge($allowed,$checks);
127 $this->session->allowed = $allowed;
129 $ignore = array_merge($ignore ?: [],$allowed);
130 foreach($this->checks as $name => $check)
131 if((!is_array($ignore) || !in_array($name,$ignore)) && !($result = $check->check($expected))){
132 $this->
component(
'log')->notice(
"Suspicious $name request");
133 if($result !== null) $this->
addBan($name,$check->delay);
143 usleep(rand(5000000,10000000));
144 http_response_code(429);
146 $_SERVER[
'REMOTE_ADDR'] = $this->remoteAddr;
147 require($this->_fred->templatePath .
'banned.php');
152 $this->_fred->halt();
158 if(($this->_banned === null) && !($this->_banned = $this->session->banned)){
159 if($this->_banned = is_file($this->
filename))
try{
162 foreach(file($this->
filename) as $reason)
163 if(substr($reason,0,strpos($reason,
':')) >= time()) $reasons[] = $reason;
166 if($reasons) \Rsi\File::write($this->
filename,implode($reasons),0666);
167 else \Rsi\File::unlink($this->
filename);
172 if($this->_fred->debug)
throw $e;
180 if($this->_checks === null){
182 foreach($this->
config(
'checks',[]) as $name => $config){
183 $class_name = \Rsi\Record::get($config,
'className',__CLASS__ .
'\\Check\\' . ucfirst($name));
184 $this->_checks[$name] =
new $class_name($this->_fred,$config);
191 if(!$this->_filename) $this->_filename = $this->
filename($this->remoteAddr);
196 if(!$this->_path) $this->_path = $this->
config(
'path') ?: \Rsi\File::tempDir();
201 if($this->_remoteAddr === null){
202 $this->_remoteAddr = $remote_addr = \Rsi\Http::remoteAddr();
203 foreach($this->proxies as $subnets => $headers)
if(\
Rsi\Http::inSubnet(explode(
';',$subnets),$remote_addr)){
205 foreach((array)$headers as $header)
if(array_key_exists($header,$_SERVER))
foreach(explode(
',',$_SERVER[$header]) as $addr){
206 if(filter_var($addr = trim($addr),FILTER_VALIDATE_IP,FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !==
false)
207 return $this->_remoteAddr = \Rsi\Http::expandAddr($addr);
208 else $reason =
'invalid';
211 "Proxy request from $remote_addr has $reason IP forward header",
214 compact(
'subnet',
'headers') + [
'remoteAddr' => $remote_addr,
'http' => $this->
component(
'request')->
server->http->data,
'request' => $_REQUEST]
216 $this->_remoteAddr =
false;
bruteForceDelay($result, $reason=null, $delay=null)
Add a brute force reason to the registry.
purge($days=null)
Purge the ban registration files.
$banCount
Number of registrations that will get you banned.
config($key, $default=null)
Retrieve a config value.
unBan($addr)
Unban a client's IP address.
$allowlist
IP-addresses (key; CIDR notation; seperate with semi-colon) to exclude from certain checks...
$blocklist
IP-addresses to ban anyhow (array of IP-address, optionally in CIDR notation).
$defaultDelay
Default time (seconds) a ban reason stays in the registry.
check($ignore=null, $expected=false)
Perform all security checks.
$_checks
Available checks (key = name, value = \Rsi\Fred\Security\Check).
$ext
Extension for ban registration file.
addBan($reason, $delay=null)
Add a ban reason to the registry.
$server
Server checks configuration (key = name, value = config as assoc.array).
$proxies
Proxy white-list (key = proxy IP-address (optionally in CIDR notation), value = header to use...
$_remoteAddr
True IP-address of the client client (optionally behind a white-listed proxy).
$bruteForceDelay
Default time (seconds) a brute force check stays in the registry.
$defaultPurgeDays
Default number of days after which a ban file will be purged.
$_path
Path to store the ban registration files (temp path if empty).
component($name)
Get a component (local or default).
server($name)
Perform a server check.