Files
nexusphp/app/Repositories/AgentAllowRepository.php
2023-04-29 03:46:14 +08:00

337 lines
13 KiB
PHP

<?php
namespace App\Repositories;
use App\Exceptions\ClientNotAllowedException;
use App\Models\AgentAllow;
use App\Models\AgentDeny;
use Illuminate\Support\Collection;
use Nexus\Database\NexusDB;
class AgentAllowRepository extends BaseRepository
{
public function getList(array $params)
{
$query = AgentAllow::query();
if (!empty($params['family'])) {
$query->where('family', 'like', "%{$params['family']}%");
}
list($sortField, $sortType) = $this->getSortFieldAndType($params);
$query->orderBy($sortField, $sortType);
return $query->paginate();
}
public function store(array $params)
{
$this->getPatternMatches($params['peer_id_pattern'], $params['peer_id_start'], $params['peer_id_match_num']);
$this->getPatternMatches($params['agent_pattern'], $params['agent_start'], $params['agent_match_num']);
$model = AgentAllow::query()->create($params);
return $model;
}
public function update(array $params, $id)
{
$this->getPatternMatches($params['peer_id_pattern'], $params['peer_id_start'], $params['peer_id_match_num']);
$this->getPatternMatches($params['agent_pattern'], $params['agent_start'], $params['agent_match_num']);
$model = AgentAllow::query()->findOrFail($id);
$model->update($params);
return $model;
}
public function getDetail($id)
{
$model = AgentAllow::query()->findOrFail($id);
return $model;
}
public function delete($id)
{
$model = AgentAllow::query()->findOrFail($id);
$model->denies()->delete();
$result = $model->delete();
return $result;
}
public function getPatternMatches($pattern, $start, $matchNum)
{
if (!preg_match($pattern, $start, $matches)) {
throw new ClientNotAllowedException(sprintf('pattern: %s can not match start: %s', $pattern, $start));
}
$matchCount = count($matches) - 1;
//due to old data may be matchNum > matchCount
// if ($matchNum > $matchCount && !IN_NEXUS) {
// throw new ClientNotAllowedException("pattern: $pattern match start: $start got matches count: $matchCount, but require $matchNum.");
// }
return array_slice($matches, 1, $matchNum);
}
/**
* @param $peerId
* @param $agent
* @param false $debug
* @return \App\Models\NexusModel|mixed
* @throws ClientNotAllowedException
*/
public function checkClient($peerId, $agent, $debug = false)
{
//check from high version to low version, if high version allow, stop!
$cacheKey = nexus_env("CACHE_KEY_AGENT_ALLOW", "all_agent_allows") . ":php";
$allows = NexusDB::remember($cacheKey, 3600, function () {
return AgentAllow::query()
->orderBy('peer_id_start', 'desc')
->orderBy('agent_start', 'desc')
->get();
});
$agentAllowPassed = null;
$versionTooLowStr = '';
foreach ($allows as $agentAllow) {
$agentAllowId = $agentAllow->id;
$logPrefix = "[ID: $agentAllowId]";
$isPeerIdAllowed = $isAgentAllowed = $isPeerIdTooLow = $isAgentTooLow = false;
//check peer_id, when handle scrape request, no peer_id, so let it pass
if ($agentAllow->peer_id_pattern == '' || $peerId === null) {
$isPeerIdAllowed = true;
} else {
$pattern = $agentAllow->peer_id_pattern;
$start = $agentAllow->peer_id_start;
$matchType = $agentAllow->peer_id_matchtype;
$matchNum = $agentAllow->peer_id_match_num;
try {
$peerIdResult = $this->isAllowed($pattern, $start, $matchNum, $matchType, $peerId, $debug, $logPrefix);
if ($debug) {
do_log(
"$logPrefix, peerIdResult: $peerIdResult, with parameters: "
. nexus_json_encode(compact('pattern', 'start', 'matchNum', 'matchType', 'peerId'))
);
}
} catch (\Exception $exception) {
do_log("$logPrefix, check peer_id error: " . $exception->getMessage(), 'error');
throw new ClientNotAllowedException("regular expression err for peer_id: " . $start . ", please ask sysop to fix this");
}
if ($peerIdResult == 1) {
$isPeerIdAllowed = true;
}
if ($peerIdResult == 2) {
$isPeerIdTooLow = true;
}
}
//check agent
if ($agentAllow->agent_pattern == '') {
$isAgentAllowed = true;
} else {
$pattern = $agentAllow->agent_pattern;
$start = $agentAllow->agent_start;
$matchType = $agentAllow->agent_matchtype;
$matchNum = $agentAllow->agent_match_num;
try {
$agentResult = $this->isAllowed($pattern, $start, $matchNum, $matchType, $agent, $debug, $logPrefix);
if ($debug) {
do_log(
"$logPrefix, agentResult: $agentResult, with parameters: "
. nexus_json_encode(compact('pattern', 'start', 'matchNum', 'matchType', 'agent'))
);
}
} catch (\Exception $exception) {
do_log("$logPrefix, check agent error: " . $exception->getMessage(), 'error');
throw new ClientNotAllowedException("regular expression err for agent: " . $start . ", please ask sysop to fix this");
}
if ($agentResult == 1) {
$isAgentAllowed = true;
}
if ($agentResult == 2) {
$isAgentTooLow = true;
}
}
//both OK, passed, client is allowed
if ($isPeerIdAllowed && $isAgentAllowed) {
$agentAllowPassed = $agentAllow;
break;
}
if ($isPeerIdTooLow && $isAgentTooLow) {
$versionTooLowStr = "Your " . $agentAllow->family . " 's version is too low, please update it after " . $agentAllow->start_name;
}
}
if ($versionTooLowStr) {
throw new ClientNotAllowedException($versionTooLowStr);
}
if (!$agentAllowPassed) {
throw new ClientNotAllowedException("Banned Client, Please goto " . getSchemeAndHttpHost() . "/faq.php#id29 for a list of acceptable clients");
}
if ($debug) {
do_log("agentAllowPassed: " . $agentAllowPassed->toJson());
}
// check if exclude
if ($agentAllowPassed->exception == 'yes') {
$agentDeny = $this->checkIsDenied($peerId, $agent, $agentAllowPassed->id);
if ($agentDeny) {
if ($debug) {
do_log("agentDeny: " . $agentDeny->toJson());
}
throw new ClientNotAllowedException(sprintf(
"[%s-%s]Client: %s is banned due to: %s",
$agentAllowPassed->id, $agentDeny->id, $agentDeny->name, $agentDeny->comment
));
}
}
if (isHttps() && $agentAllowPassed->allowhttps != 'yes') {
throw new ClientNotAllowedException(sprintf(
"[%s]This client does not support https well, Please goto %s/faq.php#id29 for a list of proper clients",
$agentAllowPassed->id, getSchemeAndHttpHost()
));
}
return $agentAllowPassed;
}
private function checkIsDenied($peerId, $agent, $familyId)
{
$cacheKey = nexus_env("CACHE_KEY_AGENT_DENY", "all_agent_denies") . ":php";
/** @var Collection $allDenies */
$allDenies = NexusDB::remember($cacheKey, 3600, function () {
return AgentDeny::query()->get()->groupBy('family_id');
});
$agentDenies = $allDenies->get($familyId, []);
foreach ($agentDenies as $agentDeny) {
if ($agentDeny->agent == $agent && preg_match("/^" . $agentDeny->peer_id . "/", $peerId)) {
return $agentDeny;
}
}
}
/**
* check peer_id or agent is allowed
*
* 0: not allowed
* 1: allowed
* 2: version too low
*
* @param $pattern
* @param $start
* @param $matchNum
* @param $matchType
* @param $value
* @param bool $debug
* @param string $logPrefix
* @return int
* @throws ClientNotAllowedException
*/
private function isAllowed($pattern, $start, $matchNum, $matchType, $value, $debug = false, $logPrefix = ''): int
{
$matchBench = $this->getPatternMatches($pattern, $start, $matchNum);
if ($debug) {
do_log("$logPrefix, matchBench: " . nexus_json_encode($matchBench));
}
if (!preg_match($pattern, $value, $matchTarget)) {
if ($debug) {
do_log(sprintf("$logPrefix, pattern: (%s) not match: (%s)", $pattern, $value));
}
return 0;
}
if ($matchNum <= 0) {
return 1;
}
$matchTarget = array_slice($matchTarget, 1);
if ($debug) {
do_log("$logPrefix, matchTarget: " . nexus_json_encode($matchTarget));
}
for ($i = 0; $i < $matchNum; $i++) {
if (!isset($matchBench[$i]) || !isset($matchTarget[$i])) {
break;
}
if ($matchType == 'dec') {
$matchBench[$i] = intval($matchBench[$i]);
$matchTarget[$i] = intval($matchTarget[$i]);
} elseif ($matchType == 'hex') {
$matchBench[$i] = hexdec($matchBench[$i]);
$matchTarget[$i] = hexdec($matchTarget[$i]);
} else {
throw new ClientNotAllowedException(sprintf("Invalid match type: %s", $matchType));
}
if ($matchTarget[$i] > $matchBench[$i]) {
//higher, pass directly
return 1;
} elseif ($matchTarget[$i] < $matchBench[$i]) {
return 2;
}
}
//NOTE: at last, after all position checked, not [NOT_MATCH] or lower, it is passed!
return 1;
}
public function checkClientSimple($peerId, $agent, $debug = false)
{
//check from high version to low version, if high version allow, stop!
$cacheKey = nexus_env("CACHE_KEY_AGENT_ALLOW", "all_agent_allows") . ":php";
$allows = NexusDB::remember($cacheKey, 3600, function () {
return AgentAllow::query()
->orderBy('peer_id_start', 'desc')
->orderBy('agent_start', 'desc')
->get();
});
$agentAllowPassed = null;
foreach ($allows as $agentAllow) {
$agentAllowId = $agentAllow->id;
$agentAllowLogPrefix = "[ID: $agentAllowId], peerId: $peerId";
$pattern = $agentAllow->peer_id_pattern;
//check peer_id, when handle scrape request, no peer_id, so let it pass
$isPeerIdAllowed = empty($pattern) || preg_match($pattern, $peerId);
$agentAllowLogPrefix .= ", peer_id pattern: $pattern, isPeerIdAllowed: $isPeerIdAllowed";
//check agent, agent must have both announce + scrape
$pattern = $agentAllow->agent_pattern;
$isAgentAllowed = !empty($pattern) && preg_match($pattern, $agent);
$agentAllowLogPrefix .= ", agent pattern: $pattern, isAgentAllowed: $isAgentAllowed";
//both OK, passed, client is allowed
if ($isPeerIdAllowed && $isAgentAllowed) {
$agentAllowPassed = $agentAllow;
do_log("$agentAllowLogPrefix, PASSED", 'debug');
break;
}
if ($debug) {
do_log("$agentAllowLogPrefix, NOT PASSED", 'debug');
}
}
if (!$agentAllowPassed) {
throw new ClientNotAllowedException("Banned Client, Please goto " . getSchemeAndHttpHost() . "/faq.php#id29 for a list of acceptable clients");
}
if ($debug) {
do_log("agentAllowPassed: " . $agentAllowPassed->toJson(), 'debug');
}
// check if exclude
if ($agentAllowPassed->exception == 'yes') {
$agentDeny = $this->checkIsDenied($peerId, $agent, $agentAllowPassed->id);
if ($agentDeny) {
if ($debug) {
do_log("agentDeny: " . $agentDeny->toJson());
}
throw new ClientNotAllowedException(sprintf(
"[%s-%s]Client: %s is banned due to: %s",
$agentAllowPassed->id, $agentDeny->id, $agentDeny->name, $agentDeny->comment
));
}
}
if (isHttps() && $agentAllowPassed->allowhttps != 'yes') {
throw new ClientNotAllowedException(sprintf(
"[%s]This client does not support https well, Please goto %s/faq.php#id29 for a list of proper clients",
$agentAllowPassed->id, getSchemeAndHttpHost()
));
}
return $agentAllowPassed;
}
}