30 foreach($this->checks as $name => $check) $config[$name] = $check->clientConfig();
31 return array_filter($config);
35 return $this->path . str_replace([
'.',
':'],
'-',$addr) .
$this->ext;
43 return $this->
hasBan() ? count(file($this->
filename,FILE_SKIP_EMPTY_LINES)) : 0;
47 return \Rsi\File::write($this->
filename,
"$time:$reason\n",0666,
true);
55 public function addBan($reason,$delay =
null){
56 if($this->remoteAddr)
try{
57 foreach($this->allowlist as $subnets => $reasons)
58 if(in_array($reason,$reasons) && \
Rsi\Http::inSubnet(explode(
';',$subnets),$this->remoteAddr))
return null;
59 if(($this->
countBan() < 10 * $this->banCount) && $this->
writeBan($reason,time() + ($delay ?: $this->defaultDelay)))
60 $this->session->banned = $this->_banned =
null;
63 if($this->_fred->debug)
throw $e;
78 if($this->
hasBan()) sleep(ceil($this->bruteForceDelay * ($result ? pow(rand(0,100),3) / 1000000 : 1)));
87 foreach($this->checks as $name=> $check)
if(!$check->unBan($addr)) $result =
false;
88 return \Rsi\File::unlink($this->
filename($addr)) && $result;
96 $config = $this->
server[$name] ?? [];
97 $class_name = \Rsi\Record::get($config,
'className',__CLASS__ .
'\\Server\\' . ucfirst($name));
98 $check =
new $class_name($this->_fred,$config);
99 return $check->check();
106 public function purge($days =
null){
107 if(is_file($filename = $this->_path .
'purge.lock') && (
filemtime($filename) > time() - $this->purgeInterval))
return null;
108 file_put_contents($filename,date(
'c'));
109 $time = time() - 86400 * ($days ===
null ? $this->defaultPurgeDays : $days);
111 foreach((
new \GlobIterator($this->path .
'*' . $this->ext)) as $filename => $file)
112 if($file->getMTime() < $time) $count += \
Rsi\File::unlink($filename);
121 public function check($ignore =
null,$expected =
false){
124 if($blocked = $this->session->blocked) $this->_banned =
true;
125 elseif($blocked ===
null) $this->session->blocked = $this->_banned = \Rsi\Http::inSubnet($this->blocklist,$this->remoteAddr);
126 if(!$this->_banned && ($ignore !==
true)){
127 if(($allowed = $this->session->allowed) ===
null){
129 foreach($this->allowlist as $subnets => $checks)
if(\
Rsi\Http::inSubnet(explode(
';',$subnets),$this->remoteAddr))
130 $allowed = array_merge($allowed,$checks);
131 $this->session->allowed = $allowed;
133 $ignore = array_merge($ignore ?: [],$allowed);
134 foreach($this->checks as $name => $check)
if(!is_array($ignore) || !in_array($name,$ignore)){
135 if(!array_key_exists($name,$this->_results)) $this->_results[$name] = $check->check($expected);
136 if(!$this->_results[$name]){
137 $this->
component(
'log')->notice(
"Suspicious $name request");
138 if($this->_results[$name] !==
null) $this->
addBan($name,$check->delay);
149 http_response_code(429);
151 $_SERVER[
'REMOTE_ADDR'] = $this->remoteAddr;
152 require($this->_fred->templatePath .
'banned.php');
157 $this->_fred->halt();
163 if(($this->_banned ===
null) && !($this->_banned = $this->session->banned)){
164 if($this->_banned = is_file($this->
filename))
try{
167 foreach(file($this->
filename) as $reason)
168 if(substr($reason,0,strpos($reason,
':')) >= time()) $reasons[] = $reason;
171 if($reasons) \Rsi\File::write($this->
filename,implode($reasons),0666);
172 else \Rsi\File::unlink($this->
filename);
177 if($this->_fred->debug)
throw $e;
185 if($this->_checks ===
null){
187 foreach($this->
config(
'checks',[]) as $name => $config){
188 $class_name = \Rsi\Record::get($config,
'className',__CLASS__ .
'\\Check\\' . ucfirst($name));
189 $this->_checks[$name] =
new $class_name($this->_fred,$config);
196 if(!$this->_filename) $this->_filename = $this->
filename($this->remoteAddr);
201 if(!$this->_path) $this->_path = $this->
config(
'path') ?: \Rsi\File::tempDir();
206 if(($this->_remoteAddr ===
null) && !\Rsi::commandLine()){
207 $this->_remoteAddr = $remote_addr = \Rsi\Http::remoteAddr();
208 foreach($this->proxies as $subnets => $headers)
if(\
Rsi\Http::inSubnet(explode(
';',$subnets),$remote_addr)){
210 foreach((array)$headers as $header)
if(array_key_exists($header,$_SERVER))
foreach(explode(
',',$_SERVER[$header]) as $addr){
211 if(filter_var($addr = trim($addr),FILTER_VALIDATE_IP,FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) !==
false)
212 return $this->_remoteAddr = \Rsi\Http::expandAddr($addr);
216 "Proxy request from $remote_addr has $reason IP forward header",
219 compact(
'subnets',
'headers') + [
'remoteAddr' => $remote_addr,
'http' => $this->
component(
'request')->
server->http->data,
'request' => $_REQUEST]
221 $this->_remoteAddr =
false;