wait for it...' . '

Redirection in about 10 secs.' . ' Save this string, if you ' . ' want to edit your post later on:
' . "$credentials
" . ' If the redirection does not work, click ' . "here" . ' to go back.'; } else { header( "refresh:3;url=/s/$sub/css=$css/random=$random_string" ); // we wait 3 seconds with the redirection $html_string = 'wait for it...' . '

Redirection in about 3 secs.' . ' If that does not work, go ' . "back."; } echo "$html_string"; } // If the post is a reply, put the original post on top. function bump_message($db, $org_id, $sub, $settings) { if ( $settings['auto_sage'] > 0 ) { $statement = $db->prepare("SELECT global_id FROM threads WHERE org_id = '$org_id' AND sub = '$sub'"); $result = $statement->execute(); $counter = 0; while ($row = $result->fetchArray(SQLITE3_NUM)) { $counter++; } if ( $counter > $settings['auto_sage'] ) { return; } } $statement = $db->prepare("SELECT post_id, text, global_id, text_id, timestamp, name, tripcode, original, edit_message, move_message FROM threads WHERE original = '$org_id' AND shadow = 'no' AND sub = '$sub'"); $result = $statement->execute(); while ($row = $result->fetchArray(SQLITE3_NUM)) { $post_id = "{$row[0]}"; $text = "{$row[1]}"; $global_id = "{$row[2]}"; $text_id = "{$row[3]}"; $timestamp = "{$row[4]}"; $name = "{$row[5]}"; $tripcode = "{$row[6]}"; $original = "{$row[7]}"; $edit_message = "{$row[8]}"; $move_message = "{$row[9]}"; } $statement = $db->prepare("DELETE FROM threads WHERE original = '$org_id' AND shadow = 'no' AND sub = '$sub'"); $result = $statement->execute(); $statement = $db->prepare("INSERT INTO threads(post_id, sub, text, org_id, shadow, global_id, text_id, timestamp, name, tripcode, original, edit_message, move_message) VALUES ('$post_id', '$sub', ?, '$org_id', 'no', '$global_id', '$text_id', '$timestamp', '$name', '$tripcode', '$original', ?, ?)"); $statement->bindParam(1, $text); $statement->bindParam(2, $edit_message); $statement->bindParam(3, $move_message); $statement->execute(); } // 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.' . '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($settings['work_dir']); if ($free_space < ($settings['min_space'] * 1024 * 1024)) { // the setting is in Megabyte, free space operates in bytes return FALSE; } return TRUE; } // Check if a name exists already, and if yes, check credentials. // If not, return false. function check_name($db, $name, $tripkey) { $statement = $db->prepare("SELECT tripcode FROM threads WHERE name = '$name'"); $result = $statement->execute(); $tripcode = ''; while ($row = $result->fetchArray(SQLITE3_NUM)) { $tripcode = "{$row[0]}"; if (!password_verify($name . $tripkey, $tripcode)) { // add logging to prevent password spraying attacks quit($db, "

Name#tripkey combination is not valid.

"); } break; } if (empty($tripcode)) { $tripcode = password_hash($name . $tripkey, PASSWORD_DEFAULT); } return $tripcode; } // 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 org_id = '$org_id'"); $result = $statement->execute(); $counter = 0; while ($row = $result->fetchArray(SQLITE3_NUM)) { $counter++; break; } 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 ' . '(yeah, Unicode...)!

'; quit($db, $quit_message); } } // 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++; // we increase the largest number by one to get the new post_id return $largest; } // Get the token from the post stream. function get_post_token() { $token = ''; if ( (!empty($_POST['token'])) ) { $token = filter($_POST['token'], 'alnum', 250); // length of token is 250 characters } return $token; } // Make an edit to an existing post function make_edit($db, $sub, $post_id, $ip, $settings) { if ( ( $settings['enable_tripcodes'] != TRUE ) ) { quit($db, "

Tripcodes need to be enabled for this to work.

"); } if ( (!check_post_exists($db, $sub, $post_id)) ) { quit($db, "

Post $post_id on sub $sub does not exist.

"); } if ( ( $settings['enable_timestamps'] == TRUE ) && ( (!empty($_POST['edit_timestamp'])) ) ) { $timestamp = make_timestamp($settings); } else { $timestamp = ''; } if ( (!empty($_POST['edit_combination'])) ) { $parts = explode('#', $_POST['edit_combination']); if ( (empty($parts[0])) || (empty($parts[1])) ) { quit($db, "

Name#tripkey not found.

"); } $name = filter($parts[0], 'email', 20); $tripkey = filter($parts[1], 'alnum', 50); } else { quit($db, "

Name#tripkey are needed for this.

