From 34290f9cff08ad83a0b94e1ee8067ac4857106ad Mon Sep 17 00:00:00 2001 From: fuzzykitten Date: Wed, 5 Feb 2025 09:34:37 -0500 Subject: [PATCH] updated index.php to 0.70 --- srv/index.php | 8767 ++++++++++++++++++++++++------------------------- 1 file changed, 4383 insertions(+), 4384 deletions(-) diff --git a/srv/index.php b/srv/index.php index 1b78050..7be9bd0 100644 --- a/srv/index.php +++ b/srv/index.php @@ -1,4384 +1,4383 @@ -Redirection in about 3 secs."; - $print_string .= " If that does not work, go"; - $print_string .= " back."; - - echo "$print_string"; -} - -// Translate simple bbcode to html, and highlight quotes, like so: -// [b bold],[i italic],[u underlined],[s strikethrough] -// [h headline],[sp spoiler],[li list element],[url link],>>quote\r\n -function bbcode_to_html($text, $settings) -{ - if ( ($settings['enable_bbcode'] == FALSE) ) { - return $text; - } - - $search = array ( - '/(\[b\ )(.*)(\])/', - '/(\[i\ )(.*)(\])/', - '/(\[u\ )(.*)(\])/', - '/(\[s\ )(.*)(\])/', - '/(\[h\ )(.*)(\])/', - '/(\[sp\ )(.*)(\])/', - '/(\[li\ )(.*)(\])/', - '/(\[url\ )(.*)(\])/', - '/>>(.*)\r\n/' - ); - - $replace = array ( - '$2', - '$2', - '$2', - '$2', - '

$2

