diff --git a/app/Console/Commands/LoadSpam.php b/app/Console/Commands/LoadSpam.php new file mode 100644 index 0000000000000000000000000000000000000000..fbe636da3d7434ba6866fd18530ea44ccff6ba6b --- /dev/null +++ b/app/Console/Commands/LoadSpam.php @@ -0,0 +1,64 @@ +<?php + +namespace App\Console\Commands; + +use Carbon; +use Illuminate\Console\Command; +use Illuminate\Support\Facades\Redis; + +class LoadSpam extends Command +{ + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'spam:load'; + + /** + * The console command description. + * + * @var string + */ + protected $description = 'Loads a list of current Spams into redis'; + + /** + * Create a new command instance. + * + * @return void + */ + public function __construct() + { + parent::__construct(); + } + + /** + * Execute the console command. + * + * @return mixed + */ + public function handle() + { + $filePath = \storage_path('logs/metager/ban.txt'); + $bans = []; + if (\file_exists($filePath)) { + $bans = json_decode(file_get_contents($filePath), true); + } + + $bansToLoad = []; + + foreach ($bans as $ban) { + $bannedUntil = Carbon::createFromFormat("Y-m-d H:i:s", $ban["banned-until"]); + if ($bannedUntil->isAfter(Carbon::now())) { + $bansToLoad[] = $ban["regexp"]; + } + } + + Redis::pipeline(function ($redis) use ($bansToLoad) { + $redis->del("spam"); + foreach ($bansToLoad as $ban) { + $redis->rpush("spam", $ban); + } + }); + } +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 51233aaaee9bbf7bc976113e4e7bdbe32ca99a1b..65b76241cde59edb99cd5b29642e2bf72ca5eefc 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -28,7 +28,7 @@ class Kernel extends ConsoleKernel $schedule->command('requests:gather')->everyFifteenMinutes(); $schedule->command('requests:useragents')->everyFiveMinutes(); $schedule->command('logs:gather')->everyMinute(); - + $schedule->command('spam:load')->everyFiveMinutes(); $schedule->call(function () { DB::table('monthlyrequests')->truncate(); DB::disconnect('mysql'); diff --git a/app/Http/Controllers/AdminSpamController.php b/app/Http/Controllers/AdminSpamController.php new file mode 100644 index 0000000000000000000000000000000000000000..e4a514ce347b6dadf4141353316a7b315c187b37 --- /dev/null +++ b/app/Http/Controllers/AdminSpamController.php @@ -0,0 +1,145 @@ +<?php + +namespace App\Http\Controllers; + +use Carbon; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Redis; + +class AdminSpamController extends Controller +{ + public function index() + { + $queries = $this->getQueries(); + + $currentBans = $this->getBans(); + $loadedBans = Redis::lrange("spam", 0, -1); + + return view("admin.spam") + ->with('title', "Spam Konfiguration - MetaGer") + ->with('queries', $queries) + ->with('bans', $currentBans) + ->with('loadedBans', $loadedBans); + } + + public function ban(Request $request) + { + $banTime = $request->input('ban-time'); + $banRegexp = $request->input('regexp'); + + $file = storage_path('logs/metager/ban.txt'); + + $bans = []; + if (file_exists($file)) { + $bans = json_decode(file_get_contents($file), true); + } + + $bans[] = ["banned-until" => Carbon::now()->add($banTime)->format("Y-m-d H:i:s"), "regexp" => $banRegexp]; + + \file_put_contents($file, json_encode($bans)); + + return redirect(url('admin/spam')); + } + + public function jsonQueries() + { + $queries = $this->getQueries(); + return response()->json($queries); + } + + public function queryregexp(Request $request) + { + $data = json_decode($request->getContent(), true); + $queries = $data["queries"]; + $regexps = [$data["regexp"]]; + + $bans = $this->getBans(); + foreach ($bans as $ban) { + $regexps[] = $ban["regexp"]; + } + + $resultData = []; + + foreach ($queries as $query) { + $matches = false; + foreach ($regexps as $regexp) { + try { + if (preg_match($regexp, $query)) { + $matches = true; + } + } catch (\Exception $e) { + // Exceptions are expected when no valid regexp is given + } + } + $resultData[] = [ + "query" => $query, + "matches" => $matches, + ]; + } + + return response()->json($resultData); + + } + + private function getQueries() + { + $minuteToFetch = Carbon::now()->subMinutes(2); + $logFile = storage_path("logs/metager/" . $minuteToFetch->format("Y/m/d") . ".log"); + + $result = shell_exec("cat $logFile | grep " . $minuteToFetch->format("H:i:")); + $result = explode(PHP_EOL, $result); + + $queries = array(); + + foreach ($result as $line) { + if ($query = \preg_match("/.*eingabe=(.*)$/", $line, $matches)) { + $queries[] = $matches[1]; + } + } + return $queries; + } + + public function getBans() + { + $file = \storage_path('logs/metager/ban.txt'); + $bans = []; + + if (file_exists($file)) { + $tmpBans = json_decode(file_get_contents($file), true); + + foreach ($tmpBans as $ban) { + #dd($ban["banned-until"]); + $bannedUntil = Carbon::createFromFormat('Y-m-d H:i:s', $ban["banned-until"]); + if ($bannedUntil->isAfter(Carbon::now())) { + $bans[] = $ban; + } + } + } + + file_put_contents($file, json_encode($bans)); + + return $bans; + } + + public function deleteRegexp(Request $request) + { + $file = \storage_path('logs/metager/ban.txt'); + $bans = []; + + if (file_exists($file)) { + $bans = json_decode(file_get_contents($file), true); + } + + $regexpToDelete = $request->input('regexp'); + $newBans = []; + + foreach ($bans as $ban) { + if ($ban["regexp"] !== $regexpToDelete) { + $newBans[] = $ban; + } + } + + file_put_contents($file, json_encode($newBans)); + return redirect(url('admin/spam')); + } +} diff --git a/app/Http/Controllers/HumanVerification.php b/app/Http/Controllers/HumanVerification.php index 6b979855358f80b1bd2b195417e9a3d8f0e8899b..741e55fef5a42d6e28090e6660d2f9e1488b560d 100644 --- a/app/Http/Controllers/HumanVerification.php +++ b/app/Http/Controllers/HumanVerification.php @@ -7,6 +7,7 @@ use Carbon; use Illuminate\Hashing\BcryptHasher as Hasher; use Illuminate\Http\Request; use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\Redis; use Input; class HumanVerification extends Controller @@ -198,20 +199,16 @@ class HumanVerification extends Controller public static function couldBeSpammer($ip) { - $possibleSpammer = false; - # Check for recent Spams $eingabe = \Request::input('eingabe'); - if (\preg_match("/^susimail\s+-site:[^\s]+\s-site:/si", $eingabe)) { - return true; - } else if (\preg_match("/^\s*site:\"linkedin\.com[^\"]*\"\s+/si", $eingabe)) { - return true; - } else if (\preg_match("/^\d+.(php|asp)\s+\?.*=.*/si", $eingabe)) { - return true; + $spams = Redis::lrange("spam", 0, -1); + foreach ($spams as $spam) { + if (\preg_match($spam, $eingabe)) { + return true; + } } - return $possibleSpammer; - + return false; } public function botOverview(Request $request) diff --git a/resources/views/admin/spam.blade.php b/resources/views/admin/spam.blade.php new file mode 100644 index 0000000000000000000000000000000000000000..ffcdfe2e98fc859a3ae351397c9d8bb76a21fd1e --- /dev/null +++ b/resources/views/admin/spam.blade.php @@ -0,0 +1,198 @@ +@extends('layouts.subPages') + +@section('title', $title ) + +@section('content') +<style> + #head { + display: flex; + align-items: center; + margin-bottom: 16px; + } + #head > button { + margin-left: 16px; + } + #head > h1 { + margin: 0; + } + #queries { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + } + + .matches { + background-color: #c9f4c9; + } + #block-requests { + margin-bottom: 16px; + } + #regexp { + margin-bottom: 8px; + } + #ban-time { + margin-bottom: 8px; + } +</style> +<div id="block-requests"> + <form method="post"> + <input class="form-control" type="text" name="regexp" id="regexp" placeholder="Type in regexp to match queries..."> + <select name="ban-time" id="ban-time" class="form-control"> + <option value="1 day">Einen Tag</option> + <option value="1 week">Eine Woche</option> + <option value="2 weeks">Zwei Wochen</option> + <option value="1 month" selected>Einen Monat</option> + </select> + <button type="submit" class="btn btn-default btn-sm">Sperren</button> + </form> +</div> +<div id="head"> + <h1>Letzte Suchanfragen</h1> + <button type="button" class="btn btn-success btn-sm">Aktualisierung stoppen (60)</button> +</div> +<input class="form-control" type="text" name="" id="check-against" placeholder="Match against..."> +<div id="queries"> + @foreach($queries as $query) + <div class="query card">{{$query}}</div> + @endforeach +</div> +<div id="bans"> + <h1>Current Bans</h1> + <table class="table table-striped"> + <thead> + <tr> + <td>Regexp</td> + <td>Banned until</td> + <td>Actions</td> + </tr> + </thead> + <tbody> + @foreach($bans as $ban) + <tr> + <td>{{ $ban["regexp"] }}</td> + <td>{{ Carbon::createFromFormat("Y-m-d H:i:s", $ban["banned-until"])->format("d.m.Y H:i:s")}} ({{ Carbon::createFromFormat("Y-m-d H:i:s", $ban["banned-until"])->diffInDays(Carbon::now()) }} Days)</td> + <td> + <form action="{{ url("admin/spam/deleteRegexp") }}" method="post"> + <input type="hidden" name="regexp" value="{{ $ban["regexp"] }}"> + <button type="submit">🗑</button> + </form> + </td> + </tr> + @endforeach + </tbody> + </table> +</div> +<div id="loadedbans"> + <h1>Loaded Bans</h1> + <table class="table table-striped"> + <thead> + <tr> + <td>Regexp</td> + </tr> + </thead> + <tbody> + @foreach($loadedBans as $ban) + <tr> + <td>{{ $ban }}</td> + </tr> + @endforeach + </tbody> + </table> +</div> +<script> + var lastUpdate = Date.now(); + var updating = true; + var buttonText = "Aktualisierung stoppen"; + var interval = setInterval(updateQueries, 1000); + $("#regexp").on("input", checkRegexp); + $("#check-against").on("input", checkRegexp); + $(document).ready(function(){ + checkRegexp(); + }); + + + $("#head > button").click(function() { + if(!updating) { + $("#head > button").removeClass("btn-danger"); + $("#head > button").addClass("btn-success"); + buttonText = "Aktualisierung stoppen"; + interval = setInterval(updateQueries, 1000); + } + var updateAt = lastUpdate + 60000; + var updateIn = Math.round((updateAt - Date.now()) / 1000); + $("#head > button").html(buttonText + " (" + updateIn + ")"); + updating = !updating; + }); + + function updateQueries() { + var updateAt = lastUpdate + 60000; + var updateIn = Math.round((updateAt - Date.now()) / 1000); + + if(!updating){ + $("#head > button").removeClass("btn-success"); + $("#head > button").addClass("btn-danger"); + buttonText = "Aktualisierung starten"; + clearInterval(interval); + } + + $("#head > button").html(buttonText + " (" + updateIn + ")"); + if(updateAt > Date.now()){ + return; + } + fetch("{{ url('admin/spam/jsonQueries') }}") + .then(response => response.json()) + .then(data => { + $("#queries").html(""); + $(data).each(function(index, el){ + $("#queries").append("<div class=\"query card\">" + el + "</div>"); + }); + lastUpdate = Date.now(); + checkRegexp(); + }); + + } + + + function checkRegexp() { + var val = $("#regexp").val(); + var queries = []; + + + $("#queries > .query").each(function(index, el){ + queries.push($(el).html()); + }); + queries.push($("#check-against").val()); + + var url = "{{ url('admin/spam/queryregexp') }}"; + var options = { + method: 'POST', + body: JSON.stringify({ + "queries": queries, + "regexp": val + }), + headers: { + 'Content-Type': 'application/json' + } + }; + + fetch(url, options) + .then(response => response.json()) + .then(data => { + $("#queries > .query").each(function(index, el){ + if(data[index]["matches"]){ + $(el).addClass("matches"); + }else{ + $(el).removeClass("matches"); + } + }); + if(data[data.length-1]["matches"]){ + $("#check-against").addClass("matches"); + }else{ + $("#check-against").removeClass("matches"); + } + }); + } + + +</script> +@endsection diff --git a/routes/web.php b/routes/web.php index e3c254a46f6b9ca85e1cfd781bba20d0dc4d936c..caf716a5e8aad65efd1db41fcf78f767980d6a89 100644 --- a/routes/web.php +++ b/routes/web.php @@ -182,6 +182,13 @@ Route::group( }); Route::get('bot', 'HumanVerification@botOverview'); Route::post('bot', 'HumanVerification@botOverviewChange'); + Route::group(['prefix' => 'spam'], function () { + Route::get('/', 'AdminSpamController@index'); + Route::post('/', 'AdminSpamController@ban'); + Route::get('jsonQueries', 'AdminSpamController@jsonQueries'); + Route::post('queryregexp', 'AdminSpamController@queryregexp'); + Route::post('deleteRegexp', 'AdminSpamController@deleteRegexp'); + }); }); Route::get('settings', function () {