dev_endboard/opt/bot.php

619 lines
26 KiB
PHP

<?php
/*
* This is the endboard software, version beta 0.73
* It is a textboard written for the use in the darknets.
*
* This file holds all the functions used to deal with bots. It can be
* included without side effects.
*
* The writing of this code started some time ago with another software
* called smolBBS. Although there is almost no original code left now,
* I still regard endboard as a fork of smolBBS.
* The author of smolBBS has required that the following text be
* distributed with any redistribution, so here it goes.
* The license and other conditions apply to endboard as well.
*
* IRC: *dulm @ irc.rizon.net
*
* Copyright (C) 2020 sandlind
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* (1) Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* (2) Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* (3)The name of the author may not be used to
* endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
// checks if the bot trap has been called recently from the ip.
// according to the parameters in the config file, the request is then
// either granted or blocked.
// if the blocking of tor is enabled, 127.0.0.1 will be included in the
// blocking, which can mean that no connections from tor or local are taken
// during the block time.
function bot_block($db, $settings, $ip)
{
if ( ($settings['enable_bot_block'] != TRUE) ) {
return;
}
if ( ($settings['enable_tor_block'] != TRUE)
&& ($_SERVER['REMOTE_ADDR'] == '127.0.0.1') ) {
return;
}
$current = time();
$max_age = $current - ($settings['block_time'] * 60);
// max age is in minutes, so times 60 to go to seconds
if ($settings['superstrict_block'] == TRUE) {
$statement = $db->prepare("SELECT unix_timestamp
FROM logs
WHERE type = 'bot'
AND ip = '$ip'
AND event in ('Level 2',
'Level 1',
'429')
AND unix_timestamp > '$max_age'");
} else {
$statement = $db->prepare("SELECT unix_timestamp
FROM logs
WHERE type = 'bot'
AND ip = '$ip'
AND event in ('Level 2',
'Level 1')
AND unix_timestamp > '$max_age'");
}
$result = $statement->execute();
$trap_visits = 0;
while ($row = $result->fetchArray(SQLITE3_NUM)) {
$trap_visits++;
}
if ( ($trap_visits > $settings['max_trap_visits']) ) {
$bot_block_message = '429';
log_event($db, $settings, 'bot', $bot_block_message, $ip);
header( 'HTTP/1.1 429 Too Many Requests' );
quit($db, '429');
}
if ( ($settings['max_landing'] > 0)
&& ($settings['superstrict_block'] == TRUE) ) {
$statement = $db->prepare("SELECT unix_timestamp
FROM logs
WHERE type = 'bot'
AND ip = '$ip'
AND event in ('landing page bot request',
'429')
AND unix_timestamp > '$max_age'");
} elseif ( ($settings['max_landing'] > 0) ) {
$statement = $db->prepare("SELECT unix_timestamp
FROM logs
WHERE type = 'bot'
AND ip = '$ip'
AND event = 'landing page bot request'
AND unix_timestamp > '$max_age'");
} else {
return;
}
$result = $statement->execute();
$landing_visits = 0;
while ($row = $result->fetchArray(SQLITE3_NUM)) {
$landing_visits++;
}
if ( ($landing_visits > $settings['max_landing']) ) {
$bot_block_message = '429';
log_event($db, $settings, 'bot', $bot_block_message, $ip);
header( 'HTTP/1.1 429 Too Many Requests' );
quit($db, '429');
}
return;
}
// receive a message from a bot, check if it's ok to post, and do it (or quit)
function bot_me($db, $settings)
{
$json_data = json_decode(file_get_contents('php://input'), TRUE);
if ( (empty($json_data['text'])) || (empty($json_data['sub'])) ) {
header( 'HTTP/1.1 400 Bad Request' );
quit($db,'400');
}
$sub = filter($json_data['sub'], 'alnum', $settings['max_name_sub']);
if (!in_array($sub, $settings['anonymous_bot_subs'])) {
if (!empty($json_data['key'])) {
$key = filter($json_data['key'], 'alnum', 20);
// 20 chars is enough for a bot key
if (!in_array($key, $settings['bot_keys'])) {
$auth_message = 'bot used wrong key';
log_event($db, $settings, 'auth', $auth_message, '');
sleep(10);
header( 'HTTP/1.1 401 Unauthorized' );
quit($db,'401');
}
} else {
header( 'HTTP/1.1 400 Bad Request' );
quit($db,'400');
}
}
if (!empty($json_data['org_id'])) {
$org_id = filter($json_data['org_id'], 'alnum',
$settings['max_name_sub']);
} else {
$org_id = '';
}
$text = strip_tags($json_data['text']);
check_spam($db, $text, $settings);
$text_id = hash('sha512', $text);
if ( (check_original_content
($db, $settings, $sub, $text_id, $org_id) == FALSE) ) {
header( 'HTTP/1.1 403 Forbidden' );
$content_message = 'This text has been posted before, the admin'
. ' requests original content.';
quit($db, $content_message);
}
make_post($db, $sub, $settings, $text, $org_id, '');
}
// check if the ip has already passed the portal, in this case return.
// if not, display a simple text and button to click to proceed.
// the page displayed is done with inline styling, so that no
// additional files will be requested.
// Update: what started with the checking of the ip, has now expanded to
// up to six parameters, which are concatenated and hashed.
function check_portal($db, $settings, $ip)
{
if ( ($settings['enable_portal'] != TRUE) ) {
return;
}
if ( ($settings['enable_portal_tor'] != TRUE)
&& ($_SERVER['REMOTE_ADDR'] == '127.0.0.1') ) {
return;
}
$current = time();
$max_age = $current - ($settings['portal_lifetime'] * 60);
// lifetime is in minutes, so times 60 to go to seconds
if ($settings['auto_prolong_portal'] == TRUE) {
$statement = $db->prepare("SELECT unix_timestamp
FROM logs
WHERE type = 'portal'
AND ip = '$ip'
AND event in ('pass',
'visit')
AND unix_timestamp > '$max_age'
ORDER BY ROWID DESC LIMIT 1");
} else {
$statement = $db->prepare("SELECT unix_timestamp
FROM logs
WHERE type = 'portal'
AND ip = '$ip'
AND event in ('pass')
AND unix_timestamp > '$max_age'
ORDER BY ROWID DESC LIMIT 1");
}
$result = $statement->execute();
$portal_pass = 0;
while ($row = $result->fetchArray(SQLITE3_NUM)) {
$portal_pass++;
}
if ( ($portal_pass > 0) ) {
// bigger zero means we have the last hit, meaning the ip
// is known
$portal_message = 'visit';
log_event($db, $settings, "portal", $portal_message, $ip);
return;
} else {
$random_string = make_token(20, 'alpha');
// $request = '/' . $random_string;
$request = $_SERVER['REQUEST_URI'] . $random_string;
header( 'HTTP/1.1 202 Accepted' );
header( 'Cache-Control: no-store', FALSE );
$html_string = "<!DOCTYPE html><html><head><style>"
. "body {font-size:30px;background-color: black;"
. "color: #33cccc;text-align: center;width: 30em;"
. "margin-left: auto;margin-right: auto;}"
. "input[type=submit] {padding:5px 15px;"
. "font-size:25px;"
. "background-color: black;"
. "color: #33cccc;"
. "cursor:pointer;"
. "border: 1px solid #11bbcc;"
. "-webkit-border-radius: 5px;"
. "border-radius: 5px;}"
. "</style></head><body><br><br><br>"
. "<code>Entry portal: "
. "Please click the button to proceed.</code>"
. "<br><br><br><br><br><br><div class='form'>"
. "<form action='/ep' method='post'>"
. "<input type='hidden' name='portal' value='$request'>"
. "<input type='submit' value='Enter'><br>"
. "</form></div>"
. "<br><br><br><br><br><br><code>Sorry for this,"
. " it's just a lowlevel protection "
. "against scraping bots.</code></body></html>";
echo "$html_string";
quit($db, '');
}
}
// If enabled, inserts an invisible link, leading to the bot tarpit
function lay_trap($settings)
{
if ( ($settings['enable_bot_trap'] == TRUE) ) {
$triggers = array(
'tr', 'login', 'wellknown',
'wp-login', 'wp-json', 'wp',
'products', 'wp-users', 'wp-admin',
'wp-adminer', 'adminer', 'php-myadmin',
'wp-uploads', 'wp-content', 'wp-config',
'wp-includes', 'static', 'img',
'images', 'uploads', 'styles',
'style', 'server-info', 'private_key',
'server-status'
);
$count_triggers = count($triggers) - 1;
$fake_link = $triggers[rand(0, $count_triggers)];
echo "<div style=\"display:none\"><a href=/$fake_link></a></div>";
}
}
// checks if posts from bots can be received or not
function post_block_bot($db, $settings, $visitor_ip)
{
$current = time();
$max_age = $current - ($settings['max_post_timeframe'] * 60);
// the number from settings is in minutes, so times 60 for secs
if ( ($settings['max_post_global'] > 0) ) {
$statement = $db->prepare("SELECT unix_timestamp
FROM logs
WHERE type in ('bot', 'user')
AND event = 'post attempt'
AND unix_timestamp > '$max_age'");
$result = $statement->execute();
$counter = 0;
while ($row = $result->fetchArray(SQLITE3_NUM)) {
$counter++;
}
if ( ($counter > $settings['max_post_global']) ) {
return FALSE;
}
}
if ( ($settings['max_post_ip'] > 0) ) {
$statement = $db->prepare("SELECT unix_timestamp
FROM logs
WHERE type in ('bot', 'user')
AND event = 'post attempt'
AND ip = '$visitor_ip'
AND unix_timestamp > '$max_age'");
$result = $statement->execute();
$counter = 0;
while ($row = $result->fetchArray(SQLITE3_NUM)) {
$counter++;
}
if ( ($counter > $settings['max_post_ip']) ) {
return FALSE;
}
}
if ( ($settings['max_post_bot'] > 0) ) {
$statement = $db->prepare("SELECT unix_timestamp
FROM logs
WHERE type = 'bot'
AND event = 'post attempt'
AND unix_timestamp > '$max_age'");
$result = $statement->execute();
$counter = 0;
while ($row = $result->fetchArray(SQLITE3_NUM)) {
$counter++;
}
if ( ($counter > $settings['max_post_bot']) ) {
return FALSE;
}
}
return TRUE;
}
// brings the user to the actual target that was requested before the
// portal took over
function redirect_target()
{
$target = $_POST['portal'];
header( "refresh:0;url=$target" );
$html_string = "<!DOCTYPE html><html><head><style>"
. "body {font-size:25px;"
. "background-color: black;"
. "color: #33cccc;"
. "text-align: center;"
. "width: 30em;"
. "margin-left: auto;"
. "margin-right: auto;}"
. "a:link, a:visited {color: red;}"
. "</style></head><body><br><br><br><code>"
. "Redirection ongoing. If that does not work, click"
. " <a href='$target'>here</a>.</code>";
echo "$html_string";
}
// Feed garbage links and texts to bots that follow invisible links.
// Feed even more garbage if the bot follows the garbage links.
// Feel free to add other variants in the arrays.
// There are nearly 10 billion possible links, and ca. 550 million
// possible text combinations. For some bots this is overdose.
function trap_me($db, $settings, $bot_ip)
{
$noun = array(
'Hermione', 'Gaius Cactus', 'The maniac',
'The orange menace', 'The fool on the hill', 'Captain Hook',
'Captain Futuro', 'Chaplain Miller', 'The ambassador of Fuggia',
'The space pope', 'An unremarkable stone', 'The three little piglets',
'The giant panda', 'Bickus Dickus', 'Incontenencia Buttocks',
'The phantom of the opera', 'Granny Smithi', 'Conani',
'Pippyn Longstockings', 'The ghost of Alberto Einsteino', 'The girl',
'The boy', 'Mr. Buourns', 'The turtle',
'Humpty-Trumpty', 'The elephant', 'The bear',
'The pope', 'Oxymandias', 'The pied piper',
'Sandor Bollocks', 'Sir Poosalot', 'The man',
'The octopus', 'Wondery Woman', 'The woman',
'Supiman', 'Spidyman', 'Bylbo Bagman',
'The eggman', 'The walrus', 'Battyman',
'Dumbledutt', 'Gullom', 'The cyberfuck',
'The lion', 'The lion king', 'An oldtimer from Tennesse',
'The alligator', 'Micky Moose', 'Marlon Brandoff',
'Suedwester Stallone', 'A bag full of hot air', 'A murder of crows',
'A litter of kittens', 'Some teeny-tiny gooslings', 'Donald Duckling',
'Wallaby', 'All the kings man', 'The rhino',
'The dingo'
);
$count_noun = count($noun) - 1;
$verb = array(
'deducted', 'wondered', 'sat on the wall',
'made a big fall', 'devoured eight boy scouts', 'played chess',
'kissed me in the dark', 'played football', 'played the flute',
'played the violin', 'self-abused', 'smoked some crack',
'breakdanced', 'icedanced', 'sneezed',
'lost their cool', 'got engaged', 'got clipped',
'jumped', 'dozed off', 'ran away',
'spit flames', 'got tarred and feathered','consumed several boars',
'drank the blood of six sheep', 'engaged in all-open unarmed combat',
'managed', 'mounted an icebear',
'brought life into the world', 'grinned',
'ran', 'machine-gunned some sandbags', 'made her voice heard',
'did some weed', 'walked', 'crawled',
'fought', 'was gung-ho', 'farted',
'bullwhipped', 'ate some icecream', 'let one fly',
'danced', 'sharpened their teeth',
'moved to the sound of music', 'waited', 'burped',
'manhandled Cesaro Romao', 'inhaled some DNA altering fumes',
'knocked out the oppressor', 'burst in tears', 'imitated Ronald Regan',
'goggled'
);
$count_verb = count($verb) - 1;
$adverb = array(
'fast', 'slowly', 'well', 'manly', 'badly',
'angrily', 'loudly', 'lewldy', 'in a flexible way',
'ignoring the grammar', 'with utter disrespect',
'quietly', 'in the way of the old days',
'gently, but firmly', 'very abruptly', 'in a textbook fashion',
'in a heated atmosphere', 'very concentrated',
'ok', 'with the speed of light cigarettes',
'during the better part of the day','nervously, as if under pressure',
'dreamily', 'with the elegance of a manatee',
'with the grace of a crap eating fly','with the speed of a slow bird',
'with a lot of mediocraty', 'ignoring the danger like a true hero',
'seeking distraction', 'like they do in the movies',
'fast, compared to an ant,', 'slow, almost mechanical',
'as if gravity was void', 'as if it was fate',
'in days of yore', 'from dusk till dawn',
'in the middle of the day', 'during quiet hours',
'before the break of dawn', 'momentarily',
'like crazy', 'like fuck'
);
$count_adverb = count($adverb) - 1;
$finish = array(
'on the emerald isle.', 'in space.', 'in the woods',
'on the bottom of the ocean.', 'in the Kalahari',
'in Siam', 'on a Tetris board.',
'where the sun don\'t shine.', 'in Oklahoma.',
'in Brazil.', 'by the sea',
'in the Cristall Palace','near the pigsty',
'close to the magnetic north pole.',
'closer than you might think.', 'in New York.',
'on the internet.', 'on mars.', 'more to the right.',
'on the Canaries', 'in Askuban.', 'to the cellar.',
'in the english garden.', 'on the sea.', 'in the living room.',
'down by the docks.', 'on twitter, now shit.',
'on the tennis court.', 'in the ceiling.', 'in the street.',
'in the streets of London.', 'in the presence of his noodleness.',
'for the fame of Eris.', 'in an alley.',
'during a replubican convention.', 'on the roof.',
'in my neighbors garden.', 'on the plane.', 'in the car.',
'on the train.', 'in the forest.'
);
$count_finish = count($finish) - 1;
$reply = array(
'What can you do ?', 'How is this possible ?',
'And it\'s all your fault.', 'And it\'s all my fault.',
'Who will be paying for that ?',
'And who is to blame, that\'s what I want to know.',
'And the blame is on us all.', 'I need some booze now.',
'I need a smoke.', 'I pity the fool who finds this funny !',
'These are the times we live in.','And what is wrong with that ?',
'How do you do ?', 'God shave the queen.',
'For fucks sake, not again',
'And that\'s exactly why we need a union.',
'Read it in the scriptures, if you don\'t believe me.',
'And this was not the first time.',
'Happens more often than you\'d think.',
'Why ? Just why ?', 'Technology will solve it all.',
'Progress will resolve it all.', 'The prophet saw this coming.',
'Many have tried, Many have tried and failed....',
'I say blimey.', 'I have said it before, we need more weed.',
'Clearly, we need more surveillance.',
'Nuke it from space, I say.',
'There are not enough catholics in this world.',
'Can you imagine ?', 'Well, I would never !',
'I had never seen such a thing !',
'Shocked, I watched the events unfold.',
'I could not believe my eyes.',
'So anyway, I says to Mabel, Mabel, I says...',
'What would Jebus do ?', 'Here\'s Tom with the weather.',
'Buy more bullets.', 'And that is not ok.',
'Finally.', 'Airstrike, now.',
'More after the commercial.', 'Back to you, Morbot.',
'Do I have no rights at all ?', 'What is the world coming to ?',
'To shreds, you say ?', 'And that made me feel very sad.',
'And that made me feel very horny.',
'Too beaucoup, too ... beaucoup.', 'Can you dig it ?',
'Feel me ?',
'Will you be able to explain this ?',
'My neighbor foretold it. He\'s good at that.',
'One more beer could not hurt, I thought.',
'"At this time of year ?" I thought to myself.',
'Buy more toilet paper.', 'We need more rocks.',
'Get them young, I say.',
'And if that\'s not good enough for you, I don\'t know what is',
'Polar bears are mens best friends.', 'Kitties are cuddly.',
'My dog can fart really loud.',
'And we will never hear the end of it.', 'Really ?',
'And I blame todays society.'
);
$count_reply = count($reply) - 1;
$triggers = array(
'tr', 'login', 'wellknown',
'wp-login', 'wp-json', 'wp',
'products', 'wp-users', 'wp-admin',
'wp-adminer', 'adminer', 'php-myadmin',
'wp-uploads', 'wp-content', 'wp-config',
'wp-includes', 'static', 'img',
'images', 'uploads', 'styles',
'style', 'server-info', 'private_key',
'server-status'
);
$count_triggers = count($triggers) - 1;
$link = read_pretty_vars("last", 'number', 1);
// we take the first digit of the link
// to check if this is a first time visitor
if ($link > 0) {
// if the bot followed a link, feed more garbage
$fake_loops = rand(500, 1000);
// 500 ... 1000 garbage links should do it
$log_message = "Level 2";
log_event($db, $settings, 'bot', $log_message, $bot_ip);
} else {
$fake_loops = rand(10, 20);
// just send 10...20 garbage links
// to first time visitors
$log_message = "Level 1";
log_event($db, $settings, 'bot', $log_message, $bot_ip);
}
$html_string = '<title> bot tarpit </title>'
. '<h1>in case you are a normal visitor, please note that '
. 'this page has no actual content,'
. ' but is just build to annoy crawling '
. 'bots that disrespect robots.txt</h1>';
if ( ($settings['enable_bot_block'] == TRUE) ) {
$html_string .= '<h1>Don\'t follow the links,'
. ' and don\'t visit this page again,'
. ' or you might get blocked.</h1>';
}
for ($fake=0; $fake<$fake_loops; $fake++) {
$line = $noun[rand(0, $count_noun)] . ' ' .
$verb[rand(0, $count_verb)] . ' ' .
$adverb[rand(0, $count_adverb)] . ' ' .
$finish[rand(0, $count_finish)] . ' ' .
$reply[rand(0, $count_reply)];
$fake_link = $triggers[rand(0, $count_triggers)];
$post_text = "<a href=/$fake_link/" . rand() . ">$line</a></div>";
$html_string .= "<br><br>$post_text<br><br>";
}
$html_string .= '</html>';
echo "$html_string";
}
// EOF