"); } $statement = $db->prepare("SELECT tripcode, org_id, original, move_message FROM threads WHERE sub = '$sub' AND shadow = 'no' AND post_id = '$post_id' ORDER BY ROWID DESC"); $result = $statement->execute(); while ($row = $result->fetchArray(SQLITE3_NUM)) { $tripcode_post = "{$row[0]}"; $org_id = "{$row[1]}"; $original = "{$row[2]}"; $move_message = "{$row[3]}"; } if (!password_verify($name . $tripkey, $tripcode_post)) { $auth_message = 'name#tripkey combination not valid'; log_event($db, $settings, 'auth', $auth_message, $ip); sleep(10); quit($db, "

Name#tripkey combination is not valid.

"); } $statement = $db->prepare("UPDATE threads SET shadow = 'yes' WHERE post_id = '$post_id' AND sub = '$sub'"); $result = $statement->execute(); $new_post_id = get_new_post_id($db, $sub); $text = strip_tags($_POST['edit_text']); $text_id = hash('sha512', $text); $global_id = hash('sha512', $sub . $new_post_id . $org_id . $text); $edit_message = 'edited by user, click "edit" to see the history'; $statement = $db->prepare("INSERT INTO threads(post_id, sub, text, org_id, shadow, global_id, text_id, timestamp, name, tripcode, original, move_message, edit_message) VALUES ('$new_post_id', '$sub', ?, '$org_id', 'no', '$global_id', '$text_id', '$timestamp', '$name', '$tripcode_post', '$original', ?, ?)"); $statement->bindParam(1, $text); $statement->bindParam(2, $move_message); $statement->bindParam(3, $edit_message); $statement->execute(); return $post_id; } // Make a timestamp, precision is set in the config file function make_timestamp($settings){ $month = gmdate("F"); $year = gmdate("Y"); if ( $settings['precision_timestamps'] == 'middle' ) { $timestamp = $month . '/' . $year; } elseif ( $settings['precision_timestamps'] == 'high' ) { $timestamp = gmdate("Y-m-d"); } elseif ( $settings['precision_timestamps'] == 'insane' ) { $timestamp = gmdate("Y-m-d H:i:s"); } else { $quarter_1 = array('January', 'February', 'March'); $quarter_2 = array('April', 'May', 'June'); $quarter_3 = array('July', 'August', 'September'); $quarter_4 = array('October', 'November', 'December'); if ( in_array($month, $quarter_1 ) ){ $timestamp = 'Q1'; } elseif ( in_array($month, $quarter_2 ) ){ $timestamp = 'Q2'; } elseif ( in_array($month, $quarter_3 ) ){ $timestamp = 'Q3'; } elseif ( in_array($month, $quarter_4 ) ){ $timestamp = 'Q4'; } $timestamp .= '/' . $year; } return $timestamp; } // 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); if ( ( $settings['enable_timestamps'] == TRUE ) && ( (!empty($_POST['timestamp'])) ) ) { $timestamp = make_timestamp($settings); } else { $timestamp = ''; } if ( ( $settings['enable_tripcodes'] == TRUE ) && (!empty($_POST['combination'])) ) { $parts = explode('#', $_POST['combination']); $name = filter($parts[0], 'email', 20); $tripkey = filter($parts[1], 'alnum', 50); $tripcode = check_name($db, $name, $tripkey); } elseif ( ( $settings['enable_tripcodes'] == TRUE ) && (!empty($_POST['mod'])) && (!empty($_POST['pass'])) ) { $name = filter($_POST['mod'], 'email', 20); $tripkey = filter($_POST['pass'], 'alnum', 50); $tripcode = check_name($db, $name, $tripkey); } else { $name = ''; $tripcode = ''; } $statement = $db->prepare("INSERT INTO threads(post_id, sub, text, org_id, shadow, global_id, text_id, timestamp, name, tripcode, original) VALUES ('$post_id', '$sub', ?, '$org_id', 'no', '$global_id', '$text_id', '$timestamp', '$name', '$tripcode', '$post_id')"); $statement->bindParam(1, $text); $statement->execute(); if ( ($org_id != $post_id) && ($settings['enable_bumping'] == TRUE) && (!isset($_POST['sage'])) ){ bump_message($db, $org_id, $sub, $settings); } return $post_id; } function make_tripcode($settings) { $tripkey = make_token(25, 'alnum'); $differ = make_token(6, 'alnum'); $name = $settings['prefix_autogen'] . $differ; $combination = $name . '#' . $tripkey; return $combination; } // 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; } // register a new sub in the table "subs" function register_sub($db, $name, $sub, $motto, $css, $botkey) { $statement = $db->prepare("INSERT INTO subs(name, type, moderator, botkey, css, motto) VALUES ('$sub', 'public', '$name', '$botkey', '$css', ?)"); $statement->bindParam(1, $motto); $statement->execute(); } // EOF