dev_endboard/opt/admin.php

1349 lines
47 KiB
PHP

<?php
/*
* This is the endboard software, version beta 0.73
* It is a textboard written for the use in the darknets.
*
* This file holds all the functions for admins and mods. It can be
* included without side effects.
*
* The writing of this code started some time ago with another software
* called smolBBS. Although there is almost no original code left now,
* I still regard endboard as a fork of smolBBS.
* The author of smolBBS has required that the following text be
* distributed with any redistribution, so here it goes.
* The license and other conditions apply to endboard as well.
*
* IRC: *dulm @ irc.rizon.net
*
* Copyright (C) 2020 sandlind
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* (1) Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* (2) Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* (3)The name of the author may not be used to
* endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
// 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 = "<h1>The name of the admin is not updated "
. "in the configfile, or it is empty. Set "
. "the variable \$admin in the file "
. " $config_file and retry.</h1>";
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(250, 'alnum');
$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, 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, name
FROM keys
WHERE type = 'admin'");
$result = $statement->execute();
while ($row = $result->fetchArray(SQLITE3_NUM)) {
$timestamp_token = "{$row[0]}";
$access_token = "{$row[1]}";
$auth_name = "{$row[2]}";
}
$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 = '<h1>Token expired.<a href=\'/aa\'>Log in </a>'
. 'again to get a new one.'
. 'This incident was logged.</h1>';
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 = '<h1>Invalid token used.'
. 'This incident was logged.</h1>';
quit($db, $quit_message);
}
} elseif ( (!isset($_POST['auth_name']))
|| (!isset($_POST['auth_password'])) ) {
$quit_message = '<h1>Please provide the name and'
. ' password for your account.</h1>';
quit($db, $quit_message);
} else {
$auth_name = filter($_POST['auth_name'], 'alnum', 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 = '<h1>Combination of password and username '
. 'is invalid. This incident was logged.</h1>';
quit($db, $quit_message);
}
$auth_message = "admin $auth_name logged on";
log_event($db, $settings, 'auth', $auth_message, '');
$current = time();
$statement = $db->prepare("UPDATE keys
SET timestamp_token = '$current'
WHERE name = '$auth_name'
AND type = 'admin'");
$result = $statement->execute();
}
$token = make_token(250, 'alnum');
$token_hash = password_hash($token, PASSWORD_DEFAULT);
$statement = $db->prepare("UPDATE keys
SET token = '$token_hash'
WHERE name = '$auth_name'
AND type = 'admin'");
$result = $statement->execute();
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, name
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]}";
$auth_name = "{$row[2]}";
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, '<h1>Token invalid. This incident was logged.</h1>');
}
$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 = '<h1>Token expired.<a href=\'/am\'>Log in</a>'
. ' again to get a new one. '
. 'This incident was logged.</h1>';
quit($db, $quit_message);
}
} elseif ( (!isset($_POST['auth_name']))
|| (!isset($_POST['auth_password'])) ) {
$quit_message = '<h1>Please provide the name'
. ' and password for your account.</h1>';
quit($db, $quit_message);
} else {
$auth_name = filter($_POST['auth_name'], 'alnum', 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 = '<h1>Combination of password and username is'
. ' invalid. This incident was logged.</h1>';
quit($db, $quit_message);
}
$auth_message = "mod $auth_name logged on";
log_event($db, $settings, 'auth', $auth_message, '');
$current = time();
$statement = $db->prepare("UPDATE keys
SET timestamp_token = '$current'
WHERE name = '$auth_name'
AND type = 'mod'");
$result = $statement->execute();
}
$token = make_token(250, 'alnum');
$token_hash = password_hash($token, PASSWORD_DEFAULT);
$statement = $db->prepare("UPDATE keys
SET token = '$token_hash'
WHERE name = '$auth_name'
AND type = 'mod'");
$result = $statement->execute();
return $token;
}
// 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');
}
}
// 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 = filter($_POST['target_sub'], 'alnum',
$settings['max_name_sub']);
if ( (check_sub_exists($db, $target_sub) != TRUE) ) {
quit($db, "<h1>Sub $target_sub does not exist.</h1>");
}
} 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 "
. "sub $sub deleted by $name";
log_event($db, $settings, "del", $delete_message, '');
$html_string .= "<h1>$delete_message</h1>";
}
} 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 .= "<h1>$delete_message</h1>";
}
} elseif ( (!empty($delete_array)) && ($delete_mode == 'move') ) {
move_post($db, $settings, $delete_array, $sub, $target_sub, $name);
}
$html_string .= '<h1>Done.</h1>';
if ($delete_mode == 'delete') {
$html_string .= "<h1><a href='/a/$sub/8/$token'>Back</a></h1>";
} else {
$html_string .= "<h1><a href='/m/$sub/7/$token'>Back</a></h1>";
}
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 .= "<h1>$delete_message</h1>";
} 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 .= "<h1>$delete_message</h1>";
}
$html_string .= "<h1>Done.</h1>";
if ($shadow_only == 'yes') {
$html_string .= "<h1><a href='/m/main/7/$token'>Back</a></h1>";
} else {
$html_string .= "<h1><a href='/a/main/8/$token'>Back</a></h1>";
}
echo "$html_string";
}
// Delete an admin or mod token to end the session
function destroy_token($db, $token, $settings, $log_message)
{
$name = '';
$statement = $db->prepare("SELECT name, token
FROM keys");
$result = $statement->execute();
while ($row = $result->fetchArray(SQLITE3_NUM)) {
$name = "{$row[0]}";
$access_token = "{$row[1]}";
if ( (password_verify($token, $access_token)) ) {
break;
}
}
if ( (empty($name)) ) {
$log_message = 'invalid token used';
} else {
$statement = $db->prepare("UPDATE keys
SET token = 'none'
WHERE name = '$name'");
$result = $statement->execute();
$log_message .= ": $name";
}
log_event($db, $settings, 'auth', $log_message, '');
}
// do a full dump of the board, including names and tripcodes. The file
// will be saved locally (in /var/opt/endboard/). Can be called by the
// admin via the admins interface, or from cli.
function dump_full($db, $settings)
{
// rewrite to include ranges
$json_dump = array();
$statement = $db->prepare("SELECT post_id, org_id,
sub, text, timestamp, name,
tripcode, original, shadow,
move_message, edit_message
FROM threads
");
$result = $statement->execute();
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]}";
$post['timestamp'] = "{$row[4]}";
$post['name'] = "{$row[5]}";
$post['tripcode'] = "{$row[6]}";
$post['original'] = "{$row[7]}";
$post['shadow'] = "{$row[8]}";
$post['move_message'] = "{$row[9]}";
$post['edit_message'] = "{$row[10]}";
array_push($json_dump, $post);
}
$diff = make_token(20, 'alnum');
$filename = $settings['work_dir'] . 'full_dump_' . $diff . '.json';
file_put_contents($filename, json_encode($json_dump,
JSON_PRETTY_PRINT
| JSON_NUMERIC_CHECK
| JSON_UNESCAPED_UNICODE));
}
// 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 = "<div class='post'>Found importfile $board_file </div>";
} else {
$html_string = "<div class='post'>Could not find importfile "
. "$board_file, aborting.</div>";
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($post['timestamp'])) {
$timestamp = $post['timestamp'];
} else {
$timestamp = '';
}
if (!empty($post['name'])) {
$name = $post['name'];
} else {
$name = '';
}
if (!empty($post['tripcode'])) {
$tripcode = $post['tripcode'];
} else {
$tripcode = '';
}
if (!empty($post['move_message'])) {
$move_message = $post['move_message'];
} else {
$move_message = '';
}
if (!empty($post['edit_message'])) {
$edit_message = $post['edit_message'];
} else {
$edit_message = '';
}
if ( (!empty($text)) && (!empty($sub))
&& (!empty($post_id)) && (!empty($org_id)) ) {
if (!check_post_exists($db, $sub, $post_id)) {
$global_id = hash('sha512', $sub . $post_id .
$org_id . $text . $timestamp);
$text_id = hash('sha512', $text);
$statement = $db->prepare("INSERT OR IGNORE INTO threads
(post_id, sub, text, org_id,
shadow, global_id, text_id,
timestamp, name, tripcode,
original, move_message,
edit_message)
VALUES ('$post_id', '$sub', ?,
'$org_id', 'no', '$global_id',
'$text_id', '$timestamp',
'$name', '$tripcode',
'$post_id', ?, ?')");
$statement->bindParam(1, $text);
$statement->bindParam(2, $move_message);
$statement->bindParam(3, $edit_message);
$statement->execute();
$counter++;
$import_message = "message $post_id from sub $sub imported";
log_event($db, $settings, 'import', $import_message, '');
$html_string .= "<div class='post'>$import_message</div>";
} else {
$import_message = "message $post_id from sub $sub"
. " existed already and was not imported";
log_event($db, $settings, 'import', $import_message, '');
$html_string .= "<div class='post'>$import_message</div>";
}
}
}
rename($board_file, $board_file . '.done');
$html_string .= "<div class='post'>Finished, imported "
. "$counter messages altogether.</div>"
. "<div class='post'>The boardfile was "
. "renamed to avoid another import.</div>";
echo "$html_string";
}
// Moves a post to a different sub. In case it is an original post, all
// replies are 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, name,
timestamp, tripcode, move_message,
edit_message, original
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]}";
$name = "{$row[3]}";
$timestamp = "{$row[4]}";
$tripcode = "{$row[5]}";
$move_message = "{$row[6]}";
$edit_message = "{$row[7]}";
$original = "{$row[8]}";
}
if ( ($org_id == $original) ) {
$new_move = "system message: "
. "post $post_id from sub \"$sub\" was moved "
. "to sub \"$target_sub\"";
$new_id = make_post($db, $target_sub, $settings, $text, '');
$delete_message = "message $delete_post from sub $sub"
. " moved to $target_sub";
log_event($db, $settings, "del", $delete_message, '');
echo "<h1>$delete_message</h1>";
$statement = $db->prepare("UPDATE threads
SET shadow = 'yes'
WHERE org_id = '$post_id'
AND sub = '$sub'");
$result = $statement->execute();
$statement = $db->prepare("UPDATE threads
SET move_message = '$new_move',
edit_message = '$edit_message',
original = '$new_id'
WHERE post_id = '$new_id'
AND sub = '$target_sub'");
$result = $statement->execute();
if ( (!empty($name)) &&
(!empty($tripcode)) ) {
$statement = $db->prepare("UPDATE threads
SET name = '$name',
tripcode = '$tripcode'
WHERE post_id = '$new_id'
AND sub = '$target_sub'");
$result = $statement->execute();
}
$statement = $db->prepare("SELECT post_id, org_id, text, name,
timestamp, tripcode, move_message,
edit_message
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]}";
$name = "{$row[3]}";
$timestamp = "{$row[4]}";
$tripcode = "{$row[5]}";
$move_message = "{$row[6]}";
$edit_message = "{$row[7]}";
$original = "{$row[8]}";
$new_move = "system message: "
. "post $post_id from sub \"$sub\" was moved "
. "to sub \"$target_sub\"";
$new_reply_id = make_post($db, $target_sub, $settings,
$text, $new_id);
$delete_message = "message $post_id from sub $sub moved "
. "to $target_sub";
log_event($db, $settings, "del", $delete_message, '');
echo "<h1>$delete_message</h1>";
$statement = $db->prepare("UPDATE threads
SET shadow = 'yes'
WHERE org_id = '$post_id'
AND sub = '$sub'");
$result = $statement->execute();
$statement = $db->prepare("UPDATE threads
SET move_message = '$new_move',
edit_message = '$edit_message',
original = '$new_id'
WHERE post_id = '$new_reply_id'
AND sub = '$target_sub'");
$result = $statement->execute();
if ( (!empty($name)) &&
(!empty($tripcode)) ) {
$statement = $db->prepare("UPDATE threads
SET name = '$name',
tripcode = '$tripcode'
WHERE post_id = '$new_id'
AND sub = '$target_sub'");
$result = $statement->execute();
}
}
} else {
$new_move = "system message: post "
. "$post_id from sub \"$sub\" was moved "
. "to sub \"$target_sub\"";
$new_id = make_post($db, $target_sub, $settings, $text, '');
$delete_message = "message $post_id from sub $sub moved"
. " to $target_sub";
log_event($db, $settings, "del", $delete_message, '');
echo "<h1>$delete_message</h1>";
$statement = $db->prepare("UPDATE threads
SET shadow = 'yes'
WHERE org_id = '$post_id'
AND sub = '$sub'");
$result = $statement->execute();
$statement = $db->prepare("UPDATE threads
SET move_message = '$new_move',
edit_message = '$edit_message',
original = '$new_id'
WHERE post_id = '$new_id'
AND sub = '$target_sub'");
$result = $statement->execute();
if ( (!empty($name)) &&
(!empty($tripcode)) ) {
$statement = $db->prepare("UPDATE threads
SET name = '$name',
tripcode = '$tripcode'
WHERE post_id = '$new_id'
AND sub = '$target_sub'");
$result = $statement->execute();
}
}
}
}
// 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 = "<h1>Combination of username and token is invalid."
. " This incident was logged.</h1>";
quit($db, $quit_message);
} else {
$statement = $db->prepare("UPDATE keys
SET key = '$password_hash'
WHERE name = '$name'
AND type = 'admin'");
$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");
}
}
// 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 = '<h1>Give your account name and password to log in.</h1>'
. '<br><p id="page"><div class=\'form\'>';
if ( ($type == 'admin') ) {
$html_string .= '<form action=\'/a\' method=\'post\'>';
} else {
$html_string .= '<form action=\'/m\' method=\'post\'>';
}
$html_string .= '<table class=\'newpost\'>'
. '<tr><td>Name</td><td><input type=\'text\' '
. 'required name=\'auth_name\' placeholder=\'Account name\'>'
. '<tr><td>Pass</td><td><input type=\'password\' required'
. ' name=\'auth_password\' placeholder=\'Account pass\'>'
. '<input type=\'submit\' value=\'Log in\'><br></td></tr>'
. '</table></form></div><hr>';
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, $sub),
$settings);
$html_string .= "<div class='post'>"
. "<tr>"
. "<td><form action='/ush' method='post'>"
. "<input type='hidden' name='token' value=$token'>"
. "<input type='hidden' name='sub' value='$sub'>"
. "<input type='hidden' name='unshadow_id' "
. "value='$post_id'>"
. "<input type='submit' value='Unshadow post "
. "$post_id'><br></td></form>"
. "-------------------------------------"
. "<td><form action='/da' method='post'>"
. "<input type='hidden' name='token' value=$token'>"
. "<input type='hidden' name='sub' value='$sub'>"
. "<input type='hidden' name='delete_posts' "
. "value='$post_id'>"
. "<input type='submit' value='Delete post "
. "$post_id'><br></td></form><br><br>"
. "<br>$sub/$post_id<br><br><code>$post_text"
. "</code><br><br>";
if ($prev_sub != $sub) {
$html_string .= "<td><form action='/usha' method='post'>"
. "<input type='hidden' name='token' "
. "value=$token'>"
. "<input type='hidden' name='unshadow_sub' "
. "value='$sub'>"
. "<input type='submit' value='Unshadow sub "
. "$sub'><br></td></form>"
. "-------------------------------------"
. "<td><form action='/da' method='post'>"
. "<input type='hidden' name='token' "
. "value=$token'>"
. "<input type='hidden' name='sub' "
. "value='$sub'>"
. "<input type='hidden' name='delete_sub' "
. "value='$sub'>"
. "<input type='submit' value='Delete sub "
. "$sub'><br></td></form><br><br>";
}
$html_string .= '</tr></div>';
$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 = '<h1>';
$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 .= " | <a href=/$link/$sub/$css/$token>$sub<code>"
. "($total_posts)</code></a>";
}
$html_string .= '</h1>';
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 = '<h1>Give the desired name for your account, '
. 'an email address'
. ' (or other contact) and a password.</h1>'
. '<br><p id=\'page\'>'
. '<div class=\'form\'><form action=\'/ap\' method=\'post\'>'
. '<table class=\'newpost\'>'
. '<tr><td>Name</td><td><input type=\'text\' '
. 'required name=\'appl_name\''
. ' placeholder=\'Name of account\'>'
. '<tr><td>Email</td><td><input type=\'text\' '
. 'required name=\'appl_email\' '
. 'placeholder=\'email or other contact\'>'
. '<tr><td>Pass</td><td><input type=\'password\' '
. 'required name=\'appl_password\' placeholder=\'Min '
. '10 chars\'><input type=\'submit\' value=\'Set password\'>'
. '<br></td></tr></table></form></div><hr>';
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 = '<h1>Give the name of your account and the token'
.' to set your password.</h1>'
.'<br><p id=\'page\'>'
.'<div class=\'form\'><form action=\'/sp\' method=\'post\'>'
.'<table class=\'newpost\'>'
.'<tr><td>Name</td><td><input type=\'text\' '
.'required name=\'auth_name\' placeholder=\'Name of '
.'account\'><tr><td>Token</td><td><input type=\'text\' '
.'required name=\'auth_token\' placeholder=\'Put your '
.'token in here\'>'
.'<tr><td>Pass</td><td><input type=\'password\' '
.'required name=\'auth_password\' '
.'placeholder=\'Set your password\'>'
.'<input type=\'submit\' value=\'Set password\'>'
.'<br></td></tr></table></form></div><hr>';
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 = '<br><p id="page">';
if ( ($type == 'admin') ) {
$html_string .= '<div class=\'form\'><form action=\'/da\' '
.'method=\'post\'><table class=\'newpost\'>'
.'<tr><td>Sub to delete</td><td><textarea rows=\'1\' '
.'cols=\'56\' name=\'delete_sub\' '
.'placeholder=\'Complete sub '
.'to delete, careful with that !!!\'>'
.'</textarea></td></tr>'
.'<tr><td>Posts to delete</td><td><textarea rows=\'1\' '
.'cols=\'56\' name=\'delete_posts\' placeholder=\'IDs to '
.'delete, separate by space, or range (ex.: 7-9)\'>'
.'</textarea></td></tr>';
} else {
$html_string .= '<div class=\'form\'><form action=\'/dm\' '
. 'method=\'post\'><table class=\'newpost\'>'
. '<tr><td>Sub to delete</td><td>'
. '<textarea rows=\'1\' cols=\'56\''
. ' name=\'shadow_sub\' placeholder=\'Delete complete'
. ' sub, careful with that !!!\'></textarea></td></tr>'
. '<tr><td>Posts to delete/move</td>'
. '<td><textarea rows=\'1\''
. ' cols=\'56\' name=\'shadow_posts\' placeholder='
. '\'IDs to delete, separate by space, or range '
. '(ex.: 7-9)\'></textarea></td></tr>'
. '<tr><td>Sub to move posts to</td>'
. '<td><textarea rows=\'1\''
. ' cols=\'56\' name=\'target_sub\' placeholder='
. '\'Leave empty to delete posts\'></textarea></td></tr>';
}
$html_string .= "<input type='hidden' name='token' value=$token'>"
. "<input type='hidden' name='sub' value='$sub'>";
$html_string .= '<tr><td></td><td><input type=\'submit\' ';
if ( ($type == 'admin') ) {
$html_string .= 'value=\'Delete\'><br></td></tr>';
} else {
$html_string .= 'value=\'Delete/move\'><br></td></tr>';
}
$html_string .= '</table></form></div><hr>';
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 = '<table><tr>'
. '<th>Timestamp</th><th>Type</th>'
. '<th>Logtext</th><th>ip</th></tr>';
foreach($logs as $log_data) {
$html_string .= "<tr>"
. "<td>$log_data[0]</td>"
. "<td>$log_data[1]</td>"
. "<td>$log_data[2]</td>"
. "<td>$log_data[3]</td>"
. "</tr>";
}
$html_string .= "</table>"
. "<a href='/dl/$type/8/$token'>clear logs</a>";
// eight is the css for the admin
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 = "<h1>$unshadow_message</h1>"
. "<h1>Done.</h1>"
. "<h1><a href='/sh/$sub/8/$token'>Back</a></h1>";
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 = "<h1>$unshadow_message</h1>"
. "<h1>Done.</h1>"
. "<h1><a href='/sh/$sub/8/$token'>Back</a></h1>";
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 = '<table><tr><td>Account name</td><td>Contact</td>'
. '<td>Status</td><td>Enable</td>'
. '<td>Disable</td><td>Delete</td>';
while ($row = $result->fetchArray(SQLITE3_NUM)) {
$name = "{$row[0]}";
$contact = "{$row[1]}";
$type = "{$row[2]}";
$html_string .= "<tr>";
$html_string .= "<td>$name</td><td>$contact</td><td>$type</td>";
if ($type != 'mod') {
$html_string .= "<td><form action='/dim' method='post'>"
. "<input type='hidden' name='token' "
. "value=$token'>"
. "<input type='hidden' name='enable_mod' "
. "value='$name'>"
. "<input type='submit' value='Enable'>"
. "<br></td></form>";
} else {
$html_string .= "<td></td>";
}
if ( ($type != 'application') && ($type != 'disabled') ) {
$html_string .= "<td><form action='/dim' method='post'>"
. "<input type='hidden' name='token' "
. "value=$token'>"
. "<input type='hidden' name='disable_mod'"
. " value='$name'>"
. "<input type='submit' value='Disable'>"
. "<br></td></form>";
} else {
$html_string .= "<td></td>";
}
$html_string .= "<td><form action='/dim' method='post'>"
. "<input type='hidden' name='token' value=$token'>"
. "<input type='hidden' name='delete_mod' value='$name'>"
. "<input type='submit' value='Delete'><br></td>"
. "</form>"
. "</tr>";
}
$html_string .= "</table></div>";
echo "$html_string";
}
// 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();
}
// EOF