', - '$2', - '
  • $2
  • ', - '$2', - '>>$1$2
    ' - ); - - return preg_replace($search, $replace, $text); - -} - -// 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 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 ( (!isset($json_data['text'])) || (!isset($json_data['sub'])) ) { - header("HTTP/1.1 400 Bad Request"); - quit($db,"400"); - } - - $sub = substr(preg_replace("/[^0-9a-zA-Z]/", "", $json_data['sub']), - 0, $settings['max_name_sub']); - - if (!in_array($sub, $settings['anonymous_bot_subs'])) { - if (isset($json_data['key'])) { - $key = substr(preg_replace("/[^0-9a-zA-Z]/", "", - $json_data['key']), 0, 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 (isset($json_data['org_id'])) { - $org_id = substr(preg_replace("/[^0-9a-zA-Z]/", "", - $json_data['org_id']), 0, $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"; - $content_message .= " requests original content."; - quit($db, $content_message); - } - - make_post($db, $sub, $settings, $text, $org_id); -} - -// If the post is a reply, put the original post on top. -function bump_message($db, $org_id, $sub) -{ - - $statement = $db->prepare("SELECT text, global_id, text_id - FROM threads - WHERE post_id = '$org_id' - AND org_id = '$org_id' - AND sub = '$sub'"); - $result = $statement->execute(); - - while ($row = $result->fetchArray(SQLITE3_NUM)) { - $text = "{$row[0]}"; - $global_id = "{$row[1]}"; - $text_id = "{$row[2]}"; - } - - $statement = $db->prepare("DELETE FROM threads - WHERE post_id = '$org_id' - AND org_id = '$org_id' - AND sub = '$sub'"); - $result = $statement->execute(); - - $statement = $db->prepare("INSERT INTO threads(post_id, sub, text, - org_id, shadow, - global_id, text_id) - VALUES ('$org_id', '$sub', ?, '$org_id', - 'no', '$global_id', '$text_id')"); - $statement->bindParam(1, $text); - - $statement->execute(); - -} - -// Break text according to config.php, also transform \r\n to
    -function break_text($text, $settings) -{ - - $post_text = wordwrap($text, $settings['line_break'], "\n", TRUE); - $post_text = nl2br($post_text, FALSE); - return $post_text; - -} - -// Lets the admin change the status of a mod or an applicant -function change_mods($db, $css, $settings, $token) -{ - - if ( (isset($_POST['disable_mod'])) ) { - $mod = strip_tags($_POST['disable_mod']); - $statement = $db->prepare("UPDATE keys - SET type = 'disabled' - WHERE name = '$mod'"); - $log_message = "Moderator account $mod was disabled"; - } elseif ( (isset($_POST['enable_mod'])) ) { - $mod = strip_tags($_POST['enable_mod']); - $statement = $db->prepare("UPDATE keys - SET type = 'mod' - WHERE name = '$mod'"); - $log_message = "Moderator account $mod was enabled"; - } elseif ( (isset($_POST['delete_mod'])) ) { - $mod = strip_tags($_POST['delete_mod']); - $statement = $db->prepare("DELETE FROM keys - WHERE name = '$mod'"); - $log_message = "Moderator account $mod was deleted"; - } - - $result = $statement->execute(); - - log_event($db, $settings, "sys", $log_message, ''); - -} - -// Check if the password for the admin is set or not. -// If it was not done already, generate token and write to db and the -// file /var/opt/endboard/admin_$name_token.txt -function check_admin($db, $settings) -{ - - if ( ($settings['admin'] == 'change-me') - || ($settings['admin'] == '') ) { - $config_file = $settings['config_file']; - $admin_message = "

    The name of the admin is not updated "; - $admin_message .= "in the configfile, or it is empty. Set "; - $admin_message .= "the variable \$admin in the file "; - $admin_message .= " $config_file and retry.

    "; - quit($db, $admin_message); - } - - $admin = $settings['admin']; - $filename = $settings['work_dir'] . "admin_" . $admin . "_token.txt"; - - $statement = $db->prepare("SELECT key FROM keys - WHERE type = 'admin' - AND name = '$admin'"); - $result = $statement->execute(); - - $key = ''; - $counter = 0; - - while ($row = $result->fetchArray(SQLITE3_NUM)) { - $key = "{$row[0]}"; - $counter++; - } - - if ( ($key == '') && (!file_exists($filename)) ) { - $token = make_token(); - $token_hash = password_hash($token, PASSWORD_DEFAULT); - - if ($counter == 0) { - $statement = $db->prepare("INSERT INTO keys(token, type, name) - VALUES ('$token_hash', - 'admin', - '$admin')"); - } else { - $statement = $db->prepare("UPDATE keys - SET token = '$token_hash' - WHERE name = '$admin' - AND type = 'admin'"); - } - - $result = $statement->execute(); - - $content = "Token = \r\n$token\r\n"; - file_put_contents($filename, $content); - - return FALSE; - } elseif ( ($key == '') ) { - return FALSE; - } else { - return TRUE; - } - -} - -// Check if access to the admin panel is enabled in the config file. -function check_admin_panel($db, $settings) -{ - - if ($settings['enable_admin_panel'] != TRUE) { - header("HTTP/1.1 403 Forbidden"); - quit($db, "403"); - } - -} - -// Checks if the number of moderators applications is more than 10. -function check_application_count($db) -{ - - $statement = $db->prepare("SELECT name - FROM keys - WHERE type = 'application'"); - $result = $statement->execute(); - - $counter = 0; - - while ($row = $result->fetchArray(SQLITE3_NUM)) { - $counter++; - } - - if ($counter > 10) { - // 10 seems reasonable in order not to loose the overview - return FALSE; - } else { - return TRUE; - } -} - -// Check if access to the admin panel and its functions can be given -// or not. First, a quick check if there is an admin defined, and if -// the default name was changed. Second, the token is checked, if there -// is one. If there is none, user/password is checked against the db. -// If the login is successful, a token is created and given back. -function check_auth_admin($db, $settings) -{ - - $token = read_pretty_vars('last', 'alnum', 250); - // The length of the token is 250 characters - - if ( (mb_strlen($token) !== 250) ) { - // The length of the token is 250 characters - $token = get_post_token(); - } - - if ( (mb_strlen($token) === 250) ) { - // The length of the token is 250 characters - $statement = $db->prepare("SELECT timestamp_token, token - FROM keys - WHERE type = 'admin'"); - $result = $statement->execute(); - - while ($row = $result->fetchArray(SQLITE3_NUM)) { - $timestamp_token = "{$row[0]}"; - $access_token = "{$row[1]}"; - } - - $current = time(); - $max_age = $current - ($settings['lifetime_token'] * 60); - // max lifetime is defined in minutes, times 60 to go to seconds - - if ( ($timestamp_token < $max_age) ) { - $auth_message = "outdated token used"; - log_event($db, $settings, "auth", $auth_message, ''); - $quit_message = "

    Token expired.Log in "; - $quit_message .= "again to get a new one."; - $quit_message .= "This incident was logged.

    "; - quit($db, $quit_message); - } - - if ( (!password_verify($token, $access_token)) ) { - $auth_message = "invalid token used"; - log_event($db, $settings, "auth", $auth_message, ''); - sleep(10); - $quit_message = "

    Invalid token used."; - $quit_message .= "This incident was logged.

    "; - quit($db, $quit_message); - } - - } elseif ( (!isset($_POST['auth_name'])) - || (!isset($_POST['auth_password'])) ) { - $quit_message = "

    Please provide the name and"; - $quit_message .= " password for your account.

    "; - quit($db, $quit_message); - } else { - $auth_name = substr(preg_replace("/[^0-9a-zA-Z]/", "", - $_POST['auth_name']), 0, 50); - // 50 seems to be a generous for the length of the username - $statement = $db->prepare("SELECT key - FROM keys - WHERE type = 'admin' - AND name = '$auth_name'"); - $result = $statement->execute(); - - $key = ''; - - while ($row = $result->fetchArray(SQLITE3_NUM)) { - $key = "{$row[0]}"; - } - - if ( (!password_verify($_POST['auth_password'], $key)) ) { - $auth_message = "wrong combination user/password"; - log_event($db, $settings, "auth", $auth_message, ''); - sleep(10); - $quit_message = "

    Combination of password and username "; - $quit_message .= "is invalid. This incident was logged.

    "; - quit($db, $quit_message); - } - - $token = make_token(); - $token_hash = password_hash($token, PASSWORD_DEFAULT); - - $current = time(); - - $statement = $db->prepare("UPDATE keys - SET timestamp_token = '$current', - token = '$token_hash' - WHERE name = '$auth_name' - AND type = 'admin'"); - $result = $statement->execute(); - - $auth_message = "admin $auth_name logged on"; - log_event($db, $settings, "auth", $auth_message, ''); - } - - return $token; -} - -// Check if access to the mod panel and can be given or not. -// The token is checked, if there is one. If there is none, -// user/password is checked against the db. -// If the login is successful, a token is created and given back. -function check_auth_mod($db, $settings) -{ - - $token = read_pretty_vars('last', 'alnum', 250); - // the length of the token is defined to 250 characters, so that it - // cannot be memorized, - // and so that it does never show fully in the addressbar - - if ( (mb_strlen($token) !== 250) ) { - // token length = 250 - $token = get_post_token(); - } - - if ( (mb_strlen($token) === 250) ) { - // token length = 250 - $statement = $db->prepare("SELECT timestamp_token, token - FROM keys - WHERE type = 'mod'"); - $result = $statement->execute(); - - $found_token = FALSE; - - while ($row = $result->fetchArray(SQLITE3_NUM)) { - $timestamp_token = "{$row[0]}"; - $access_token = "{$row[1]}"; - if ( (password_verify($token, $access_token)) ) { - $found_token = TRUE; - break; - } - } - - if ( ($found_token == FALSE) ) { - $auth_message = "invalid token used"; - log_event($db, $settings, "auth", $auth_message, ''); - quit($db, "

    Token invalid. This incident was logged.

    "); - } - - $current = time(); - $max_age = $current - ($settings['lifetime_token'] * 60); - // lifetime of token is in minutes, times 60 to go to seconds - - if ($timestamp_token < $max_age) { - $auth_message = ("outdated token used"); - log_event($db, $settings, "auth", $auth_message, ''); - $quit_message = "

    Token expired.Log in"; - $quit_message .= " again to get a new one. "; - $quit_message .= "This incident was logged.

    "; - quit($db, $quit_message); - } - } elseif ( (!isset($_POST['auth_name'])) - || (!isset($_POST['auth_password'])) ) { - $quit_message = "

    Please provide the name"; - $quit_message .= " and password for your account.

    "; - quit($db, $quit_message); - } else { - $auth_name = substr(preg_replace("/[^0-9a-zA-Z]/", "", - $_POST['auth_name']), 0, 50); - // 50 seems to be a generous for the length of the username - $statement = $db->prepare("SELECT key FROM keys - WHERE type = 'mod' - AND name = '$auth_name'"); - $result = $statement->execute(); - - $key = ''; - - while ($row = $result->fetchArray(SQLITE3_NUM)) { - $key = "{$row[0]}"; - } - - if ( (!password_verify($_POST['auth_password'], $key)) ) { - $auth_message = "wrong combination user/password"; - log_event($db, $settings, "auth", $auth_message, ''); - sleep(10); - $quit_message = "

    Combination of password and username is"; - $quit_message .= " invalid. This incident was logged.

    "; - quit($db, $quit_message); - } - - $token = make_token(); - $token_hash = password_hash($token, PASSWORD_DEFAULT); - - $current = time(); - - $statement = $db->prepare("UPDATE keys - SET timestamp_token = '$current', - token = '$token_hash' - WHERE name = '$auth_name'"); - $result = $statement->execute(); - - $auth_message = "mod $auth_name logged on"; - log_event($db, $settings, "auth", $auth_message, ''); - } - - return $token; -} - -// Check the hashed captcha against the hashed solutions in the db. -function check_captcha($db, $settings) -{ - if ( (!isset($_POST['post_token'])) ) { - quit($db, "

    What are you up to ? Use the postform.

    "); - } - - if ( ($settings['use_captcha'] == FALSE) ){ - $post_hash = hash("sha512", $_POST['post_token']); - } elseif ( (!isset($_POST['math_one'])) - || (!isset($_POST['math_two'])) - || (!isset($_POST['math_type'])) - || (!isset($_POST['math_answer'])) ) { - quit($db, "

    What are you up to ? Use the postform.

    "); - } else { - $post_summary = ($_POST['math_one'] . $_POST['math_two'] . - $_POST['math_type'] . $_POST['math_answer'] . - $_POST['post_token']); - $post_hash = hash("sha512", $post_summary); - } - - $current = time(); - $max_age = $current - $settings['lifetime_captcha'] * 60 * 60; - // lifetime is in hours in the configfile, - // so times 60 * 60 to go to seconds - - $statement = $db->prepare("SELECT hash, unix_timestamp - FROM captchas - WHERE hash = '$post_hash' - AND unix_timestamp > '$max_age'"); - $result = $statement->execute(); - - $counter = 0; - - while ($row = $result->fetchArray(SQLITE3_NUM)) { - $counter++; - } - - if ( ($settings['use_captcha'] == FALSE) && ($counter < 1) ) { - $quit_message = "

    Unauthorized attempt to post."; - $quit_message .= "Use a newly opened postform.

    "; - quit($db, $quit_message); - } elseif ( ($counter < 1) ) { - quit($db, "

    wrong answer or captcha expired, try again

    "); - } else { - $statement = $db->prepare("DELETE FROM captchas - WHERE hash = '$post_hash'"); - $result = $statement->execute(); - } -} - -// Check if we have enough free space on the harddisk to allow new posts -function check_free_space($db, $settings) -{ - - $free_space = disk_free_space("/"); - - if ($free_space < ($settings['min_space'] * 1024 * 1024)) { - // the setting is in Megabyte, free space operates in bytes - return FALSE; - } - - return TRUE; -} - -// Check if the maximum requests as defined in the config file are -// exhausted or not -function check_max_requests($db, $settings, $ip) -{ - - $current = time(); - - if ( ($settings['max_requests_ip'] > 0) - && ($_SERVER['REMOTE_ADDR'] != '127.0.0.1') ) { - $max_age = $current - ($settings['max_requests_timeframe'] * 60); - // max age is in minutes, so times 60 to go to seconds - $max_visits = $settings['max_requests_ip']; - } elseif ( ($settings['max_requests_tor'] > 0) - && ($_SERVER['REMOTE_ADDR'] == '127.0.0.1') ) { - $max_age = $current - ($settings['max_requests_tor_timeframe'] * 60); - // max age is in minutes, so times 60 to go to seconds - $max_visits = $settings['max_requests_tor']; - } else { - return; - } - - $statement = $db->prepare("SELECT unix_timestamp - FROM logs - WHERE type = 'portal' - AND ip = '$ip' - AND event in ('visit') - AND unix_timestamp > '$max_age'"); - - $result = $statement->execute(); - - $visits = 0; - - while ($row = $result->fetchArray(SQLITE3_NUM)) { - $visits++; - } - - if ( ($visits > $max_visits) ) { - $block_message = "429"; - log_event($db, $settings, "user", $block_message, $ip); - header("HTTP/1.1 429 Too Many Requests"); - quit($db, "429"); - } -} - -// Check if access to the mod panel is enabled in the config file. -function check_mod_panel($db, $settings) -{ - - if ($settings['enable_mod_panel'] != TRUE) { - header("HTTP/1.1 403 Forbidden"); - quit($db, "403"); - } - -} - -// checks if content has been posted before, according the config file. -function check_original_content($db, $settings, $sub, $text_id, $org_id) -{ - $statement = $db->prepare("SELECT post_id, sub, org_id - FROM threads - WHERE text_id = '$text_id'"); - $result = $statement->execute(); - - while ($row = $result->fetchArray(SQLITE3_NUM)) { - $result_post_id = "{$row[0]}"; - $result_sub = "{$row[1]}"; - $result_org_id = "{$row[2]}"; - if ( ($settings['original_content_global'] == TRUE) ){ - return FALSE; - } elseif ( ($settings['original_content_sub'] == TRUE) - && ($sub == $result_sub) ){ - return FALSE; - } elseif ( ($settings['original_content_thread'] == TRUE) - && ($sub == $result_sub) - && ($org_id == $result_org_id) ){ - return FALSE; - } - } - - return TRUE; -} - -// Check if a message actually exists in the database when replying. -// Note that if the links of the site are used for navigation, this is -// always the case (unless it was deleted meanwhile). -// This routine is mostly to prevent malicious users from creating -// ghost messages that are in the db but are never displayed -// (because the threadstart is missing). -function check_org_id_exists($db, $sub, $org_id) -{ - - $statement = $db->prepare("SELECT post_id - FROM threads - WHERE sub = '$sub' - AND shadow = 'no' - AND post_id = '$org_id'"); - $result = $statement->execute(); - - $counter = 0; - - while ($row = $result->fetchArray(SQLITE3_NUM)) { - $counter++; - } - - if ( ($counter < 1) ) { - // if the counter is smaller 1, there is no match - return FALSE; - } else { - return TRUE; - } -} - -// 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 concantenated 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 { - $request = $_SERVER['REQUEST_URI']; - header("HTTP/1.1 202 Accepted"); - $html_string = "


    "; - $html_string .= "Entry portal: "; - $html_string .= "Please click the button to proceed."; - $html_string .= "





    "; - $html_string .= "
    "; - $html_string .= ""; - $html_string .= "
    "; - $html_string .= "
    "; - $html_string .= "





    Sorry for this,"; - $html_string .= " it's just a lowlevel protection "; - $html_string .= "against scraping bots."; - echo "$html_string"; - quit($db, ''); - } -} - -// Check if a message exists already in the database when importing. -function check_post_exists($db, $sub, $org_id, $post_id) -{ - - $statement = $db->prepare("SELECT post_id - FROM threads - WHERE sub = '$sub' - AND org_id = '$org_id' - AND post_id = '$post_id'"); - $result = $statement->execute(); - - $counter = 0; - - while ($row = $result->fetchArray(SQLITE3_NUM)) { - $counter++; - } - - if ( ($counter < 1) ) { - // if the counter is smaller 1, there is no match - return FALSE; - } else { - return TRUE; - } -} - -// A simple check if the post is spam or not, also if it is too long -// or too short. -// These are a few of the only original lines of code left from smolBBS. -function check_spam($db, $text, $settings) -{ - - if (preg_match('/^(.)\1*$/u ', $text)) { - quit($db, "

    Spam detected!

    "); - } - - $post_length = strlen($text); - - if ($post_length < $settings['min_char']) { - quit($db, "

    Post too short!

    "); - } - - if ($post_length > $settings['max_char']) { - quit($db, "

    Post too long!

    "); - } - - $text = str_replace(array("\n","\r"), '', $text); - - if (substr_count($text, ' ') === strlen($text)) { - quit($db, "

    Spam detected! Post contained only spaces!

    "); - } -//rewrite, does not work for all cases - if (ctype_space($text)) { - $quit_message = "

    Spam detected! Post contained only spaces "; - $quit_message .= "(yeah, Unicode...)!

    "; - quit($db, $quit_message); - } - -} - -// Check if a sub exists before display -// If the name does not exist, name* is searched -function check_sub_exists($db, $sub) -{ - - if ( ($sub == 'overboard') || ($sub == 'main') ) { - return $sub; - } - - $statement = $db->prepare("SELECT sub - FROM threads - WHERE sub = '$sub' - AND shadow = 'no'"); - - $result = $statement->execute(); - - $counter = 0; - - while ($row = $result->fetchArray(SQLITE3_NUM)) { - $counter++; - $sub = "{$row[0]}"; - } - - if ( ($counter < 1) ) { - // if the counter is smaller 1, there is no match - $statement = $db->prepare("SELECT sub - FROM threads - WHERE sub GLOB '$sub*' - AND shadow = 'no' - ORDER BY sub - ASC LIMIT 1"); - - $result = $statement->execute(); - - $counter_2 = 0; - - while ($row = $result->fetchArray(SQLITE3_NUM)) { - $counter_2++; - $sub = "{$row[0]}"; - } - - if ( ($counter_2 < 1) ) { - // if the counter is smaller 1, there is no match - return FALSE; - } else { - return $sub; - } - - } else { - return $sub; - } -} - -// record $_SERVER info - -function debug_server($db, $settings) - -{ - - - - $filename = $settings['work_dir'] . 'debug_server.txt'; - - $content = print_r($_SERVER, TRUE); - - file_put_contents($filename, $content, FILE_APPEND | LOCK_EX); - - - -} - - -// Delete selected logs -function delete_logs($db, $type, $token) -{ - - if ($type == 'all') { - $statement = $db->prepare("DELETE FROM logs"); - $delete_message = "all logs were deleted"; - } else { - $statement = $db->prepare("DELETE FROM logs - WHERE type = '$type'"); - $delete_message = "logs of type $type were deleted"; - } - - $result = $statement->execute(); - - echo "$delete_message"; -} - -// Delete one post or a range of posts. -// If you use php8.* you can change to str_contains below. -function delete_posts($db, $sub, $settings, $token, $delete_mode) -{ -//rewrite to check if post exists first, -// and only move it if it is not shadowed - if ($delete_mode == 'shadow') { - $posts = strip_tags($_POST['shadow_posts']); - } elseif ($delete_mode == 'move') { - $posts = strip_tags($_POST['shadow_posts']); - $target_sub = substr(preg_replace("/[^0-9a-zA-Z]/", "", - $_POST['target_sub']), 0, - $settings['max_name_sub']); - if ( (check_sub_exists($db, $target_sub) != TRUE) ) { - quit($db, "

    Sub $target_sub does not exist.

    "); - } - } else { - $posts = strip_tags($_POST['delete_posts']); - } - -// if (str_contains($posts, '-')) { -// only uncomment if you use php8.* - if (strpos($posts, '-') !== FALSE) { - $delete_minmax = explode('-', $posts); - $delete_array = array(); - for($delete = $delete_minmax['0']; - $delete <= $delete_minmax['1']; $delete++) { - array_push($delete_array, $delete); - } - } else { - $delete_array = explode(' ', $posts); - } - - $statement = $db->prepare("SELECT name, token - FROM keys - WHERE type = 'mod'"); - $result = $statement->execute(); - - while ($row = $result->fetchArray(SQLITE3_NUM)) { - $name = "{$row[0]}"; - $access_token = "{$row[1]}"; - if ( (password_verify($token, $access_token)) ) { - break; - } - } - - $html_string = ''; - - if ( (!empty($delete_array)) && ($delete_mode == 'delete') ) { - - foreach($delete_array as $delete_post) { - $statement = $db->prepare("DELETE FROM threads - WHERE post_id = '$delete_post' - AND sub = '$sub'"); - $result = $statement->execute(); - - $delete_message = "message $delete_post from "; - $delete_message .= "sub $sub deleted by $name"; - log_event($db, $settings, "del", $delete_message, ''); - $html_string .= "

    $delete_message

    "; - - } - - } elseif ( (!empty($delete_array)) && ($delete_mode == 'shadow') ) { - - foreach($delete_array as $delete_post) { - $statement = $db->prepare("UPDATE threads - SET shadow = 'yes' - WHERE post_id = '$delete_post' - AND sub = '$sub'"); - $result = $statement->execute(); - - $delete_message = "message $delete_post from "; - $delete_message .= "sub $sub shadowed by $name"; - log_event($db, $settings, "del", $delete_message, ''); - - $html_string .= "

    $delete_message

    "; - } - - } elseif ( (!empty($delete_array)) - && ($delete_mode == 'move') ) { - - move_post($db, $settings, $delete_array, $sub, $target_sub, $name); - - } - - $html_string .= "

    Done.

    "; - - if ($delete_mode == 'delete') { - $html_string .= "

    Back

    "; - } else { - $html_string .= "

    Back

    "; - } - - echo "$html_string"; -} - -// Delete or shadow a whole sub -function delete_sub($db, $sub, $settings, $token, $shadow_only) -{ - - $token_hash = hash("sha512",$token); - $statement = $db->prepare("SELECT name - FROM keys - WHERE token = '$token_hash'"); - $result = $statement->execute(); - - while ($row = $result->fetchArray(SQLITE3_NUM)) { - $name = "{$row[0]}"; - } - - $html_string = ''; - - if ( ($sub != '') && ($shadow_only == 'no') ) { - - $statement = $db->prepare("DELETE FROM threads - WHERE sub = '$sub'"); - $result = $statement->execute(); - - $delete_message = "sub $sub deleted by $name"; - log_event($db, $settings, "del", $delete_message, ''); - - $html_string .= "

    $delete_message

    "; - - } elseif ( ($sub != '') && ($shadow_only == 'yes') ) { - - $statement = $db->prepare("UPDATE threads - SET shadow = 'yes' - WHERE sub = '$sub'"); - $result = $statement->execute(); - - $delete_message = "sub $sub shadowed by $name"; - log_event($db, $settings, "del", $delete_message, ''); - - $html_string .= "

    $delete_message

    "; - } - - $html_string .= "

    Done.

    "; - - if ($shadow_only == 'yes') { - $html_string .= "

    Back

    "; - } else { - $html_string .= "

    Back

    "; - } - - echo "$html_string"; -} - -// Delete an admin or mod token to end the session -function destroy_token($db, $token, $settings) -{ - $name = ''; - $token_hash = hash("sha512", $token); - $statement = $db->prepare("SELECT name - FROM keys - WHERE token = '$token_hash'"); - $result = $statement->execute(); - - while ($row = $result->fetchArray(SQLITE3_NUM)) { - $name = "{$row[0]}"; - } - - if ($name == '') { - $logoff_message = "token used to log off was invalid"; - } else { - $logoff_message = "$name logged off"; - } - - log_event($db, $settings, "auth", $logoff_message, ''); - - $statement = $db->prepare("UPDATE keys - SET token = 'none' - WHERE token = '$token_hash'"); - $result = $statement->execute(); - -} - -// Dump the contents of a sub,a thread or the whole board to a json -// file and send it to the browser. -// If used on the overboard, it will dump everything, including the -// messages from subs that are not actually displayed on the overboard. -function dump($db, $sub, $org_id, $settings) -{ -// rewrite to include ranges - header('Content-Type: application/json'); - - if ($org_id != '') { - $statement = $db->prepare("SELECT post_id, text - FROM threads - WHERE sub = '$sub' - AND shadow = 'no' - AND org_id = '$org_id'"); - $result = $statement->execute(); - $thread_dump = array(); - $thread_dump['sub'] = $sub; - $thread_dump['org_id'] = $org_id; - - while ($row = $result->fetchArray(SQLITE3_NUM)) { - $post = array(); - $post['post_id'] = "{$row[0]}"; - $post['text'] = "{$row[1]}"; - array_push($thread_dump, $post); - } - - echo json_encode($thread_dump, JSON_PRETTY_PRINT - | JSON_NUMERIC_CHECK - | JSON_UNESCAPED_UNICODE); - - } elseif ($sub == 'overboard') { - $statement = $db->prepare("SELECT post_id, org_id, sub, text - FROM threads - WHERE shadow = 'no'"); - $result = $statement->execute(); - $overboard_dump = array(); - - while ($row = $result->fetchArray(SQLITE3_NUM)) { - $post = array(); - $post['post_id'] = "{$row[0]}"; - $post['org_id'] = "{$row[1]}"; - $post['sub'] = "{$row[2]}"; - $post['text'] = "{$row[3]}"; - array_push($overboard_dump, $post); - } - - echo json_encode($overboard_dump, JSON_PRETTY_PRINT - | JSON_NUMERIC_CHECK - | JSON_UNESCAPED_UNICODE); - - } else { - $statement = $db->prepare("SELECT post_id, org_id, text - FROM threads - WHERE sub = '$sub' - AND shadow = 'no'"); - $result = $statement->execute(); - $sub_dump = array(); - $sub_dump['sub'] = $sub; - - while ($row = $result->fetchArray(SQLITE3_NUM)) { - $post = array(); - $post['post_id'] = "{$row[0]}"; - $post['org_id'] = "{$row[1]}"; - $post['text'] = "{$row[2]}"; - array_push($sub_dump, $post); - } - - echo json_encode($sub_dump, JSON_PRETTY_PRINT - | JSON_NUMERIC_CHECK - | JSON_UNESCAPED_UNICODE); - - } -} - -// Prevents additional tries to log in after too many failed tries. -// To be configured in the config file. -// Note: if enabled, this function can be used to dos the access to the -// admin and mod panels by sending constant junk requests. -// Still better than someone trying millions of user/password -// combinations, right ? -// If really paranoid, enable the admin panel only if you use it or -// let it listen on another address altogether. -// Read your logfiles to check if attacks are ongoing. -function fail2ban($db, $settings) -{ - - if ($settings['enable_fail2ban'] != TRUE) { - return; - } - - $current = time(); - $max_age = $current - ($settings['auth_time_frame'] * 60); - // max age is in minutes, so times 60 to go to seconds - - if ($settings['superstrict'] == TRUE) { - $statement = $db->prepare("SELECT unix_timestamp - FROM logs - WHERE type = 'auth' - AND event in ( - 'wrong combination user/password', - 'invalid token used', - 'wrong combination name/token', - 'fail2ban triggered') - AND unix_timestamp > '$max_age'"); - } else { - $statement = $db->prepare("SELECT unix_timestamp - FROM logs - WHERE type = 'auth' - AND event in ( - 'wrong combination user/password', - 'invalid token used', - 'wrong combination name/token') - AND unix_timestamp > '$max_age'"); - } - - $result = $statement->execute(); - - $counter = 0; - - while ($row = $result->fetchArray(SQLITE3_NUM)) { - $counter++; - } - - if ($counter > $settings['auth_max']) { - $fail2ban_message = "fail2ban triggered"; - log_event($db, $settings, "auth", $fail2ban_message, ''); - header("HTTP/1.1 429 Too Many Requests"); - quit($db, "429"); - } -} - -// Select a new post_id for a new post. It is one higher than the -// previous existing highest number. -// This means in theory that if a message is deleted the number could -// be assigned to a different one (if it was the latest message that -// was deleted). -// This behavior can be prevented when working with a moderators account. -// Note that in contrast to previous versions, replies are inside -// the same numbering system. -function get_new_post_id($db, $sub) -{ - - $largest = 0; - - $statement = $db->prepare("SELECT post_id - FROM threads - WHERE sub = '$sub' - ORDER BY post_id DESC - LIMIT 1"); - // we just want the highest element - $result = $statement->execute(); - - while ($row = $result->fetchArray(SQLITE3_NUM)) { - $largest = "{$row[0]}"; - } - - $largest++; - - return $largest; -} - -// Get the admin or mod token from the post stream. -function get_post_token() -{ - - $token = ''; - - if ( (!empty($_POST['token'])) ) { - $token = substr(preg_replace("/[^0-9a-zA-Z]/", "", - $_POST['token']), 0, 250); - // length of token is 250 characters - } - - return $token; -} - -// Check how many pretty vars have been sent -function get_pretty_vars_count() -{ - - $raw_vars = explode('/', $_SERVER['REQUEST_URI']); - $count = count($raw_vars); - - return $count; - -} - -// Return number of posts in sub or overboard, with or without replies. -function give_total_posts($db, $sub, $original_only, $settings) -{ - - if ( ($original_only) && ($sub != 'overboard') ) { - $statement = $db->prepare("SELECT post_id - FROM threads - WHERE sub = '$sub' - AND post_id = org_id - AND shadow = 'no'"); - $result = $statement->execute(); - $counter = 0; - while ($row = $result->fetchArray(SQLITE3_NUM)) { - $counter++; - } - } elseif ( ($sub == 'overboard') ) { - - $no_overboard = ''; - $last = array_pop($settings['no_overboard']); - - foreach($settings['no_overboard'] as $ex_sub) { - $str = "'" . $ex_sub . "', "; - $no_overboard .= $str; - } - - $no_overboard .= "'" . $last . "'"; - $statement = $db->prepare("SELECT post_id - FROM threads - WHERE post_id = org_id - AND sub NOT IN ($no_overboard) - AND shadow = 'no'"); - $result = $statement->execute(); - $counter = 0; - while ($row = $result->fetchArray(SQLITE3_NUM)) { - $counter++; - } - } else { - $statement = $db->prepare("SELECT post_id - FROM threads - WHERE sub = '$sub' - AND shadow = 'no'"); - $result = $statement->execute(); - $counter = 0; - while ($row = $result->fetchArray(SQLITE3_NUM)) { - $counter++; - } - } - - return $counter; - -} - -// This function can be used to replicate an existing board. -// Save the dump from the overboard of the existing board, then put -// it under /var/opt/endboard/board.json of your new server -// (or whatever name you defined in the config-file). -// Log in to your admin panel, click on link "import" in the footer -function import_overboard($db, $settings) -{ - - $board_file = $settings['work_dir'] . $settings['import_file']; - - if ( file_exists($board_file) ) { - $html_string = "
    Found importfile $board_file
    "; - } else { - $html_string = "
    Could not find importfile "; - $html_string .= "$board_file, aborting.
    "; - echo "$html_string"; - return; - } - - $board_json = file_get_contents($board_file); - $board_list = json_decode($board_json, TRUE); - $counter = 0; - - foreach($board_list as $post) { - $post_id = $post['post_id']; - $text = $post['text']; - $sub = $post['sub']; - $org_id = $post['org_id']; - - if ( (!empty($text)) && (!empty($sub)) - && (!empty($post_id)) && (!empty($org_id)) ) { - if (!check_post_exists($db, $sub, $org_id, $post_id)) { - $global_id = hash("sha512", $sub . $post_id . $org_id . $text); - $text_id = hash("sha512", $text); - - $statement = $db->prepare("INSERT OR IGNORE INTO threads - (post_id, sub, text, org_id, - shadow, global_id, text_id) - VALUES ('$post_id', '$sub', ?, - '$org_id', 'no', '$global_id', - '$text_id')"); - $statement->bindParam(1, $text); - - $statement->execute(); - - $counter++; - $import_message = "message $post_id from sub $sub imported"; - log_event($db, $settings, "import", $import_message, ''); - $html_string .= "
    $import_message
    "; - } else { - $import_message = "message $post_id from sub $sub"; - $import_message .= " existed already and was not imported"; - log_event($db, $settings, "import", $import_message, ''); - $html_string .= "
    $import_message
    "; - } - } - } - - rename($board_file, $board_file . '.done'); - - $html_string .= "
    Finished, imported "; - $html_string .= "$counter messages altogether.
    "; - $html_string .= "
    The boardfile was "; - $html_string .= "renamed to avoid another import.
    "; - - echo "$html_string"; -} - -// Log an event to the db. Also, delete the overflow of logs as -// defined in the config file. -function log_event($db, $settings, $type, $text, $ip) -{ - - if ( ($settings['enable_logging'] != TRUE) ) { - return; - } - - $current = time(); - $timestamp = date('Y-m-d H:i:s', $_SERVER['REQUEST_TIME']); - - $statement = $db->prepare("INSERT INTO logs(event, type, timestamp, - unix_timestamp, ip) - VALUES (?, '$type', '$timestamp', - '$current', ?)"); - $statement->bindParam(1, $text); - $statement->bindParam(2, $ip); - $statement->execute(); - - if ( ($settings['cap_logs'] > 0) ) { - $statement = $db->prepare("DELETE FROM logs - WHERE ROWID IN - (SELECT ROWID FROM logs - ORDER BY ROWID DESC - LIMIT -1 OFFSET ?)"); - // to prevent the db from bloating (and to prevent attacks), we - // allow only so many lines of logs at any one time, and we check - // this with each call. - $statement->bindParam(1, $settings['cap_logs']); - $result = $statement->execute(); -} - -} - -// Do some magic number trickery (easteregg) -function make_id_text($post_id) -{ - - if ($post_id == 1) { - $id_text = "first post - yeah"; - } elseif ($post_id == 42) { - $id_text = "...and thanks for all the fish..."; - } elseif ($post_id == 69) { - $id_text = "$post_id 😏"; - } elseif ($post_id == 104) { - $id_text = "10-4 affirmative"; - } elseif ($post_id == 143) { - $id_text = "$post_id πŸ’Œ"; - } elseif ($post_id == 404) { - $id_text = "content not found"; - } elseif ($post_id == 420) { - $id_text = "🌿🌿🌿"; - } elseif ($post_id == 666) { - $id_text = "πŸ‘ΏπŸ‘ΏπŸ‘Ώ "; - } elseif ($post_id == 911) { - $id_text = "How can I help you ?"; - } elseif ($post_id == 1312) { - $id_text = "all cats are beautiful"; - } else { - $id_text = $post_id; - } - - return $id_text; - -} - -// Make all tables that are needed (one each for posts, keys, hashes -// (captchas), hashes (passwords) and logs). -// Also, the hashes for the captchas are cropped to 20000. -function make_basic_tables($db) -{ - - // make basic tables: threads, captchas, logs, keys - $db->exec('CREATE TABLE IF NOT EXISTS "captchas" ( - "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - "hash" TEXT UNIQUE, - "unix_timestamp" INTEGER - )'); - - $db->exec('CREATE TABLE IF NOT EXISTS "logs" ( - "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - "timestamp" TEXT, - "unix_timestamp" INTEGER, - "type" TEXT, - "event" TEXT, - "ip" TEXT - )'); - - $db->exec('CREATE TABLE IF NOT EXISTS "keys" ( - "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - "name" TEXT UNIQUE NOT NULL, - "type" INTEGER NOT NULL, - "email" TEXT, - "key" TEXT, - "subs" TEXT, - "token" TEXT, - "timestamp_token" INTEGER - )'); - - $db->exec('CREATE TABLE IF NOT EXISTS "threads" ( - "id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, - "post_id" INTEGER NOT NULL, - "shadow" TEXT NOT NULL, - "sub" TEXT NOT NULL, - "global_id" TEXT NOT NULL UNIQUE, - "text_id" TEXT NOT NULL, - "text" TEXT NOT NULL, - "org_id" INTEGER NOT NULL, - UNIQUE(post_id, sub) ON CONFLICT IGNORE - )'); - - $statement = $db->prepare("DELETE FROM captchas - WHERE ROWID IN - (SELECT ROWID FROM captchas - ORDER BY ROWID DESC - LIMIT -1 OFFSET 20000)"); - // to prevent the db from bloating (and to prevent attacks), we - // allow only 20000 captchas at any one time, and we check this - // with each call. This should be enough if your site is getting - // less or equal to 100.000 visitors a day. - // Total combinations of captcha and token are ca. 2000 * 62^250. - $result = $statement->execute(); - -} - -// Make a new post to a sub -function make_post($db, $sub, $settings, $text, $org_id) -{ - - $post_id = get_new_post_id($db, $sub); - - if ($org_id == '') { - $org_id = $post_id; - } elseif ( (!check_org_id_exists($db, $sub, $org_id)) ) { - quit($db, "

    Post $org_id on sub $sub does not exist.

    "); - } - - $global_id = hash("sha512", $sub . $post_id . $org_id . $text); - $text_id = hash("sha512", $text); - - $statement = $db->prepare("INSERT INTO threads(post_id, sub, text, - org_id, shadow, - global_id, text_id) - VALUES ('$post_id', '$sub', ?, '$org_id', - 'no', '$global_id', '$text_id')"); - $statement->bindParam(1, $text); - $statement->execute(); - - if ( ($org_id != $post_id) && ($settings['enable_bumping'] == TRUE) ){ - bump_message($db, $org_id, $sub); - } - - return $post_id; -} - -// Make a token to grant access to the admin panel or the mod panel for -// a limited time. A way of having sessions without cookies. -// Also used as a hidden field in the post form to prevent double -// posting by sending the same input twice. -function make_token() -{ - - $characters = '0123456789'; - $characters .= 'abcdefghijklmnopqrstuvwxyz'; - $characters .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; - $random_string = ''; - - for ($i = 0; $i < 250; $i++) { - // token length is set to 250 characters - $index = random_int(0, 61); - // we have 62 to choose, so 0 to 61 - $random_string .= $characters[$index]; - } - - return $random_string; -} - -// Moves a post to a different sub. In case it is an original post, all -// replies re moved as well. -function move_post($db, $settings, $delete_array, $sub, $target_sub, $name) -{ - foreach($delete_array as $delete_post) { - $statement = $db->prepare("UPDATE threads - SET shadow = 'yes' - WHERE post_id = '$delete_post' - AND sub = '$sub'"); - $result = $statement->execute(); - - $statement = $db->prepare("SELECT post_id, org_id, text - FROM threads - WHERE post_id = '$delete_post' - AND sub = '$sub'"); - $result = $statement->execute(); - - while ($row = $result->fetchArray(SQLITE3_NUM)) { - $post_id = "{$row[0]}"; - $org_id = "{$row[1]}"; - $text = "{$row[2]}"; - } - - if ( ($post_id == $org_id) ) { - $text .= "\r\n\r\n\r\n\r\n [system generated message: \r\n"; - $text .= "post $post_id from sub \"$sub\" was moved \r\n"; - $text .= "to sub \"$target_sub\"]"; - $new_id = make_post($db, $target_sub, $settings, $text, ''); - $delete_message = "message $delete_post from sub $sub"; - $delete_message .= " moved to $target_sub by $name"; - log_event($db, $settings, "del", $delete_message, ''); - - echo "

    $delete_message

    "; - - $statement = $db->prepare("UPDATE threads - SET shadow = 'yes' - WHERE org_id = '$post_id' - AND sub = '$sub'"); - $result = $statement->execute(); - - $statement = $db->prepare("SELECT post_id, org_id, text - FROM threads - WHERE org_id = '$post_id' - AND post_id != '$post_id' - AND sub = '$sub'"); - $result = $statement->execute(); - - while ($row = $result->fetchArray(SQLITE3_NUM)) { - $post_id = "{$row[0]}"; - $org_id = "{$row[1]}"; - $text = "{$row[2]}"; - - $text .= "\r\n\r\n\r\n\r\n [system generated message:\r\n"; - $text .= "post $post_id from sub \"$sub\" was moved \r\n"; - $text .= "to sub \"$target_sub\"]"; - make_post($db, $target_sub, $settings, $text, $new_id); - - $delete_message = "message $post_id from sub $sub moved "; - $delete_message .= "to $target_sub by $name"; - log_event($db, $settings, "del", $delete_message, ''); - - echo "

    $delete_message

    "; - } - } else { - $text .= "\r\n\r\n\r\n\r\n [system generated message: post\r\n"; - $text .= "$post_id from sub \"$sub\" was moved \r\n"; - $text .= "to sub \"$target_sub\"]"; - make_post($db, $target_sub, $settings, $text, ''); - $delete_message = "message $post_id from sub $sub moved"; - $delete_message .= " to $target_sub by $name"; - log_event($db, $settings, "del", $delete_message, ''); - - echo "

    $delete_message

    "; - } - - } - -} - -// 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; -} -// checks if posts from users can be received or not -function post_block_user($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; - } - } - - return TRUE; -} - -// Show each post in a thread -function print_thread($db, $sub, $css, $settings, $org_id) -{ - - $html_string = "
    "; - - $statement = $db->prepare("SELECT post_id, org_id, sub, text - FROM threads WHERE sub = '$sub' - AND org_id = '$org_id' - AND shadow = 'no' - ORDER BY post_id DESC"); - $result = $statement->execute(); - - while ($row = $result->fetchArray(SQLITE3_NUM)) { - $post_id = "{$row[0]}"; - $org_id = "{$row[1]}"; - $post_text = "{$row[3]}"; - $post_text = break_text(bbcode_to_html($post_text, $settings), - $settings); - $id_text = make_id_text($post_id); - - $html_string .= "
    "; - $html_string .= "
    #$id_text

    "; - $html_string .= "$post_text"; - $html_string .= "


    "; - } - - $html_string .= "
    "; - - echo "$html_string"; - -} - -// Show each post of the overboard (so all original posts in their -// sequence, including bumps, except for the subs that are excluded) -function print_overboard($db, $css, $settings, $page) -{ - - $out = ''; - $pagination = $settings['pagination']; - - if ( (!empty($settings['no_overboard'])) ) { - $last = array_pop($settings['no_overboard']); - - foreach($settings['no_overboard'] as $no_overboard) { - $str = "'" . $no_overboard . "', "; - $out .= $str; - } - - $out .= "'" . $last . "'"; - - } - - if ($page == 'all') { - $statement = $db->prepare("SELECT post_id, org_id, sub, text - FROM threads - WHERE post_id = org_id - AND shadow = 'no' - AND sub NOT IN ($out) - ORDER BY ROWID DESC"); - } elseif ($page > 0) { - // if the page is defined - $page_start = ($page - 1) * $settings['pagination']; - $statement = $db->prepare("SELECT post_id, org_id, sub, text - FROM threads - WHERE post_id = org_id - AND shadow = 'no' - AND sub NOT IN ($out) - ORDER BY ROWID DESC - LIMIT '$page_start', '$pagination'"); - } else { - $statement = $db->prepare("SELECT post_id, org_id, sub, text - FROM threads - WHERE post_id = org_id - AND shadow = 'no' - AND sub NOT IN ($out) - ORDER BY ROWID DESC - LIMIT '$pagination'"); - } - - $result = $statement->execute(); - - while ($row = $result->fetchArray(SQLITE3_NUM)) { - - $html_string = ''; - - $post_id = "{$row[0]}"; - $org_id = "{$row[1]}"; - $sub = "{$row[2]}"; - $text = "{$row[3]}"; - $post_text = break_text(bbcode_to_html($text, $settings), $settings); - - $html_string .= "
    "; - $link_string_1 = "/r/$sub/$org_id/op/$css"; - $link_string_2 = "/r/$sub/$org_id/$css"; - $link_string_3 = "/s/$sub/$css"; - $html_string .= "
    "; - $html_string .= "$sub "; - $html_string .= ""; - } - -} - -// Show each post of an individual feed -function print_individual_feed($db, $css, $settings, $ex_subs, $in_subs) -{ - - $counter = 0; - - if ( (!empty($ex_subs)) ) { - $out = ''; - $last = array_pop($ex_subs); - - foreach($ex_subs as $ex_sub) { - $str = "'" . $ex_sub . "', "; - $out .= $str; - } - - $out .= "'" . $last . "'"; - - $statement = $db->prepare("SELECT post_id, org_id, sub, text - FROM threads - WHERE post_id = org_id - AND shadow = 'no' - AND sub NOT IN ($out) - ORDER BY ROWID DESC"); - } elseif ( (!empty($in_subs)) ) { - $in = ''; - $last = array_pop($in_subs); - - foreach($in_subs as $in_sub) { - $str = "'" . $in_sub . "', "; - $in .= $str; - } - - $in .= "'" . $last . "'"; - - $statement = $db->prepare("SELECT post_id, org_id, sub, text - FROM threads - WHERE post_id = org_id - AND shadow = 'no' - AND sub IN ($in) - ORDER BY ROWID DESC"); - } - - $result = $statement->execute(); - - while ($row = $result->fetchArray(SQLITE3_NUM)) { - - $html_string = ''; - - $counter++; - - $post_id = "{$row[0]}"; - $org_id = "{$row[1]}"; - $sub = "{$row[2]}"; - $text = "{$row[3]}"; - - $post_text = break_text(bbcode_to_html($text, $settings), $settings); - - $link_string_1 = "/r/$sub/$org_id/op/$css"; - $link_string_2 = "/r/$sub/$org_id/$css"; - $link_string_3 = "/s/$sub/$css"; - - $html_string .= "
    "; - $html_string .= "$sub "; - $html_string .= ""; - $html_string .= "#$post_id

    $post_text"; - $html_string .= "

    reply
    "; - - echo "$html_string"; - - print_replies($db, $sub, $post_id, $org_id, $settings, $css); - - echo "
    "; - } - - return $counter; -} - -// Show each post in a sub -function print_sub($db, $sub, $css, $settings, $page) -{ - - echo "
    "; - - $pagination = $settings['pagination']; - - if ($page == 'all') { - $statement = $db->prepare("SELECT post_id, org_id, sub, text - FROM threads - WHERE sub = '$sub' - AND shadow = 'no' - AND post_id = org_id - ORDER BY ROWID DESC"); - } elseif ($page > 0) { - // if the page is defined - $page_start = ($page - 1) * $settings['pagination']; - $statement = $db->prepare("SELECT post_id, org_id, sub, text - FROM threads - WHERE sub = '$sub' - AND shadow = 'no' - AND post_id = org_id - ORDER BY ROWID DESC - LIMIT '$page_start', '$pagination'"); - } else { - $statement = $db->prepare("SELECT post_id, org_id, sub, text - FROM threads - WHERE sub = '$sub' - AND shadow = 'no' - AND post_id = org_id - ORDER BY ROWID DESC - LIMIT '$pagination'"); - } - - $result = $statement->execute(); - - while ($row = $result->fetchArray(SQLITE3_NUM)) { - - $html_string = ''; - - $post_id = "{$row[0]}"; - $org_id = "{$row[1]}"; - $text = "{$row[3]}"; - $post_text = break_text(bbcode_to_html($text, $settings), $settings); - - $html_string .= "
    "; - - $id_text = make_id_text($post_id); - $link_string_1 = "/r/$sub/$org_id/op/$css"; - $link_string_2 = "/r/$sub/$org_id/$css"; - - $html_string .= "
    "; - $html_string .= "#$id_text"; - $html_string .= "

    $post_text

    "; - $html_string .= "reply
    "; - - echo "$html_string"; - - print_replies($db, $sub, $post_id, $org_id, $settings, $css); - - echo "
    "; - echo "
    "; - } - - echo "
    "; - -} - -// Print footer, with the total messages and some links, for the subs -function print_footer_sub($sub, $total_posts, $css, $page, $settings) -{ - - if ( ($total_posts > $settings['pagination']) && ($page != 'all') ) { - $number_first_message = ($page - 1) * $settings['pagination'] + 1; - $number_last_message = - $number_first_message + $settings['pagination'] - 1; - - if ($number_last_message > $total_posts) { - $number_last_message = $total_posts; - } - - $next_page = $page + 1; - $prev_page = $page - 1; - $pages_total = ceil($total_posts / $settings['pagination']); - - $show_string_1 = "page:$page/$pages_total|posts:"; - $show_string_1 .= "$number_first_message"; - $show_string_1 .= "-$number_last_message/$total_posts|"; - - $link_string_1 = "/s/$sub/$next_page/$css"; - $link_string_2 = "/s/$sub/$prev_page/$css"; - $link_string_3 = "/s/$sub/all/$css"; - - if ( ($number_first_message > 1) - && ($number_last_message < $total_posts) ) { - $left_section = "$show_string_1
    "; - $html_string .= "$left_section
    "; - $html_string .= "
    auth" . - "|user" . - "|bot" . - "|del" . - "|import" . - "|sys"; - - $html_string = "
    "; - $html_string .= "$left_section
    "; - - $mid_section = "/dt/$token'>log out" . - "|" . - "show shadowed posts and subs
    "; - $right_section = "/im/$css/$token'>import" . - "|view mods
    "; - - $html_string .= ""; - } else { - $mid_section = "**********
    "; - } - - if ( ($settings['enable_admin_panel'] == TRUE) - && (!check_admin($db, $settings)) ) { - $right_section = "set admin password
    "; - } elseif ($settings['take_applications'] == TRUE) { - $right_section = "apply for mod account"; - } else { - $right_section = "**********"; - } - - $html_string .= "
    $mid_section"; - $html_string .= "
    $right_section"; - $html_string .= "
    "; - - echo "$html_string"; -} - -// Print the footer for the mod -function print_footer_mod($css, $settings, $token, $sub) -{ - - $left_section = "***************"; - - $html_string = "
    "; - $html_string .= "
    $left_section
    "; - - $mid_section = "/dt/$token'>log out
    "; - $right_section = "***************"; - - $html_string .= "
    "; - $html_string .= "
    $left_section
    "; - - $mid_section = "/s/overboard/$css'>overboard
    |"; - $mid_section .= "show subs
    "; - - $right_section = "/iv/$subs_string/$css'>get link for multifeed"; - - $html_string .= "
    "; - $html_string .= "
    $left_section
    "; - - $mid_section = "/s/$sub/$css'>back to $sub
    |"; - $mid_section .= "overboard
    "; - - $right_section = "/d/$sub/$org_id'>save $msg"; - - $html_string .= "
    "; - } else { - $html_string = ""; - $html_string .= ""; - } - - echo "$html_string"; -} - -// Show all replies to a given post -function print_replies($db, $sub, $post_id, $org_id, $settings, $css) -{ - - $sub_statement = $db->prepare("SELECT post_id, org_id, sub, text - FROM threads - WHERE sub = '$sub' - AND org_id = '$org_id' - AND shadow = 'no'"); - $sub_result = $sub_statement->execute(); - - $answers = array(); - $counter = 0; - - while ($row = $sub_result->fetchArray(SQLITE3_NUM)) { - $sub_post_id = "{$row[0]}"; - $sub_org_id = "{$row[1]}"; - $sub_text = "{$row[3]}"; - if ($sub_post_id != $sub_org_id) { - $counter++; - $post = array(); - $sub_post_text = break_text(bbcode_to_html - ($sub_text, $settings), $settings); - array_push($post, $sub_post_id); - array_push($post, $sub_org_id); - array_push($post, $sub_post_text); - array_push($answers, $post); - } - } - - $display_number = $counter - 1; - - if ($counter == 0) { - // no replies exist for this message - return; - } - - $last_answer = array_pop($answers); - $last_post_id = $last_answer[0]; - $last_post_text = $last_answer[2]; - - $html_string = ''; - - if ($counter > 1) { - // we have at least one reply - $html_string .= "
    "; - $html_string .= "Show $display_number more replies"; - - foreach ($answers as $display_msg) { - $answer_post_id = $display_msg[0]; - $answer_post_text = $display_msg[2]; - $link_string_1 = "/r/$sub/$org_id/$answer_post_id/$css"; - $html_string .= ""; - } - - $html_string .= "
    "; - } - - $link_string_1 = "/r/$sub/$org_id/$last_post_id/$css"; - - $html_string .= "

    "; - $html_string .= "#$last_post_id

    "; - $html_string .= "$last_post_text
    "; - - echo "$html_string"; -} - -// prints the fixed topheader with some text -function print_top_header($text) -{ - echo "

    $text

    "; -} - -// Close the database, so to not let this work for the garbage collector -// and loose time. -// Also display a final message (optionally), and exit. -function quit($db, $text) -{ - - $db->close(); - - if ($text != '') { - echo "$text"; - } - - exit; -} - -// Parse pretty vars at different oositions and with different filters -function read_pretty_vars($position, $filter, $length) -{ - - $var = ''; - - $raw_vars = explode('/', $_SERVER['REQUEST_URI']); - $pretty_vars = array(); - - foreach($raw_vars as $raw_var) { - if ($filter == 'number') { - $pretty_var = substr(preg_replace("/[^0-9]/", "", - $raw_var), 0, $length); - } elseif ($filter == 'alnum') { - $pretty_var = substr(preg_replace("/[^0-9a-zA-Z]/", "", - $raw_var), 0, $length); - } elseif ($filter == 'alnumplus') { - $pretty_var = substr(preg_replace("/[^0-9a-zA-Z+-]/", "", - $raw_var), 0, $length); - } - array_push($pretty_vars, $pretty_var); - } - - if ($position == 'last') { - $var = array_pop($pretty_vars); - } elseif ( (isset($pretty_vars[$position])) ) { - $var = $pretty_vars[$position]; - } - - return $var; - -} - -// Receives an application and writes it to the db. -function set_application($db, $name, $email, $password, $settings) -{ - - $statement = $db->prepare("INSERT INTO keys(type, name, email, key) - VALUES ('application', '$name', - '$email', '$password')"); - $statement->execute(); - -} - -// Set the css that we use -function set_css($mode) -{ - - $admin_css = array( - 'auth_admin', - 'admin', - 'delete_admin', - 'delete_logs', - 'logs', - 'password', - 'setup', - 'shadow', - 'view_mods', - 'change_mods', - 'unshadow_post', - 'unshadow_sub', - 'import'); - $mod_css = array( - 'apply', - 'auth_mod', - 'mod', - 'delete_mod'); - - if (in_array($mode, $admin_css)) { - $css = 8; - // the css for the admin - } elseif (in_array($mode, $mod_css)) { - $css = 7; - // the css for the mods - } else { - $css = read_pretty_vars("last", "number", 2); - // we will not have more than 99 styles, so two digits - } - - if ( (!empty($_POST['css'])) ) { - $css = substr(preg_replace("/[^0-9]/", "", $_POST['css']), 0, 2); - // we will not have more than 99 styles, so two digits - } - - if ( (empty($css)) || ($css < 1) || ($css > 9) ) { - // if no css is given, or the value is out of range, - $css = 6; - // resort to the default style - } - - return $css; -} - -// Set the stylesheet we use -function set_css_file($css) -{ - - if ($css == 9) { - // nine means no style - $css_file = 'no_file'; - } elseif ($css == 8) { - // eight is admin - $css_file = '/css/admin.css'; - } elseif ($css == 7) { - // seven is moderator - $css_file = '/css/mod.css'; - } elseif ($css == 6) { - // six is the default (Forest) - $css_file = '/css/style4.css'; - } elseif ($css == 5) { - $css_file = '/css/style20.css'; - } elseif ($css > 2) { - // 3,4,5 is Santa Muerte - $css_file = '/css/style8.css'; - } elseif ($css > 1) { - // 2 is Kali - $css_file = '/css/style12.css'; - } else{ - // 1 is Skull - $css_file = '/css/style10.css'; - } - - return $css_file; - -} - -// Check what is expected from the request, and set mode accordingly. -// The variable "$short_mode" is read from the request. -// The elements in the triggers array other than 'tr', capture requests -// that are standard scans for vulns + target discovery. -// Part of the bot trap, which can be disabled in the config file -function set_mode($short_mode, $settings) -{ - - $triggers = array ( - 'tr', 'login', 'wellknown', 'wplogin', - 'wpjson', 'wp', 'products', 'wpusers', - 'wpadmin', 'wpadminer', 'adminer', 'phpmyadmin', - 'wpuploads', 'wpcontent', 'wpconfig', 'wpincludes', - 'static', 'img', 'images', 'uploads', - 'styles', 'style', 'serverinfo', 'privatekey', - 'serverstatus' - ); - - $short_modes = array ( - 'cm', 'dim', 'pa', 'ush', - 'dsh', 'usha', 'dsa', 'aa', - 'am', 'dt', 'a', 'm', - 'ap', 'sp', 'im', 'da', - 'dm', 'dl', 'su', 'lo', - 'sh', 'iv', 'p', 'r', - 'v', 's', 'b', 'd' - ); - - $long_modes = array ( - 'view_mods', 'change_mods', 'password', 'unshadow_post', - 'delete_post', 'unshadow_sub', 'delete_sub', 'auth_admin', - 'auth_mod', 'destroy_token', 'admin', 'mod', - 'apply', 'setup', 'import', 'delete_admin', - 'delete_mod', 'delete_logs', 'subs', 'logs', - 'shadow', 'individual_view', 'post', 'reply', - 'view', 'view', 'bot', 'dump' - ); - - if ( (in_array($short_mode, $triggers)) - && ($settings['enable_bot_trap']) ) { - $mode = 'trap'; - } elseif ( (!in_array($short_mode, $short_modes)) ) { - $mode = 'landing'; - // if nothing fits, we display the landing page - } else { - for ($i = 0; $i < count($short_modes); $i++) { - if ( ($short_modes[$i] == $short_mode) ) { - $mode = $long_modes[$i]; - break; - } - } - } - - return $mode; -} - -// In case of replies, check which post we are replying to -// (based on the pretty vars in GET requests) -function set_org_id() -{ - - $org_id = read_pretty_vars(3, "number", 10); - // we read from the third position, and a message does not need more - // than 10 digits (=max 9 999 999 999 messages) - return $org_id; - -} - -// Get the page if it is defined, otherwise set to 1 -function set_page() -{ - - if (get_pretty_vars_count() == 5) { - // the page is only defined if there are five vars - $page = read_pretty_vars(3, "number", 10); - // read from third position, and a page does not need more - // than 10 digits - } else { - $page = 0; - // if there is no page given, we set it to zero - } - - if ( ($page < 1) ) { - // if the page is zero, it means we did not - // get a number before - - $page = read_pretty_vars(3, "alnum", 3); - // read from the third position, and a page does not need more - // than 10 digits, this time we allow letters, too. - - if ( ($page != 'all') ) { - - $page = 1; - // if page is not "all", we start with the first - } - } - - return $page; -} - -// Set the password for the admin, delete all old admin accounts -// (there can only be one...) -function set_password($db, $name, $password_hash, $token, $settings) -{ - - $db_token = ''; - $db_name = ''; - - $statement = $db->prepare("SELECT name, token - FROM keys - WHERE type = 'admin' - AND name = '$name'"); - $result = $statement->execute(); - - while ($row = $result->fetchArray(SQLITE3_NUM)) { - $db_name = "{$row[0]}"; - $db_token = "{$row[1]}"; - } - - if ( (!password_verify($token, $db_token)) ) { - $auth_message = "wrong combination name/token"; - log_event($db, $settings, "auth", $auth_message, ''); - sleep(10); - $quit_message = "

    Combination of username and token is invalid."; - $quit_message .= "This incident was logged.

    "; - quit($db, $quit_message); - } else { - $statement = $db->prepare("UPDATE keys - SET key = '$password_hash' - WHERE name = '$name'"); - $result = $statement->execute(); - - $statement = $db->prepare("DELETE FROM keys - WHERE type = 'admin' - AND name != '$name'"); - $result = $statement->execute(); - - unlink($settings['work_dir'] . "admin_" . $name . "_token.txt"); - } - -} - -// Determine if there is an original post_id given in the poststream -// (in case of replies) -function set_post_org_id() -{ - - $org_id = ''; - - if ( (!empty($_POST['org_id'])) ) { - $org_id = substr(preg_replace("/[^0-9]/", "", - $_POST['org_id']), 0, 10); - // no post id needs more than 10 digits - // (= 9 999 999 999 messages) - - } - - return $org_id; -} - -// Determine which sub we use currently, first read from POST, -// than from the pretty vars (GET) -function set_sub($settings) -{ - - $sub = ''; - - if ( (!empty($_POST['sub'])) ) { - $sub = substr(preg_replace("/[^0-9a-zA-Z]/", "", $_POST['sub']), - 0, $settings['max_name_sub']); - } elseif (get_pretty_vars_count() > 1) { - $sub = read_pretty_vars(2, "alnum", $settings['max_name_sub']); - // read the sub from the second position - } - - if ($sub == '') { - $sub = 'main'; - } - - return $sub; -} - -// Get the quote, if there is one. -function set_quote() -{ - - if (get_pretty_vars_count() == 6) { - $quote = read_pretty_vars(4, "alnum", 10); - // read from fourth position, and a post id does not need more - // than 10 digits - } else { - $quote = ''; - } - - return $quote; - -} - -// Show the form that allows the admin and mods to log in. -// Can be disabled in the config file. -function show_auth_form($db, $css, $settings, $type) -{ - $html_string = "

    Give your account name and password to log in.

    "; - $html_string .= "

    "; - - if ( ($type == 'admin') ) { - $html_string .= "

    "; - $html_string .= "
    "; - } else { - $html_string .= "
    "; - $html_string .= ""; - } - - $html_string .= ""; - $html_string .= "
    NameNo posting possible, no space on filesystem"; - return; - } - - if ( (post_block_user($db, $settings, $ip) != TRUE) ) { - echo "

    Max posts exhausted. Retry later.

    "; - return; - } - - $html_string = "
    "; - $html_string .= ""; - $html_string .= ""; - - if ( (!$org_id) && $sub == 'main') { - $html_string .= ""; - $html_string .= "
    prepare("INSERT OR IGNORE INTO captchas(hash, unix_timestamp) - VALUES ('$hash', '$current')"); - $statement->execute(); - - echo "$html_string"; -} - -// Show all posts that have been shadowed by moderators -function show_shadowed($db, $css, $settings, $token) -{ - - print_top_header('Shadowed messages and subs'); - - $statement = $db->prepare("SELECT post_id, org_id, sub, text - FROM threads - WHERE shadow = 'yes' - ORDER BY sub"); - - $result = $statement->execute(); - - $prev_sub = ''; - - $html_string = ''; - - while ($row = $result->fetchArray(SQLITE3_NUM)) { - $post_id = "{$row[0]}"; - $org_id = "{$row[1]}"; - $sub = "{$row[2]}"; - $text = "{$row[3]}"; - $post_text = break_text(bbcode_to_html($text, $settings), $settings); - - $html_string .= "
    "; - $html_string .= "
    "; - $html_string .= ""; - $html_string .= ""; - $html_string .= "prepare("SELECT DISTINCT sub - FROM threads - WHERE shadow = 'no' - ORDER BY sub - COLLATE NOCASE"); - $result = $statement->execute(); - - while ($row = $result->fetchArray(SQLITE3_NUM)) { - $sub = "{$row[0]}"; - $total_posts = give_total_posts($db, $sub, FALSE, $settings); - $html_string .= " | $sub"; - $html_string .= "($total_posts)"; - } - - $html_string .= ""; - - echo "$html_string"; -} - -// Show the existing subs to a user, including their count -// Differentiates between subs with > 10 posts (high-traffic) -// and lower (low-traffic). Also shows the last five subs that -// were posted to. -function show_subs_count($db, $css, $settings) -{ - $out = ''; - - if ( (!empty($settings['no_overboard'])) ) { - $last = array_pop($settings['no_overboard']); - - foreach($settings['no_overboard'] as $no_overboard) { - $str = "'" . $no_overboard . "', "; - $out .= $str; - } - - $out .= "'" . $last . "'"; - - } - - $statement = $db->prepare("SELECT post_id - FROM threads - WHERE sub NOT IN ($out) - AND shadow = 'no'"); - $result = $statement->execute(); - $counter = 0; - - while ($row = $result->fetchArray(SQLITE3_NUM)) { - $counter++; - } - - $statement = $db->prepare("SELECT post_id - FROM threads - WHERE sub NOT IN ($out) - AND shadow = 'no' - AND post_id = org_id"); - $result = $statement->execute(); - $counter_org = 0; - - while ($row = $result->fetchArray(SQLITE3_NUM)) { - $counter_org++; - } - - $replies = $counter - $counter_org; - - $html_string = "

    Subs with some traffic:

    "; - $html_string .= "overboard"; - $html_string .= "($counter_org/$replies)"; - - $statement = $db->prepare("SELECT DISTINCT sub - FROM threads - WHERE shadow = 'no' - ORDER BY sub - COLLATE NOCASE"); - $result = $statement->execute(); - - $high_traffic_subs = array(); - $low_traffic_subs = array(); - - while ($row = $result->fetchArray(SQLITE3_NUM)) { - $display_sub = array(); - $sub = "{$row[0]}"; - $total_posts = give_total_posts($db, $sub, FALSE, $settings); - $total_org_posts = give_total_posts($db, $sub, TRUE, $settings); - $replies = $total_posts - $total_org_posts; - - array_push($display_sub, $sub); - array_push($display_sub, $total_org_posts); - array_push($display_sub, $replies); - - if ( ($total_posts > 10) ) { - // we define any sub with more than ten messages as high traffic - // anything below as low traffic - array_push($high_traffic_subs, $display_sub); - } else { - array_push($low_traffic_subs, $display_sub); - } - } - - - foreach($high_traffic_subs as $display_sub) { - $html_string .= " | $display_sub[0]"; - $html_string .= "($display_sub[1]/$display_sub[2])"; - } - - $html_string .= "



    "; - $html_string .= "

    Other subs:

    "; - - $temp = array_reverse($low_traffic_subs); - $first_display_sub = array_pop($temp); - $low_traffic_subs = array_reverse($temp); - - $html_string .= ""; - $html_string .= "$first_display_sub[0]"; - $html_string .= "($first_display_sub[1]/$first_display_sub[2])"; - - foreach($low_traffic_subs as $display_sub) { - $html_string .= " | "; - $html_string .= "$display_sub[0]($display_sub[1]/$display_sub[2])"; - } - - $html_string .= "


    "; - - $statement = $db->prepare("SELECT DISTINCT sub - FROM threads - WHERE shadow = 'no' - ORDER BY ROWID DESC - LIMIT 5"); - $result = $statement->execute(); - - $html_string .= "

    Subs with recent posts:

    "; - - while ($row = $result->fetchArray(SQLITE3_NUM)) { - $sub = "{$row[0]}"; - $html_string .= "$sub "; - } - - $html_string .= "

    "; - - echo "$html_string"; -} - -// 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 "
    "; - } -} - -// Show the existing subs to a user, without the count -function show_subs_no_count($db, $css) -{ - - $html_string = "

    overboard"; - - $statement = $db->prepare("SELECT DISTINCT sub - FROM threads - WHERE shadow = 'no' - ORDER BY sub - COLLATE NOCASE"); - $result = $statement->execute(); - - while ($row = $result->fetchArray(SQLITE3_NUM)) { - $sub = "{$row[0]}"; - if ( ($sub != '') ) { - $html_string .= " | $sub"; - } - } - - $html_string .= "

    "; - - echo "$html_string"; -} - -// Show the form that allows to set individual feeds. -function show_set_feeds_form($db, $settings, $css) -{ -// rewrite: page ? - $html_string = "


    Set your multifeed:

    "; - $html_string .= "

    "; - $html_string .= "

    "; - $html_string .= ""; - $html_string .= "
    Show everything except: "; - $html_string .= ""; - $html_string .= "
    OR Show nothing but: "; - $html_string .= ""; - $html_string .= ""; - $html_string .= "
    "; - $html_string .= "
    "; - $html_string .= ""; - $html_string .= "
    Name"; - $html_string .= "
    "; - $html_string .= ""; - $html_string .= "" + . "" + . "-------------------------------------" + . "

    " + . "
    $sub/$post_id

    $post_text" + . "

    "; + + if ($prev_sub != $sub) { + + $html_string .= "" + . "-------------------------------------" + . "

    "; + } + + $html_string .= ''; + + $prev_sub = $sub; + } + + echo "$html_string"; +} + +// Show the existing subs to the admin +function show_subs_admin_mod($db, $settings, $css, $token, $type) +{ + if ( ($type == 'admin') ) { + $link = 'a'; + } else { + $link = 'm'; + } + + $html_string = '

    '; + + $statement = $db->prepare("SELECT DISTINCT sub + FROM threads + WHERE shadow = 'no' + ORDER BY sub + COLLATE NOCASE"); + $result = $statement->execute(); + + while ($row = $result->fetchArray(SQLITE3_NUM)) { + $sub = "{$row[0]}"; + $total_posts = give_total_posts($db, $sub, FALSE, $settings); + $html_string .= " | $sub" + . "($total_posts)"; + } + + $html_string .= '

    '; + + echo "$html_string"; +} + +// Show the existing subs to a user, including their count +// Differentiates between subs with > 10 posts (high-traffic) +// and lower (low-traffic). Also shows the last five subs that +// were posted to. +function show_subs_count($db, $css, $settings) +{ + $out = ''; + + if ( (!empty($settings['no_overboard'])) ) { + $last = array_pop($settings['no_overboard']); + + foreach($settings['no_overboard'] as $no_overboard) { + $str = "'" . $no_overboard . "', "; + $out .= $str; + } + + $out .= "'" . $last . "'"; + + } + + $statement = $db->prepare("SELECT post_id + FROM threads + WHERE sub NOT IN ($out) + AND shadow = 'no'"); + $result = $statement->execute(); + $counter = 0; + + while ($row = $result->fetchArray(SQLITE3_NUM)) { + $counter++; + } + + $statement = $db->prepare("SELECT post_id + FROM threads + WHERE sub NOT IN ($out) + AND shadow = 'no' + AND post_id = org_id"); + $result = $statement->execute(); + $counter_org = 0; + + while ($row = $result->fetchArray(SQLITE3_NUM)) { + $counter_org++; + } + + $replies = $counter - $counter_org; + + $html_string = "

    Subs with some traffic:

    " + . "overboard" + . "($counter_org/$replies)"; + + $statement = $db->prepare("SELECT DISTINCT sub + FROM threads + WHERE shadow = 'no' + ORDER BY sub + COLLATE NOCASE"); + $result = $statement->execute(); + + $high_traffic_subs = array(); + $low_traffic_subs = array(); + + while ($row = $result->fetchArray(SQLITE3_NUM)) { + $display_sub = array(); + $sub = "{$row[0]}"; + $total_posts = give_total_posts($db, $sub, FALSE, $settings); + $total_org_posts = give_total_posts($db, $sub, TRUE, $settings); + $replies = $total_posts - $total_org_posts; + + array_push($display_sub, $sub); + array_push($display_sub, $total_org_posts); + array_push($display_sub, $replies); + + if ( ($total_posts > 10) ) { + // we define any sub with more than ten messages as high traffic + // anything below as low traffic + array_push($high_traffic_subs, $display_sub); + } else { + array_push($low_traffic_subs, $display_sub); + } + } + + + foreach($high_traffic_subs as $display_sub) { + $html_string .= " | $display_sub[0]" + . "($display_sub[1]/$display_sub[2])"; + } + + $html_string .= '




    Other subs:

    '; + + $temp = array_reverse($low_traffic_subs); + $first_display_sub = array_pop($temp); + $low_traffic_subs = array_reverse($temp); + + $html_string .= "" + . "$first_display_sub[0]" + . "($first_display_sub[1]/$first_display_sub[2])"; + + foreach($low_traffic_subs as $display_sub) { + $html_string .= " | " + . "$display_sub[0]($display_sub[1]/$display_sub[2])"; + } + + $statement = $db->prepare("SELECT DISTINCT sub + FROM threads + WHERE shadow = 'no' + ORDER BY ROWID DESC + LIMIT 5"); + $result = $statement->execute(); + + $html_string .= '


    ' + . '

    Subs with recent posts:

    '; + + while ($row = $result->fetchArray(SQLITE3_NUM)) { + $sub = "{$row[0]}"; + $html_string .= "$sub "; + } + + $html_string .= '

    '; + + echo "$html_string"; +} + +// 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 "
    "; + } +} + +// Show the existing subs to a user, without the count +function show_subs_no_count($db, $css) +{ + + $html_string = "

    overboard"; + + $statement = $db->prepare("SELECT DISTINCT sub + FROM threads + WHERE shadow = 'no' + ORDER BY sub + COLLATE NOCASE"); + $result = $statement->execute(); + + while ($row = $result->fetchArray(SQLITE3_NUM)) { + $sub = "{$row[0]}"; + if ( ($sub != '') ) { + $html_string .= " | $sub"; + } + } + + $html_string .= '

    '; + + echo "$html_string"; +} + +// Show the form that allows to set individual feeds. +function show_set_feeds_form($db, $settings, $css) +{ + + $html_string = '


    Set your multifeed:

    ' + . '

    ' + . '

    ' + . '
    Name"; - - if ( ($type == 'admin') ) { - $html_string .= "
    "; - $html_string .= ""; - $html_string .= ""; + } else { + $html_string .= 'required placeholder=\'Any last words ?...\'>' + . ''; + } + + if ( (!empty($org_id)) ) { + $html_string .= ""; + } + + $token = make_token(); + $current = time(); + + if ($settings['use_captcha']) { + $math_one = rand(20,80); + $math_two = rand(1,19); + // the first number should be bigger than the next, to avoid + // negative results. Also, results should be below 100. + $math_type = rand(0,1); + // 0 means +, 1 means - + if ($math_type == 0) { + $answer = $math_one + $math_two; + $math_type = '+'; + } elseif ($math_type == 1) { + $answer = $math_one - $math_two; + $math_type = '-'; + } + + $summary = ($math_one . $math_two . $math_type . $answer . $token); + $hash = hash('sha512', $summary); + + $html_string .= ""; + } else { + $hash = hash('sha512', $token); + + $html_string .= ""; + } + + $html_string .= '
    Sub to delete
    " + . "$math_one " + . "$math_type $math_two " + . "
    " + . "

    '; + + $statement = $db->prepare("INSERT OR IGNORE INTO captchas(hash, unix_timestamp) + VALUES ('$hash', '$current')"); + $statement->execute(); + + echo "$html_string"; +} + +// Show all posts that have been shadowed by moderators +function show_shadowed($db, $css, $settings, $token) +{ + + print_top_header('Shadowed messages and subs'); + + $statement = $db->prepare("SELECT post_id, org_id, sub, text + FROM threads + WHERE shadow = 'yes' + ORDER BY sub"); + + $result = $statement->execute(); + + $prev_sub = ''; + + $html_string = ''; + + while ($row = $result->fetchArray(SQLITE3_NUM)) { + $post_id = "{$row[0]}"; + $org_id = "{$row[1]}"; + $sub = "{$row[2]}"; + $text = "{$row[3]}"; + $post_text = break_text(bbcode_to_html($text, $settings), $settings); + + $html_string .= "
    " + . "
    " + . "" + . "" + . "" + . "
    " + . "" + . "" + . "" + . "
    " + . "" + . "" + . "
    " + . "" + . "" + . "" + . "
    ' + . '
    Show everything except: ' + . '' + . '
    OR Show nothing but: ' + . '' + . '' + . '
    ' + . '

    '; + + echo "$html_string"; +} + +// Show the form that allows to apply for a moderators account. +function show_apply_form($db, $settings) +{ +//rewrite to set rows ? + $html_string = '

    Give the desired name for your account, ' + . 'an email address' + . ' (or other contact) and a password.

    ' + . '

    ' + . '

    ' + . '' + . '
    Name' + . '
    Email' + . '
    Pass' + . '

    '; + + echo "$html_string"; +} + +// Show the form that allows to set passwords for the admin account. +function show_set_password_form($db, $settings) +{ +//rewrite to set rows ? + $html_string = '

    Give the name of your account and the token' + .' to set your password.

    ' + .'

    ' + .'

    ' + .'' + .'
    Name
    Token' + .'
    Pass' + .'' + .'

    '; + + echo "$html_string"; +} + +// Show the form that enables the admin and the mods to delete, +// shadow or move messages and subs +function show_form_admin_mod($db, $sub, $token, $type) +{ + + $html_string = '

    '; + + if ( ($type == 'admin') ) { + $html_string .= '

    ' + .'' + .''; + } else { + $html_string .= '
    Sub to delete
    Posts to delete
    ' + . '' + . '' + . '' + . '' + . ''; + } + + $html_string .= "" + . ""; + + $html_string .= ''; + } else { + $html_string .= 'value=\'Delete/move\'>
    '; + } + + $html_string .= '
    Sub to delete' + . '
    Posts to delete/move
    Sub to move posts to


    '; + + echo "$html_string"; +} + +// Display the logs to the admin +function show_logs($db, $type, $token) +{ + + $logs = array(); + + if ($type == 'all') { + $statement = $db->prepare("SELECT timestamp, type, event, ip + FROM logs + ORDER BY unix_timestamp DESC"); + } else { + $statement = $db->prepare("SELECT timestamp, type, event, ip + FROM logs + WHERE type = '$type' + ORDER BY unix_timestamp DESC"); + } + + $result = $statement->execute(); + + while ($row = $result->fetchArray(SQLITE3_NUM)) { + $log = array(); + $timestamp = "{$row[0]}"; + $log_type = "{$row[1]}"; + $event = "{$row[2]}"; + $ip = "{$row[3]}"; + array_push($log, $timestamp, $log_type, $event, $ip); + array_push($logs, $log); + } + + $html_string = '' + . '' + . ''; + + foreach($logs as $log_data) { + + $html_string .= "" + . "" + . "" + . "" + . "" + . ""; + + } + + $html_string .= "
    TimestampTypeLogtextip
    $log_data[0]$log_data[1]$log_data[2]$log_data[3]
    " + . "clear logs"; + // eight is the css for the admin + + 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 = ' bot tarpit ' + . '

    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

    '; + + if ( ($settings['enable_bot_block'] == TRUE) ) { + $html_string .= '

    Don\'t follow the links,' + . ' and don\'t visit this page again,' + . ' or you might get blocked.

    '; + } + + 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 = "$line"; + $html_string .= "

    $post_text

    "; + } + + $html_string .= ''; + + echo "$html_string"; +} + +// Unshadow one post. +function unshadow_post($db, $settings, $token) +{ + + $post = strip_tags($_POST['unshadow_id']); + $sub = strip_tags($_POST['sub']); + + $statement = $db->prepare("UPDATE threads + SET shadow = 'no' + WHERE post_id = '$post' + AND sub = '$sub'"); + + $result = $statement->execute(); + + $unshadow_message = "message $post from sub $sub unshadowed"; + log_event($db, $settings, "del", $unshadow_message, ''); + + $html_string = "

    $unshadow_message

    " + . "

    Done.

    " + . "

    Back

    "; + + echo "$html_string"; +} + +// Unshadow a whole sub. +function unshadow_sub($db, $settings, $token) +{ + + $sub = strip_tags($_POST['unshadow_sub']); + + $statement = $db->prepare("UPDATE threads + SET shadow = 'no' + WHERE sub = '$sub'"); + + $result = $statement->execute(); + + $unshadow_message = "sub $sub unshadowed"; + log_event($db, $settings, "del", $unshadow_message, ''); + + $html_string = "

    $unshadow_message

    " + . "

    Done.

    " + . "

    Back

    "; + + echo "$html_string"; +} + +// Lets the admin view the status of the mods +function view_mods($db, $css, $settings, $token) +{ + + $statement = $db->prepare("SELECT name, email, type + FROM keys + WHERE type in ('mod', + 'application', + 'disabled')"); + $result = $statement->execute(); + + print_top_header('Moderators accounts'); + + $html_string = '' + . '' + . ''; + + while ($row = $result->fetchArray(SQLITE3_NUM)) { + + $name = "{$row[0]}"; + $contact = "{$row[1]}"; + $type = "{$row[2]}"; + + $html_string .= ""; + $html_string .= ""; + + if ($type != 'mod') { + $html_string .= ""; + } else { + $html_string .= ""; + } + + if ( ($type != 'application') && ($type != 'disabled') ) { + $html_string .= ""; + } else { + $html_string .= ""; + } + + $html_string .= "" + . "" + . ""; + + } + + $html_string .= "
    Account nameContactStatusEnableDisableDelete
    $name$contact$type
    " + . "" + . "" + . "" + . "
    " + . "" + . "" + . "" + . "
    " + . "" + . "" + . "
    "; + + echo "$html_string"; +} + +//++++++++++++++++ Actual control flow starts here +++++++++++++++++++// + +$db_file = $settings['work_dir'] . $settings['board_file']; +// initialize db +$db = new SQLite3($db_file, SQLITE3_OPEN_CREATE | SQLITE3_OPEN_READWRITE); +$db->busyTimeout(50000); +// set a long timeout + +// if this condition is fulfilled, we have most likely crashed before +// need to do real error handling (rewrite) +if ( (!file_exists($db_file)) ) { + echo "

    Cannot find file $db_file, are the settings correct ?

    "; + exit; +} + +//make tables if needed +make_tables($db); + +// checks with which mode we are called +$short_mode = read_pretty_vars(1, 'alnum', 20); +// we read from the first position. Mode descriptions need 20 characters max. + +// try to check the visitors (local) ip. On success, check if this ip should +// be blocked or not (according to the settings in the config file) +// also check if the portal has been passed or not, and display it +// if not (and it is enabled). +// Updated with more variables than just the (local) ip, it's now working +// surprisingly well, also on tor. The variable is still called 'ip'. +if ( (!empty($_SERVER['REMOTE_ADDR'])) + && ($settings['enable_logging'] == TRUE) ) { + $visitor_ip = $_SERVER['REMOTE_ADDR']; + + $check_server = array ( + 'HTTP_USER_AGENT', + 'HTTP_ACCEPT_ENCODING', + 'HTTP_ACCEPT_LANGUAGE', + 'HTTP_ACCEPT', + 'HTTP_HOST', + 'HTTP_X_REQUESTED_WITH', + 'HTTP_X_I2P_DESTB64', + 'HTTP_X_I2P_DESTB32', + 'HTTP_X_I2P_DESTHASH', + 'HTTP_UPGRADE_INSECURE_REQUESTS', + 'HTTP_SEC_GPC', + 'HTTP_PRIORITY', + 'HTTP_SEC_FETCH_MODE', + 'HTTP_SEC_FETCH_DEST' + ); + + foreach ($check_server as $id) { + if ( (!empty($_SERVER[$id])) ) { + $visitor_ip .= $_SERVER[$id]; + } + } + + $visitor_ip = hash('sha512', $visitor_ip); + + if ( (empty($_POST['portal'])) && ($short_mode != 'b') ) { + check_portal($db, $settings, $visitor_ip); + } elseif ( (!empty($_POST['portal'])) ) { + $portal_message = 'pass'; + log_event($db, $settings, 'portal', $portal_message, $visitor_ip); + redirect_target(); + quit($db, ''); + } + + check_max_requests($db, $settings, $visitor_ip); + bot_block($db, $settings, $visitor_ip); +} else { + $visitor_ip = ''; +} + +$mode = set_mode($short_mode, $settings); + +// initializes the css +$css = set_css($mode); +$css_file = set_css_file($css); + +// First actions that are common to many modes: check which sub is +// requested and if it exists +$sub_wanted = array( + 'admin', 'delete_admin', 'delete_mod', 'dump', + 'mod', 'post', 'reply', 'view' +); + +if ( (in_array($mode, $sub_wanted)) ) { + $sub = set_sub($settings); +} + +$check_sub = array( + 'admin', 'delete_admin', 'delete_mod', 'dump', + 'mod', 'reply', 'view' +); + +if ( (in_array($mode, $check_sub)) ) { + $sub_check = check_sub_exists($db, $sub); + + if ($sub_check == FALSE) { + print_header($css_file); + quit($db, "

    Sub '$sub' does not exist.

    "); + } else { + $sub = $sub_check; + } +} + +// Do the things that only admins and mods need: check if access +// is enabled and do auth checks +$admin_mode = array( + 'admin', 'change_mods', 'delete_admin', 'delete_logs', + 'import', 'logs', 'shadow', 'unshadow_post', + 'unshadow_sub', 'view_mods' +); + +if ( (in_array($mode, $admin_mode)) ) { + check_admin_panel($db, $settings); + $token = check_auth_admin($db, $settings); +} + +$mod_mode = array( + 'delete_mod', 'mod' +); + +if ( (in_array($mode, $mod_mode)) ) { + check_mod_panel($db, $settings); + $token = check_auth_mod($db, $settings); +} + +// Show the header, this is for almost all modes +$show_header = array( + 'admin', 'apply', 'auth_admin', 'auth_mod', + 'change_mods', 'delete_admin', 'delete_logs', 'delete_mod', + 'destroy_token', 'import', 'individual_view', 'landing', + 'logs', 'mod', 'password', 'post', + 'reply', 'shadow', 'setup', 'subs', + 'unshadow_post', 'unshadow_sub', 'view', 'view_mods' +); + +if ( (in_array($mode, $show_header)) ) { + print_header($css_file); +} + +// debug_server($db, $settings); +// remove comment slashes to enable debugging + + +// Do particular actions for the mode we have been called with and exit. +// Note: there are no breaks in any of these cases, as there usually would. +// Instead, each case calls the quit function at some point, which exits +// the script. +switch($mode) { + + // displays the admin panel + case 'admin': + echo "

    $sub

    "; + show_subs_admin_mod($db, $settings, $css, $token, 'admin'); + show_form_admin_mod($db, $sub, $token, 'admin'); + print_sub($db, $sub, $css, $settings, 'all'); + print_footer_admin($css, $settings, $token); + quit($db, ""); + + // checks if applications are taken for moderator accounts, if yes, + // displays the form and processes the input + case 'apply': + if ($settings['take_applications'] != TRUE) { + header('HTTP/1.1 403 Forbidden'); + quit($db, '403'); + } + + if (check_application_count($db) == TRUE) { + if ( (!empty($_POST['appl_name'])) + && (!empty($_POST['appl_email'])) ) { + if (strlen($_POST['appl_password']) < 10) { + // each password should be ten characters at least + $quit_message = '

    Password must be at least 10' + . ' characters. Please retry.

    '; + quit($db, $quit_message); + } elseif (!preg_match("#[a-zA-Z]+#", $_POST['appl_password'])) { + $quit_message = '

    Password must include at least' + . ' one letter. Please retry.

    '; + quit($db, $quit_message); + } elseif (preg_match('/^(.)\1*$/u ', $_POST['appl_password'])) { + $quit_message = '

    Password cannot be one letter ' + . 'only. Please retry.

    '; + quit($db, $quit_message); + } + + $appl_name = filter($_POST['appl_name'], 'alnum', 50); + // 50 characters should be enough for a moderators name + $appl_email = filter($_POST['appl_email'], 'email', 50); + // 50 characters should be enough for a moderators email + $appl_password = password_hash($_POST['appl_password'], + PASSWORD_DEFAULT); + echo '

    Received application for moderator account: '; + echo "$appl_name.

    "; + set_application($db, $appl_name, $appl_email, + $appl_password, $settings); + } else { + show_apply_form($db, $settings); + } + } else { + $quit_message = '

    Too many pending applications already,' + . ' try later.

    '; + quit($db, $quit_message); + } + + quit($db, ""); + + // authentification for the admin + case 'auth_admin': + check_admin_panel($db, $settings); + fail2ban($db, $settings); + if (!check_admin($db, $settings)) { + show_set_password_form($db, $settings); + } else { + show_auth_form($db, $css, $settings, 'admin'); + } + quit($db, ""); + + // authentification for the mods + case 'auth_mod': + check_mod_panel($db, $settings); + fail2ban($db, $settings); + show_auth_form($db, $css, $settings, 'mod'); + quit($db, ""); + + // checks if bot posting is enabled, if yes processes the post + case 'bot': + if ( (empty($settings['bot_keys'])) + && (empty($settings['anonymous_bot_subs'])) ) { + header( 'HTTP/1.1 403 Forbidden' ); + quit($db, '403'); + } elseif ( (post_block_bot($db, $settings, $visitor_ip) != TRUE) ) { + $bot_block_message = '429 - too many bot posts'; + log_event($db, $settings, 'bot', $bot_block_message, $visitor_ip); + header( 'HTTP/1.1 429 Too Many Requests' ); + quit($db, '429'); + } else { + bot_me($db, $settings); + $bot_message = 'post attempt'; + log_event($db, $settings, 'bot', $bot_message, $visitor_ip); + } + quit($db, '200'); + + // lets the admin view, disable and delete mod accounts + case 'change_mods': + if ( (isset($_POST['disable_mod'])) + || (isset($_POST['enable_mod'])) + || (isset($_POST['delete_mod'])) ) { + change_mods($db, $css, $settings, $token); + } else { + quit($db, '

    Nothing was selected, nothing was unshadowed.

    '); + } + print_footer_admin($css, $settings, $token); + header( "refresh:3;url=/cm/8/$token" ); + // 8 is the css of the admin. + // We wait 3 seconds with the redirection. + $quit_message = "

    Redirection in about 3 secs. If that does" + . " not work, go back."; + quit($db, $quit_message); + + // lets the admin delete messages or whole subs + case 'delete_admin': + + if ( (!empty($_POST['delete_sub'])) ) { + + if ( ($_POST['delete_sub'] != $sub) ) { + quit($db, "

    From here, you can only delete sub $sub.

    "); + } + + delete_sub($db, $sub, $settings, $token, 'no'); + } elseif ( (!empty($_POST['delete_posts'])) ) { + delete_posts($db, $sub, $settings, $token, 'delete'); + } else { + quit($db, '

    Nothing was selected, nothing was deleted.

    '); + } + + quit($db, ""); + + // lets the admin delete the endboard logs + case 'delete_logs': + $type = read_pretty_vars(2, 'alnum', 10); + // we read from the second position. 10 + // characters is enough for the type. + + show_subs_admin_mod($db, $settings, $css, $token, 'admin'); + + delete_logs($db, $type, $token); + print_footer_admin($css, $settings, $token); + quit($db, ""); + + // lets moderators shadow messages or whole subs + case 'delete_mod': + + if ( (!empty($_POST['shadow_sub'])) ) { + + if ( $_POST['shadow_sub'] != $sub ) { + quit($db, "

    From here, you can only delete sub $sub.

    "); + } + + delete_sub($db, $sub, $settings, $token, 'yes'); + } elseif ( (!empty($_POST['shadow_posts'])) + && (!empty($_POST['target_sub'])) ) { + delete_posts($db, $sub, $settings, $token, 'move'); + } elseif ( (!empty($_POST['shadow_posts'])) ) { + delete_posts($db, $sub, $settings, $token, 'shadow'); + } else { + quit($db, '

    Nothing was selected, nothing was deleted.

    '); + } + + quit($db, ""); + + // deletes an access token in the db, so it cannot be used anymore + case 'destroy_token': + $token = read_pretty_vars('last', 'alnum', 250); + // the length of the token is 250 characters + destroy_token($db, $token, $settings, 'log off'); + header( 'refresh:3;url=/s/overboard' ); + // wait 3 seconds with the redirection + $html_string = '

    You logged out. Redirection in about 3 secs.' + . ' If that does not work, go ' + . 'back to overboard.'; + echo "$html_string"; + quit($db, ""); + + // exports a thread, a sub or the whole board to a json file + // and sents it to the user + case 'dump': + $org_id = read_pretty_vars(3, 'number', 10); + // we read from the third postion, ten digits is enough + dump($db, $sub, $org_id, $settings); + quit($db, ""); + + // lets the admin import json dumps + case 'import': + import_overboard($db, $settings); + $quit_message = "

    All done." + . " Back to admin panel

    "; + quit($db, $quit_message); + + // shows an individual feed + case 'individual_view': + $ex_subs = array(); + $in_subs = array(); + $subs = read_pretty_vars(2, 'alnumplus', 1500); + // we read from the second position. 1500 characters + // should be enough to describe the subs (at least ~70 subs). + $title = ''; + + if (strpos($subs, '-') !== FALSE) { + $ex_subs = explode('-', $subs); + } elseif (strpos($subs, '+') !== FALSE) { + $in_subs = explode('+', $subs); + } elseif ( (!empty($_POST['ex_subs'])) ) { + $ex_subs = explode(' ', $_POST['ex_subs']); + } elseif ( (!empty($_POST['in_subs'])) ) { + $in_subs = explode(' ', $_POST['in_subs']); + } else { + quit($db, '

    Please choose subs to include or exclude.

    '); + } + + if (count($ex_subs) > 0) { + // if we have at least one exsub + foreach($ex_subs as $ex_sub) { + $ex_sub = filter($ex_sub, 'alnum', $settings['max_name_sub']); + + if ( ($ex_sub != '') + && (check_sub_exists($db, $ex_sub) == TRUE) ) { + $title .= "-" . $ex_sub; + } + + } + + $description = "ex"; + } elseif (count($in_subs) > 0) { + // otherwise, if we have at least one insub + foreach($in_subs as $in_sub) { + $in_sub = filter($in_sub, 'alnum', $settings['max_name_sub']); + + if ( ($in_sub != '') + && (check_sub_exists($db, $in_sub) == TRUE) ) { + $title .= "+" . $in_sub; + } + + } + + $description = "inc"; + } + + if ( (empty($title)) ) { + $quit_message = '

    The subs you chose do not exist. Please ' + . 'choose existing subs to include or exclude.

    '; + quit($db, $quit_message); + } + + echo "$title"; + + print_top_header("Multifeed ($description)"); + echo '

    '; + + $total_posts = print_individual_feed($db, $css, $settings, + $ex_subs, $in_subs); + + echo '

    '; + + lay_trap($settings); + + print_footer_multifeeds($title, $total_posts, $css, $settings); + quit($db, ""); + + // displays the landing page as defined in the config file + // also: check if there was a request URI, if yes, mark as bot + case 'landing': + if ( ( (!empty($_SERVER['REQUEST_URI'])) ) + && ( (strlen($_SERVER['REQUEST_URI']) > 6) ) ) { + // if more than 6 chars to go to the landing page, + // this could be a bot, especially if repeated + $bot_message = 'landing page bot request'; + log_event($db, $settings, 'bot', $bot_message, $visitor_ip); + } + + show_landing_page($css); + show_subs_no_count($db, $css); + lay_trap($settings); + print_footer_landing($db, $settings); + quit($db, ""); + + // lets the admin view the endboard logs + case 'logs': + show_subs_admin_mod($db, $settings, $css, $token, 'admin'); + $type = read_pretty_vars(2, 'alnum', 10); + // we read from the second position + // 10 characters is enough + + if ( (empty($type)) ) { + $type = 'all'; + } + + show_logs($db, $type, $token); + print_footer_admin($css, $settings, $token); + quit($db, ""); + + // displays the mod panel + case 'mod': + echo "

    $sub

    "; + show_subs_admin_mod($db, $settings, $css, $token, 'mod'); + show_form_admin_mod($db, $sub, $token, 'mod'); + print_sub($db, $sub, $css, $settings, 'all'); + print_footer_mod($css, $settings, $token, $sub); + quit($db, ""); + + // shows the form to set passwords for the admin + case 'password': + check_admin_panel($db, $settings); + fail2ban($db, $settings); + + if (check_admin($db, $settings)) { + show_auth_form($db, $css, $settings, 'admin'); + } else { + show_set_password_form($db, $settings); + } + + quit($db, ""); + + // makes a new post + case 'post': + $org_id = set_post_org_id(); + $text = strip_tags($_POST['text']); + $text_id = hash('sha512', $text); + + check_spam($db, $text, $settings); + + if (check_free_space($db, $settings) == FALSE) { + $quit_message = '

    Filesystem is almost full, ' + . 'no posting possible!

    '; + quit($db, $quit_message); + } elseif ( (check_original_content($db, $settings, $sub, + $text_id, $org_id) == FALSE) ) { + $quit_message = '

    This text has been posted before, ' + . 'the admin requests original content.

    '; + quit($db, $quit_message); + } elseif ( (mb_strtolower(substr( $sub, 0, 9 )) === 'overboard') ) { + // overboard has nine characters + $quit_message = '

    Subs cannot be named \'overboard\', also' + . ' their names cannot start with it.

    '; + quit($db, $quit_message); + } elseif ( (mb_strtolower(substr( $sub, 0, 4 )) === 'main') + && ($sub != 'main') ) { + // main has four characters + quit($db, '

    Subs names cannot start with \'main\'.

    '); + } elseif ( (post_block_user($db, $settings, $visitor_ip) != TRUE) ) { + $post_block_message = '429 - too many user posts'; + log_event($db, $settings, 'user', $post_block_message, $visitor_ip); + header( 'HTTP/1.1 429 Too Many Requests' ); + quit($db, '429'); + } + + check_captcha($db, $settings); + + $post_message = "post attempt"; + log_event($db, $settings, 'user', $post_message, $visitor_ip); + + make_post($db, $sub, $settings, $text, $org_id); + answer_redirect($sub, $css); + + quit($db, ""); + + // shows a thread + case 'reply': + $org_id = set_org_id(); + + if ( (!check_org_id_exists($db, $sub, $org_id)) ) { + quit($db, "

    Post $org_id on sub $sub does not exist.

    "); + } + + $quote = set_quote(); + $msg = ($sub . '/' . $org_id); + + print_top_header("$msg"); + + echo '

    '; + + show_post_form($db, $msg, $sub, $settings, $org_id, + $css, $quote, $visitor_ip); + print_thread($db, $sub, $css, $settings, $org_id); + lay_trap($settings); + print_footer_reply($sub, '', $css, $msg, $org_id, $settings); + + quit($db, ''); + + // lets the admin set their password + case 'setup': + check_admin_panel($db, $settings); + + if (check_admin($db, $settings)) { + show_auth_form($db, $css, $settings, 'admin'); + } + + if (strlen($_POST['auth_password']) < 10) { + // each password must have at least 10 characters + $quit_message = '

    Password must be at least 10 characters.' + . ' Please retry.

    '; + quit($db, $quit_message); + } elseif (!preg_match("#[a-zA-Z]+#", $_POST['auth_password'])) { + $quit_message = '

    Password must include at least one letter.' + . ' Please retry.

    '; + quit($db, $quit_message); + } elseif (preg_match('/^(.)\1*$/u ', $_POST['auth_password'])) { + $quit_message = '

    Password cannot be one letter only.' + . ' Please retry.

    '; + quit($db, $quit_message); + } + + $auth_name = filter($_POST['auth_name'], 'alnum', 50); + // 50 characters is enough for a admin name + $auth_password = password_hash($_POST['auth_password'], + PASSWORD_DEFAULT); + set_password($db, $auth_name, $auth_password, + $_POST['auth_token'], $settings); + $password_message = "password set for: $auth_name"; + log_event($db, $settings, "sys", $password_message, $visitor_ip); + echo "

    $password_message

    "; + echo '

    Back to login

    '; + quit($db, ""); + + // lets the admin view the shadowed posts and subs + case 'shadow': + show_shadowed($db, $css, $settings, $token); + print_footer_admin($css, $settings, $token); + quit($db, ""); + + // displays all subs, including their message counts + case 'subs': + echo 'available subs'; + show_subs_count($db, $css, $settings); + show_set_feeds_form($db, $settings, $css); + lay_trap($settings); + quit($db, ""); + + // sends garbage links to nosy bots and spiders + case 'trap': + print_header('no_file'); + trap_me($db, $settings, $visitor_ip); + quit($db, ""); + + // lets the admin unshadow a post that a moderator shadowed + case 'unshadow_post': + if ( (!empty($_POST['unshadow_id'])) ) { + unshadow_post($db, $settings, $token); + } else { + quit($db, '

    Nothing was selected, nothing was unshadowed.

    '); + } + + print_footer_admin($css, $settings, $token); + quit($db, ""); + + // lets the admin unshadow a sub that a moderator shadowed + case 'unshadow_sub': + if ( (!empty($_POST['unshadow_sub'])) ) { + unshadow_sub($db, $settings, $token); + } else { + quit($db, '

    Nothing was selected, nothing was unshadowed.

    '); + } + + print_footer_admin($css, $settings, $token); + quit($db, ""); + + // shows a sub or the overboard + case 'view': + $page = set_page($mode); + + if ( $sub == 'overboard' ) { + $title = $settings['title']; + } else { + $title = "sub/$sub"; + } + + echo "$title"; + + $total_posts = give_total_posts($db, $sub, TRUE, $settings); + + if ( ($page != 'all') + && ((($settings['pagination'] * $page) > + ($total_posts + $settings['pagination']))) ) { + quit($db, "

    Not enough posts to display page = $page...!

    "); + } + + print_top_header("$sub"); + echo '

    '; + + lay_trap($settings); + + if ( ($sub != '') && $sub != 'overboard' ) { + show_post_form($db, '', $sub, $settings, '', $css, '', $visitor_ip); + print_sub($db, $sub, $css, $settings, $page); + } else { + print_overboard($db, $css, $settings, $page); + } + + echo '

    '; + + lay_trap($settings); + + print_footer_sub($sub, $total_posts, $css, $page, $settings); + quit($db, ""); + + // lets the admin view, disable and delete mod accounts + case 'view_mods': + view_mods($db, $css, $settings, $token); + print_footer_admin($css, $settings, $token); + quit($db, ""); + +} + +// EOF