1633 lines
39 KiB
PHP
1633 lines
39 KiB
PHP
<?php
|
|
require_once 'lib/sec.php';
|
|
|
|
require_once 'lib/admin.php';
|
|
require_once 'lib/auth.php';
|
|
|
|
require_once 'lib/archives.php';
|
|
|
|
define('IN_APP', true);
|
|
|
|
auth_user();
|
|
|
|
if (!has_level('mod')) {
|
|
APP::denied();
|
|
}
|
|
|
|
require_once 'lib/csp.php';
|
|
|
|
/*
|
|
if (has_flag('developer')) {
|
|
$mysql_suppress_err = false;
|
|
ini_set('display_errors', 1);
|
|
error_reporting(E_ALL);
|
|
}
|
|
*/
|
|
|
|
class App {
|
|
protected
|
|
// Routes
|
|
$actions = array(
|
|
'index',
|
|
'update',
|
|
'unban',
|
|
'search',
|
|
'ban_ips'
|
|
),
|
|
|
|
$is_manager = false,
|
|
|
|
$_salt_data = null,
|
|
|
|
$_cloudflare_ready = false,
|
|
|
|
$now = 0,
|
|
|
|
$report_cats = array(
|
|
1 => 'rule violation',
|
|
2 => 'illegal content'
|
|
)
|
|
;
|
|
|
|
const TPL_ROOT = 'views/';
|
|
|
|
const
|
|
TABLE_NAME = 'banned_users',
|
|
APPEALS_TABLE = 'appeals',
|
|
TEMPLATE_TABLE = 'ban_templates',
|
|
REP_CAT_TABLE = 'report_categories',
|
|
PAGE_SIZE = 50,
|
|
DATE_FORMAT = 'm/d/Y H:i:s',
|
|
DATE_FORMAT_SHORT = 'm/d/y',
|
|
WEBROOT = '/bans',
|
|
WARN_THRES = 10,
|
|
REVERSE_PARTS = 3,
|
|
SALTFILE = '/www/keys/legacy.salt',
|
|
THUMB_WEB_ROOT = 'https://i.4cdn.org/bans/thumb/',
|
|
THUMB_ROOT = '/www/4chan.org/web/images/bans/thumb/',
|
|
MAX_BAN_DAYS = 9999,
|
|
REPORT_TEMPLATE = 190, // template used for report system abuses
|
|
EXEC_LIMIT = 90
|
|
;
|
|
|
|
public function __construct() {
|
|
$this->is_developer = has_flag('developer');
|
|
|
|
$this->is_manager = has_level('manager') || $this->is_developer;
|
|
|
|
$this->now = time();
|
|
}
|
|
|
|
static public function denied() {
|
|
require_once(self::TPL_ROOT . 'denied.tpl.php');
|
|
die();
|
|
}
|
|
|
|
final protected function success($redirect = null, $no_exit = false) {
|
|
$this->redirect = $redirect;
|
|
$this->renderHTML('success');
|
|
if (!$no_exit) {
|
|
die();
|
|
}
|
|
}
|
|
|
|
final protected function error($msg) {
|
|
$this->message = $msg;
|
|
$this->renderHTML('error');
|
|
die();
|
|
}
|
|
|
|
/**
|
|
* Renders HTML template
|
|
*/
|
|
private function renderHTML($view) {
|
|
require_once(self::TPL_ROOT . $view . '.tpl.php');
|
|
}
|
|
|
|
/**
|
|
* Returns the data as json
|
|
*/
|
|
final protected function successJSON($data = null) {
|
|
$this->renderJSON(array('status' => 'success', 'data' => $data));
|
|
die();
|
|
}
|
|
|
|
/**
|
|
* Returns the error as json and exits
|
|
*/
|
|
final protected function errorJSON($message = null) {
|
|
$payload = array('status' => 'error', 'message' => $message);
|
|
$this->renderJSON($payload);
|
|
die();
|
|
}
|
|
|
|
/**
|
|
* Returns a JSON response
|
|
*/
|
|
private function renderJSON($data) {
|
|
header('Content-type: application/json');
|
|
echo json_encode($data);
|
|
}
|
|
|
|
/**
|
|
* Plurals
|
|
*/
|
|
private function pluralize($count, $one = '', $not_one = 's') {
|
|
return $count == 1 ? $one : $not_one;
|
|
}
|
|
|
|
private function init_cloudflare() {
|
|
if ($this->_cloudflare_ready) {
|
|
return true;
|
|
}
|
|
|
|
$this->_cloudflare_ready = true;
|
|
|
|
$cfg = file_get_contents('/www/global/yotsuba/config/cloudflare_config.ini');
|
|
|
|
if (!$cfg) {
|
|
return false;
|
|
}
|
|
|
|
if (preg_match('/CLOUDFLARE_API_TOKEN.?=.?([^\r\n]+)/', $cfg, $m) === false) {
|
|
return false;
|
|
}
|
|
|
|
define('CLOUDFLARE_API_TOKEN', $m[1]);
|
|
define('CLOUDFLARE_EMAIL', 'cloudflare@4chan.org');
|
|
define('CLOUDFLARE_ZONE', '4chan.org');
|
|
define('CLOUDFLARE_ZONE_2', '4cdn.org');
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Disables blacklisted md5
|
|
*/
|
|
private function disable_blacklist_by_md5($md5) {
|
|
if (!$md5) {
|
|
return true;
|
|
}
|
|
$sql = "UPDATE blacklist SET active = 0 WHERE field = 'md5' AND contents = '%s' LIMIT 1";
|
|
return !!mysql_global_call($sql, $md5);
|
|
}
|
|
|
|
/**
|
|
* Checks if a board is valid
|
|
*/
|
|
private function is_board_valid($board) {
|
|
if ($board === 'test') {
|
|
if (has_flag('developer')) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if ($board === 'j') {
|
|
return false;
|
|
}
|
|
|
|
$query = "SELECT dir FROM boardlist WHERE dir = '%s' LIMIT 1";
|
|
|
|
$res = mysql_global_call($query, $board);
|
|
|
|
return mysql_num_rows($res) === 1;
|
|
}
|
|
|
|
public function is_pass_valid($pass_id) {
|
|
$sql = "SELECT user_hash FROM pass_users WHERE user_hash = '%s' LIMIT 1";
|
|
|
|
$res = mysql_global_call($sql, $pass_id);
|
|
|
|
if (!$res) {
|
|
return false;
|
|
}
|
|
|
|
return mysql_num_rows($res) === 1;
|
|
}
|
|
|
|
public function is_template_valid($id) {
|
|
$id = (int)$id;
|
|
|
|
if (!$this->is_manager) {
|
|
$clause = "AND level != 'manager'";
|
|
}
|
|
else {
|
|
$clause = '';
|
|
}
|
|
|
|
$query = "SELECT no FROM ban_templates WHERE no = $id $clause LIMIT 1";
|
|
|
|
$res = mysql_global_call($query);
|
|
|
|
return $res && mysql_num_rows($res) === 1;
|
|
}
|
|
|
|
private function get_md5_from_blacklist_id($blid) {
|
|
$blid = (int)$blid;
|
|
|
|
$sql = "SELECT content FROM dmca_actions WHERE blacklist_id = $blid";
|
|
|
|
$res = mysql_global_call($sql);
|
|
|
|
if (!$res) {
|
|
return null;
|
|
}
|
|
|
|
$content = mysql_fetch_row($res)[0];
|
|
|
|
if (!$content) {
|
|
return null;
|
|
}
|
|
|
|
$post = json_decode($content, true);
|
|
|
|
if (!$post || !$post['md5']) {
|
|
return null;
|
|
}
|
|
|
|
return $post['md5'];
|
|
}
|
|
|
|
private function get_ban_templates() {
|
|
if (!$this->is_manager) {
|
|
$clause = "WHERE level != 'manager'";
|
|
}
|
|
else {
|
|
$clause = '';
|
|
}
|
|
|
|
$query = "SELECT rule, no, name FROM ban_templates $clause ORDER BY rule";
|
|
|
|
$res = mysql_global_call($query);
|
|
|
|
if (!$res) {
|
|
return array();
|
|
}
|
|
|
|
$data = array('global' => array());
|
|
|
|
while ($row = mysql_fetch_assoc($res)) {
|
|
$board = preg_replace('/(.)[0-9]+x?$/', '\1', $row['rule']);
|
|
|
|
if ($board === 'global') {
|
|
$data['global'][] = $row;
|
|
}
|
|
else {
|
|
if (!isset($data[$board])) {
|
|
$data[$board] = array();
|
|
}
|
|
|
|
$data[$board][] = $row;
|
|
}
|
|
}
|
|
|
|
return $data;
|
|
}
|
|
|
|
/**
|
|
* Get the Pass ID from a ban id or post id or ban request id
|
|
*/
|
|
private function get_pass_id_from_ref($ref) {
|
|
$ref = trim($ref);
|
|
|
|
// /board/post_id
|
|
if (preg_match('/^\/([a-z0-9]+)\/([0-9]+)$/', $ref, $m)) {
|
|
$board = $m[1];
|
|
$val = (int)$m[2];
|
|
|
|
if (!$this->is_board_valid($board)) {
|
|
$this->error('Invalid board for Pass Ref.');
|
|
}
|
|
|
|
$sql = "SELECT 4pass_id FROM `%s` WHERE no = $val LIMIT 1";
|
|
$res = mysql_board_call($sql, $board);
|
|
}
|
|
// ban_id
|
|
else if (preg_match('/^[0-9]+$/', $ref)) {
|
|
$val = (int)$ref;
|
|
$sql = "SELECT 4pass_id FROM `banned_users` WHERE no = $val LIMIT 1";
|
|
$res = mysql_global_call($sql);
|
|
}
|
|
// !ban_request_id
|
|
else if (preg_match('/^![0-9]+$/', $ref)) {
|
|
$val = (int)ltrim($ref, '!');
|
|
|
|
return $this->get_pass_id_from_ban_request($val);
|
|
}
|
|
else {
|
|
$this->error('Invalid Pass Reference.');
|
|
}
|
|
|
|
if (!$res) {
|
|
$this->error('Database Error (gpifr)');
|
|
}
|
|
|
|
$pass_id = mysql_fetch_row($res)[0];
|
|
|
|
if (!$pass_id) {
|
|
$this->error("The referenced post or ban doesn't have a Pass associated with it.");
|
|
}
|
|
|
|
return $pass_id;
|
|
}
|
|
|
|
private function get_pass_id_from_ban_request($id) {
|
|
$sql = "SELECT spost FROM `ban_requests` WHERE id = $id LIMIT 1";
|
|
$res = mysql_global_call($sql);
|
|
|
|
if (!$res) {
|
|
$this->error('Database Error (gpifbr)');
|
|
}
|
|
|
|
$post = mysql_fetch_row($res)[0];
|
|
|
|
if (!$post) {
|
|
$this->error('Invalid Pass Reference.');
|
|
}
|
|
|
|
$post = unserialize($post);
|
|
|
|
$pass_id = $post['4pass_id'];
|
|
|
|
if (!$pass_id) {
|
|
$this->error("The referenced post or ban doesn't have a Pass associated with it.");
|
|
}
|
|
|
|
return $pass_id;
|
|
}
|
|
|
|
/**
|
|
* Get salt for ban url hashing. Cached.
|
|
*/
|
|
private function get_salt() {
|
|
if ($this->_salt_data === null) {
|
|
$salt = file_get_contents(self::SALTFILE);
|
|
|
|
if (!$salt) {
|
|
$this->error('Internal Server Error (gbt)');
|
|
}
|
|
|
|
$this->_salt_data = $salt;
|
|
}
|
|
|
|
return $this->_salt_data;
|
|
}
|
|
|
|
/**
|
|
* Hash 4chan Pass
|
|
*/
|
|
private function hash_pass_id($pass_id) {
|
|
$salt = $this->get_salt();
|
|
|
|
if (!$pass_id) {
|
|
return '';
|
|
}
|
|
|
|
return sha1($pass_id . $salt);
|
|
}
|
|
|
|
/**
|
|
* Checks the ban template to see if the ban has a preview thumbnail.
|
|
*/
|
|
private function has_ban_thumbnail($id) {
|
|
$id = (int)$id;
|
|
|
|
$query = 'SELECT save_post FROM ' . self::TEMPLATE_TABLE . " WHERE no = $id";
|
|
|
|
$res = mysql_global_call($query);
|
|
|
|
if (!$res || !mysql_num_rows($res)) {
|
|
return true;
|
|
}
|
|
|
|
return mysql_fetch_row($res)[0] !== 'json_only';
|
|
}
|
|
|
|
/**
|
|
* Gets the report category for bans for report system abuse
|
|
*/
|
|
private function get_report_category_title($cat_id) {
|
|
$query = "SELECT title FROM " . self::REP_CAT_TABLE . " WHERE id = " . (int)$cat_id;
|
|
|
|
$res = mysql_global_call($query);
|
|
|
|
if (!$res) {
|
|
return 'N/A';
|
|
}
|
|
|
|
$cat = mysql_fetch_row($res);
|
|
|
|
if (!$cat) {
|
|
return 'N/A';
|
|
}
|
|
|
|
return $cat[0];
|
|
}
|
|
|
|
/**
|
|
* Calculates and returns ban thumbnail url
|
|
*/
|
|
private function get_ban_thumbnail($board, $post_id) {
|
|
$salt = $this->get_salt();
|
|
|
|
return self::THUMB_WEB_ROOT . $board . '/'
|
|
. sha1($board . $post_id . $salt) . 's.jpg';
|
|
}
|
|
|
|
/**
|
|
* Delete preserved thumbnail
|
|
*/
|
|
private function delete_ban_thumbnail($board, $post_id) {
|
|
$salt = $this->get_salt();
|
|
|
|
if (preg_match('/^[a-z0-9]+$/', $board) === false) {
|
|
return false;
|
|
}
|
|
|
|
$fid = $board . '/' . sha1($board . $post_id . $salt) . 's.jpg';
|
|
|
|
$file = self::THUMB_ROOT . $fid;
|
|
|
|
$ret = true;
|
|
|
|
if (file_exists($file)) {
|
|
$ret = unlink($file);
|
|
}
|
|
|
|
if (!$this->_cloudflare_ready) {
|
|
$this->init_cloudflare();
|
|
}
|
|
|
|
cloudflare_purge_url(self::THUMB_WEB_ROOT . $fid, true);
|
|
|
|
return $ret;
|
|
}
|
|
|
|
/**
|
|
* Removes html from strings
|
|
*/
|
|
private function strip_html($str) {
|
|
$str = preg_replace('/<br ?\/?>/', "\n", $str);
|
|
$str = preg_replace('/<[^>]+>/', '', $str);
|
|
return htmlspecialchars(htmlspecialchars_decode($str, ENT_QUOTES), ENT_QUOTES);
|
|
}
|
|
|
|
/**
|
|
* Returns the derefered archive url for a post
|
|
* or false if no archive is defined.
|
|
*/
|
|
private function archive_url($board, $post_id, $thread_id = null) {
|
|
$url = return_archive_link($board, $post_id, false, true, $thread_id); // lib/archives
|
|
|
|
if ($url) {
|
|
$url = derefer_url($url); // lib/admin
|
|
}
|
|
|
|
return $url;
|
|
}
|
|
|
|
/**
|
|
* Splits the name and returns array($name, $tripcode or null)
|
|
*/
|
|
private function format_name($name, $for_report = false) {
|
|
$name = str_replace(''', "'", $name);
|
|
|
|
if (strpos($name, '#')) {
|
|
return explode('#', $name);
|
|
}
|
|
|
|
if (strpos($name, '<span ')) {
|
|
$parts = explode('</span> <span class="postertrip">', $name);
|
|
|
|
if ($parts[1]) {
|
|
if ($for_report) {
|
|
if ($parts[1][0] === '!') {
|
|
$parts[1] = substr($parts[1], 1);
|
|
}
|
|
}
|
|
else {
|
|
$parts[1] = str_replace('!!', '!', $parts[1]);
|
|
}
|
|
}
|
|
|
|
return $parts;
|
|
}
|
|
|
|
return array($name, null);
|
|
}
|
|
|
|
/**
|
|
* Truncates the hostname, returns:
|
|
* array((string: $truncated_hustname, bool: $is_truncated)
|
|
*/
|
|
private function format_reverse($reverse) {
|
|
$parts = explode('.', $reverse);
|
|
|
|
$rev = implode('.<wbr>', array_slice($parts, -self::REVERSE_PARTS));
|
|
|
|
if (count($parts) > self::REVERSE_PARTS) {
|
|
$rev = array('...' . $rev, true);
|
|
}
|
|
else {
|
|
$rev = array($rev, false);
|
|
}
|
|
|
|
return $rev;
|
|
}
|
|
|
|
/**
|
|
* Splits the reason, returns array($public_reason or null, $private_reason or null)
|
|
*/
|
|
private function format_reason($reason) {
|
|
$parts = explode('<>', $reason, 2);
|
|
|
|
// Some old bans don't have a public reason.
|
|
if (count($parts) < 2) {
|
|
$parts = array(null, $parts[0]);
|
|
}
|
|
|
|
return $parts;
|
|
}
|
|
|
|
private function process_reason_tags($reason) {
|
|
$now = date(self::DATE_FORMAT_SHORT, $_SERVER['REQUEST_TIME']);
|
|
|
|
$tags = [ '%now%', '%sign%' ];
|
|
$replace = [ "($now)", "($now, {$_COOKIE['4chan_auser']})"];
|
|
|
|
return str_replace($tags, $replace, $reason);
|
|
}
|
|
|
|
private function contains_html($str) {
|
|
return preg_match('/<[^>]+>/', preg_replace('/<br ?\/?>/', '', $str)) === 1;
|
|
}
|
|
|
|
private function br2nl($str) {
|
|
return preg_replace('/<br ?\/?>/', "\n", preg_replace('/[\r\n]/', '', $str));
|
|
}
|
|
|
|
/**
|
|
* Formats the subject field (spoiler)
|
|
* returns array($clean_subject, (bool)$is_spoiler);
|
|
*/
|
|
private function format_subject($sub) {
|
|
if (strpos($sub, 'SPOILER<>') === 0) {
|
|
$sub = substr($sub, 9);
|
|
if ($sub === false) {
|
|
$sub = '';
|
|
}
|
|
$spoiler = true;
|
|
}
|
|
else {
|
|
$spoiler = false;
|
|
}
|
|
|
|
return array($sub, $spoiler);
|
|
}
|
|
|
|
/**
|
|
* Formats name, reason, and length related fields.
|
|
*/
|
|
private function format_entry($row) {
|
|
$nametrip = $this->format_name($row['name']);
|
|
$row['name'] = $nametrip[0];
|
|
|
|
if (!$row['tripcode']) {
|
|
$row['tripcode'] = $nametrip[1];
|
|
}
|
|
|
|
$reason = $this->format_reason($row['reason']);
|
|
$row['public_reason'] = $reason[0];
|
|
$row['private_reason'] = $reason[1];
|
|
|
|
if (preg_match('/\(blacklist ID: ([0-9]+)\)/', $row['private_reason'], $blid)) {
|
|
$blid = (int)$blid[1];
|
|
|
|
$md5 = $this->get_md5_from_blacklist_id($blid);
|
|
|
|
if ($md5) {
|
|
$row['blacklisted_md5'] = $md5;
|
|
}
|
|
}
|
|
|
|
if ($row['expires_on']) {
|
|
$row['permanent'] = false;
|
|
|
|
$length = $row['expires_on'] - $row['created_on'];
|
|
|
|
if ($length <= self::WARN_THRES) { // fixme
|
|
$row['warn'] = true;
|
|
}
|
|
else {
|
|
$row['warn'] = false;
|
|
}
|
|
|
|
$row['length'] = $length;
|
|
}
|
|
else {
|
|
$row['permanent'] = true;
|
|
$row['warn'] = false;
|
|
}
|
|
|
|
if (isset($row['post_json']) && $row['post_json'] !== '') {
|
|
$row['post_json'] = json_decode($row['post_json'], true);
|
|
}
|
|
|
|
return $row;
|
|
}
|
|
|
|
private function str_to_sql_time($str) {
|
|
if (!$str) {
|
|
return false;
|
|
}
|
|
|
|
if (!preg_match('/(\d\d)\/(\d\d)\/(\d\d)/', $str, $m)) {
|
|
return false;
|
|
}
|
|
|
|
return "20{$m[3]}-{$m[1]}-{$m[2]} 00:00:00";
|
|
}
|
|
|
|
/**
|
|
* Ban duration in days (integer)
|
|
*/
|
|
private function days_duration($delta) {
|
|
return (int)floor($delta / 86400);
|
|
}
|
|
|
|
/**
|
|
* Ban duration string (X day(s))
|
|
*/
|
|
private function pretty_duration($delta) {
|
|
$count = (int)floor($delta / 86400);
|
|
|
|
if ($count > 1) {
|
|
$head = $count . ' days';
|
|
}
|
|
else {
|
|
$head = '1 day';
|
|
}
|
|
|
|
return $head;
|
|
}
|
|
|
|
private function is_ip_rangebanned($ip) {
|
|
$long_ip = ip2long($ip);
|
|
|
|
if (!$long_ip) {
|
|
$this->error('Invalid IP.');
|
|
}
|
|
|
|
$query =<<<SQL
|
|
SELECT id FROM iprangebans
|
|
WHERE range_start <= $long_ip AND range_end >= $long_ip
|
|
AND active = 1 AND boards = '' AND expires_on = 0
|
|
AND ops_only = 0 AND img_only = 0 AND lenient = 0 AND ua_ids = ''
|
|
LIMIT 1
|
|
SQL;
|
|
|
|
$res = mysql_global_call($query);
|
|
|
|
if (!$res) {
|
|
return false;
|
|
}
|
|
|
|
return mysql_num_rows($res) > 0;
|
|
}
|
|
|
|
private function updateCommit() {
|
|
if (!isset($_POST['id'])) {
|
|
$this->error('Entry not found.');
|
|
}
|
|
|
|
$tbl = self::TABLE_NAME;
|
|
|
|
$id = (int)$_POST['id'];
|
|
|
|
$query = <<<SQL
|
|
SELECT no, board, post_num, global, zonly, active, UNIX_TIMESTAMP(now) as created_on,
|
|
name, tripcode, host, reverse, reason, UNIX_TIMESTAMP(length) as expires_on,
|
|
admin as created_by, template_id FROM $tbl WHERE no = $id LIMIT 1
|
|
SQL;
|
|
|
|
$res = mysql_global_call($query);
|
|
|
|
if (!$res) {
|
|
$this->error('Database error (0).');
|
|
}
|
|
|
|
if (mysql_num_rows($res) < 1) {
|
|
$this->error('Entry not found.');
|
|
}
|
|
|
|
$entry = mysql_fetch_assoc($res);
|
|
|
|
$entry = $this->format_entry($entry);
|
|
|
|
// ---
|
|
|
|
$set_fields = array();
|
|
|
|
// Active
|
|
if (isset($_POST['active'])) {
|
|
$active = 1;
|
|
}
|
|
else {
|
|
$active = 0;
|
|
}
|
|
|
|
if ((int)$entry['active'] === 1) {
|
|
if ($active === 0) {
|
|
$set_fields[] = 'active = 0';
|
|
$set_fields[] = "unbannedby = '"
|
|
. mysql_real_escape_string($_COOKIE['4chan_auser']) . "'";
|
|
$set_fields[] = 'unbannedon = NOW()';
|
|
|
|
if ($entry['board'] && $entry['post_num'] &&
|
|
$this->has_ban_thumbnail($entry['template_id'])) {
|
|
$this->delete_ban_thumbnail($entry['board'], $entry['post_num']);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if ($active === 1) {
|
|
$set_fields[] = 'active = 1';
|
|
}
|
|
}
|
|
|
|
// Length
|
|
$permanent = $warn = false;
|
|
$days = $length = null;
|
|
|
|
if (isset($_POST['permanent'])) {
|
|
$permanent = true;
|
|
}
|
|
else if (isset($_POST['warn'])) {
|
|
$warn = true;
|
|
}
|
|
else if (isset($_POST['days'])) {
|
|
$days = (int)$_POST['days'];
|
|
|
|
if ($days < 0 || $days > self::MAX_BAN_DAYS) {
|
|
$this->error('Invalid ban length.');
|
|
}
|
|
|
|
if ($_POST['days'] === '0') {
|
|
$warn = true;
|
|
}
|
|
}
|
|
|
|
if ($permanent !== $entry['permanent'] && $permanent) {
|
|
$set_fields[] = "length = '0'";
|
|
}
|
|
else if ($warn !== $entry['warn'] && $warn) {
|
|
$set_fields[] = 'length = now';
|
|
}
|
|
else if ($days !== null) {
|
|
if (!isset($entry['length']) || $days !== $this->days_duration($entry['length'])) {
|
|
$set_fields[] = "length = DATE_ADD(now, INTERVAL $days DAY)";
|
|
}
|
|
}
|
|
|
|
// Global
|
|
if (isset($_POST['global'])) {
|
|
if (!$entry['global']) {
|
|
$set_fields[] = 'global = 1';
|
|
}
|
|
}
|
|
else {
|
|
if ($entry['global']) {
|
|
$set_fields[] = 'global = 0';
|
|
}
|
|
}
|
|
|
|
// Unappealable
|
|
if ($this->is_manager) {
|
|
if (isset($_POST['zonly'])) {
|
|
if (!$entry['zonly']) {
|
|
$set_fields[] = 'zonly = 1';
|
|
}
|
|
}
|
|
else {
|
|
if ($entry['zonly']) {
|
|
$set_fields[] = 'zonly = 0';
|
|
}
|
|
}
|
|
}
|
|
|
|
// Public
|
|
$public_reason = null;
|
|
|
|
if (isset($_POST['public_reason']) && $_POST['public_reason'] !== '') {
|
|
if ($this->contains_html($entry['public_reason'])) {
|
|
$this->error("You can't edit public reasons containing HTML");
|
|
}
|
|
|
|
$public_reason = nl2br(htmlspecialchars($_POST['public_reason'], ENT_QUOTES), false);
|
|
}
|
|
|
|
if ($public_reason === $entry['public_reason']) {
|
|
$public_reason = null;
|
|
}
|
|
|
|
// Private reason
|
|
$private_reason = null;
|
|
|
|
if (isset($_POST['private_reason']) && $_POST['private_reason'] !== '') {
|
|
$private_reason = $this->process_reason_tags($_POST['private_reason']);
|
|
|
|
$private_reason = htmlspecialchars($private_reason, ENT_QUOTES);
|
|
|
|
if ($private_reason === $entry['private_reason']) {
|
|
$private_reason = null;
|
|
}
|
|
}
|
|
|
|
// Reason field
|
|
if ($public_reason !== null || $private_reason !== null) {
|
|
$reason = '';
|
|
|
|
if ($public_reason !== null) {
|
|
$reason .= $public_reason;
|
|
}
|
|
else {
|
|
$reason .= $entry['public_reason'];
|
|
}
|
|
|
|
if ($private_reason !== null) {
|
|
$reason .= "<>$private_reason";
|
|
}
|
|
else {
|
|
$reason .= "<>{$entry['private_reason']}";
|
|
}
|
|
|
|
$set_fields[] = "reason = '" . mysql_real_escape_string($reason) . "'";
|
|
}
|
|
|
|
// ---
|
|
|
|
if (empty($set_fields)) {
|
|
$this->success(self::WEBROOT . '?action=update&id=' . $id);
|
|
}
|
|
|
|
$set_fields = implode(', ', $set_fields);
|
|
|
|
$query =<<<SQL
|
|
UPDATE `$tbl` SET $set_fields
|
|
WHERE no = $id LIMIT 1
|
|
SQL;
|
|
|
|
$res = mysql_global_call($query);
|
|
|
|
if (!$res) {
|
|
$this->error('Database error (2).');
|
|
}
|
|
|
|
|
|
$this->success(self::WEBROOT . '?action=update&id=' . $id);
|
|
}
|
|
|
|
private function create_ban($ip, $reason, $days, $board = null, $pass_id = null, $pwd = null, $force = false, $no_reverse = false) {
|
|
$long_ip = ip2long($ip);
|
|
|
|
$html_ip = htmlspecialchars($ip);
|
|
|
|
if (!$long_ip) {
|
|
return 'Invalid IP: ' . $html_ip;
|
|
}
|
|
|
|
if ($board) {
|
|
$board = mysql_real_escape_string($board);
|
|
|
|
if ($board === false) {
|
|
$this->error('Database error (eb0).');
|
|
}
|
|
|
|
$global = 0;
|
|
$board_clause = "(board = '$board' OR global = 1)";
|
|
}
|
|
else {
|
|
$global = 1;
|
|
$board = '';
|
|
$board_clause = 'global = 1';
|
|
}
|
|
|
|
if (!$force) {
|
|
$query = "SELECT COUNT(*) FROM banned_users WHERE active = 1 AND $board_clause AND host = '%s'";
|
|
|
|
$res = mysql_global_call($query, $ip);
|
|
|
|
if (!$res) {
|
|
return 'Database Error: ' . $html_ip . ' (1)';
|
|
}
|
|
|
|
if ((int)mysql_fetch_row($res)[0] > 0) {
|
|
return 'Already banned: ' . $html_ip;
|
|
}
|
|
}
|
|
|
|
if ($no_reverse) {
|
|
$reverse = $ip;
|
|
}
|
|
else {
|
|
$reverse = gethostbyaddr($ip);
|
|
}
|
|
|
|
if ($days == -1) {
|
|
$length = '0';
|
|
}
|
|
else {
|
|
$length = date('Y-m-d H:i:s', time() + $days * ( 24 * 60 * 60 ));
|
|
}
|
|
|
|
if (!$pass_id) {
|
|
$pass_id = '';
|
|
}
|
|
|
|
$query = <<<SQL
|
|
INSERT INTO banned_users (global, board, host, reverse, reason, admin, zonly,
|
|
length, name, 4pass_id, password, post_num, admin_ip)
|
|
VALUES ($global, '$board', '%s', '%s', '%s', '%s', 0, '%s', '', '%s', '%s', 0, '%s')
|
|
SQL;
|
|
|
|
$res = mysql_global_call($query,
|
|
$ip, $reverse, $reason, $_COOKIE['4chan_auser'],
|
|
$length, $pass_id, $pwd, $_SERVER['REMOTE_ADDR']
|
|
);
|
|
|
|
if (!$res) {
|
|
return 'Database Error: ' . $html_ip . ' (2)';
|
|
}
|
|
|
|
return "Banned $html_ip (" . htmlspecialchars($reverse) . ")";
|
|
}
|
|
|
|
/**
|
|
* Ban a list of IPs
|
|
*/
|
|
private function ban_ips() {
|
|
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|
$this->renderHTML('bans-banips');
|
|
return;
|
|
}
|
|
|
|
if (!isset($_POST['ips']) || !isset($_POST['public_reason'])) {
|
|
$this->error('Bad Request.');
|
|
}
|
|
|
|
$public_reason = trim($_POST['public_reason']);
|
|
|
|
if (!isset($_POST['private_reason'])) {
|
|
$private_reason = '';
|
|
}
|
|
else {
|
|
$private_reason = trim($_POST['private_reason']);
|
|
}
|
|
|
|
if ($public_reason === '') {
|
|
$this->error('Public reason cannot be empty');
|
|
}
|
|
|
|
$reason = htmlspecialchars(nl2br($public_reason), ENT_QUOTES) . '<>'
|
|
. htmlspecialchars($private_reason, ENT_QUOTES);
|
|
|
|
if (!isset($_POST['days']) || $_POST['days'] == '') {
|
|
$days = -1;
|
|
}
|
|
else {
|
|
$days = (int)trim($_POST['days']);
|
|
|
|
if ($days < -1) {
|
|
$days = -1;
|
|
}
|
|
}
|
|
|
|
$board = null;
|
|
|
|
if (isset($_POST['board']) && $_POST['board'] !== '') {
|
|
if ($this->is_board_valid($_POST['board'])) {
|
|
$board = $_POST['board'];
|
|
}
|
|
else {
|
|
$this->error('Invalid board.');
|
|
}
|
|
}
|
|
|
|
set_time_limit(self::EXEC_LIMIT);
|
|
|
|
$ips = preg_split('/[\r\n]+/', $_POST['ips']);
|
|
|
|
if ($this->is_developer && isset($_POST['passid']) && $_POST['passid']) {
|
|
if (count($ips) > 1) {
|
|
$this->error('Only one IP can be banned alongside a 4chan Pass.');
|
|
}
|
|
|
|
$pass_id = trim($_POST['passid']);
|
|
|
|
if (!$this->is_pass_valid($pass_id)) {
|
|
$this->error('Invalid 4chan Pass.');
|
|
}
|
|
}
|
|
else {
|
|
$pass_id = null;
|
|
}
|
|
|
|
if ($this->is_developer && isset($_POST['pwd']) && $_POST['pwd']) {
|
|
if (count($ips) > 1) {
|
|
$this->error('Only one IP can be banned alongside a Password.');
|
|
}
|
|
|
|
$pwd = trim($_POST['pwd']);
|
|
|
|
if (!preg_match('/^[a-f0-9]{32}$/', $pwd)) {
|
|
$this->error('Invalid Password.');
|
|
}
|
|
}
|
|
else {
|
|
$pwd = null;
|
|
}
|
|
|
|
$status = [];
|
|
|
|
$force = isset($_POST['force']) && $_POST['force'];
|
|
|
|
$no_reverse = isset($_POST['no_reverse']) && $_POST['no_reverse'];
|
|
|
|
$no_rangebans = isset($_POST['no_rangebans']) && $_POST['no_rangebans'];
|
|
|
|
$dups = array();
|
|
|
|
foreach ($ips as $ip) {
|
|
$ip = trim($ip);
|
|
|
|
if (strpos($ip, ':') !== false) {
|
|
$ip = preg_replace('/:[0-9]+$/', '', $ip);
|
|
}
|
|
|
|
if (isset($dups[$ip])) {
|
|
continue;
|
|
}
|
|
|
|
$dups[$ip] = true;
|
|
|
|
if ($no_rangebans && $this->is_ip_rangebanned($ip)) {
|
|
$status[] = 'Already rangebanned: ' . htmlspecialchars($ip);
|
|
continue;
|
|
}
|
|
|
|
$status[] = $this->create_ban($ip, $reason, $days, $board, $pass_id, $pwd, $force, $no_reverse);
|
|
}
|
|
|
|
$this->status = $status;
|
|
|
|
$this->renderHTML('bans-banips');
|
|
}
|
|
|
|
/**
|
|
* Unban
|
|
*/
|
|
public function unban() {
|
|
if (!isset($_POST['ids'])) {
|
|
$this->errorJSON('Bad Request');
|
|
}
|
|
|
|
$ids = array();
|
|
|
|
$tmp = explode(',', $_POST['ids']);
|
|
|
|
foreach ($tmp as $id) {
|
|
$id = (int)$id;
|
|
|
|
if ($id) {
|
|
$ids[] = $id;
|
|
}
|
|
}
|
|
|
|
if (empty($ids)) {
|
|
$this->errorJSON('Nothing to do');
|
|
}
|
|
|
|
$tbl = self::TABLE_NAME;
|
|
|
|
$clause = implode(',', $ids);
|
|
$count = count($ids);
|
|
|
|
$query =<<<SQL
|
|
UPDATE $tbl SET active = 0, unbannedby = '%s', unbannedon = NOW()
|
|
WHERE no IN($clause) AND active = 1 LIMIT $count
|
|
SQL;
|
|
|
|
$res = mysql_global_call($query, $_COOKIE['4chan_auser']);
|
|
|
|
if (!$res) {
|
|
$this->errorJSON('Database Error (0)');
|
|
}
|
|
|
|
// Delete ban thumbnails
|
|
$query = "SELECT board, post_num FROM $tbl WHERE no IN($clause)";
|
|
|
|
$res = mysql_global_call($query, $_COOKIE['4chan_auser']);
|
|
|
|
if (!$res) {
|
|
$this->errorJSON('Database Error (1)');
|
|
}
|
|
|
|
while ($entry = mysql_fetch_assoc($res)) {
|
|
if ($entry['board'] && $entry['post_num']) {
|
|
$this->delete_ban_thumbnail($entry['board'], $entry['post_num']);
|
|
}
|
|
}
|
|
|
|
$this->successJSON(array('affected' => mysql_affected_rows()));
|
|
}
|
|
|
|
/**
|
|
* Update entry
|
|
*/
|
|
public function update() {
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$this->updateCommit();
|
|
}
|
|
else if (isset($_GET['id'])) {
|
|
require_once 'lib/geoip2.php';
|
|
|
|
$tbl = self::TABLE_NAME;
|
|
|
|
$id = (int)$_GET['id'];
|
|
|
|
$query =<<<SQL
|
|
SELECT no, board, post_num, global, zonly, active, UNIX_TIMESTAMP(now) as created_on,
|
|
name, tripcode, host, reverse, reason, UNIX_TIMESTAMP(length) as expires_on,
|
|
admin as created_by, unbannedby, UNIX_TIMESTAMP(unbannedon) as unbanned_on,
|
|
template_id, md5, password, 4pass_id, post_json FROM $tbl WHERE no = $id LIMIT 1
|
|
SQL;
|
|
|
|
$res = mysql_global_call($query);
|
|
|
|
if (!$res) {
|
|
$this->error('Database Error (1).');
|
|
}
|
|
|
|
if (mysql_num_rows($res) < 1) {
|
|
$this->error('Entry not found.');
|
|
}
|
|
|
|
$this->item = $this->format_entry(mysql_fetch_assoc($res));
|
|
|
|
// User info (email field)
|
|
if ($this->item['post_json']) {
|
|
$this->item['user_info'] = decode_user_meta($this->item['post_json']['ua']);
|
|
}
|
|
else {
|
|
$this->item['user_info'] = [];
|
|
}
|
|
|
|
// Get geolocation
|
|
$geoinfo = GeoIP2::get_country($this->item['host']);
|
|
|
|
if ($geoinfo && isset($geoinfo['country_code'])) {
|
|
$geo_loc = array();
|
|
|
|
if (isset($geoinfo['city_name'])) {
|
|
$geo_loc[] = $geoinfo['city_name'];
|
|
}
|
|
|
|
if (isset($geoinfo['state_code'])) {
|
|
$geo_loc[] = $geoinfo['state_code'];
|
|
}
|
|
|
|
$geo_loc[] = $geoinfo['country_name'];
|
|
|
|
$this->item['location'] = htmlspecialchars(implode(', ', $geo_loc), ENT_QUOTES);
|
|
}
|
|
else {
|
|
$this->item['location'] = null;
|
|
}
|
|
|
|
// Check if the thumbnail is available
|
|
if (is_array($this->item['post_json'])
|
|
&& $this->item['post_json']['ext']
|
|
&& $this->item['template_id'])
|
|
{
|
|
$this->item['has_thumbnail'] = $this->has_ban_thumbnail($this->item['template_id']);
|
|
}
|
|
else {
|
|
$this->item['has_thumbnail'] = false;
|
|
}
|
|
|
|
// Count previous bans by IP
|
|
$query = "SELECT COUNT(*) FROM $tbl WHERE host = '%s'";
|
|
|
|
$res = mysql_global_call($query, $this->item['host']);
|
|
|
|
if (!$res) {
|
|
$this->error('Database Error (2).');
|
|
}
|
|
|
|
$count = (int)mysql_fetch_row($res)[0];
|
|
|
|
if ($count < 0) {
|
|
$count = 0;
|
|
}
|
|
|
|
$this->item['ban_history'] = $count;
|
|
|
|
// Count previous bans by Pass (double query for indexes)
|
|
if ($this->item['4pass_id']) {
|
|
$count = 0;
|
|
|
|
$query = "SELECT COUNT(*) FROM $tbl WHERE active = 1 AND 4pass_id = '%s'";
|
|
|
|
$res = mysql_global_call($query, $this->item['4pass_id']);
|
|
|
|
if (!$res) {
|
|
$this->error('Database Error (2-1).');
|
|
}
|
|
|
|
$count += (int)mysql_fetch_row($res)[0];
|
|
|
|
$query = "SELECT COUNT(*) FROM $tbl WHERE active = 0 AND 4pass_id = '%s'";
|
|
|
|
$res = mysql_global_call($query, $this->item['4pass_id']);
|
|
|
|
if (!$res) {
|
|
$this->error('Database Error (2-2).');
|
|
}
|
|
|
|
$count += (int)mysql_fetch_row($res)[0];
|
|
|
|
$this->item['ban_history_pass'] = $count;
|
|
}
|
|
|
|
if ($this->item['template_id']) {
|
|
$query = 'SELECT name FROM ban_templates WHERE no = ' . (int)$this->item['template_id'];
|
|
$res = mysql_global_call($query);
|
|
$this->item['template_name'] = mysql_fetch_row($res)[0];
|
|
}
|
|
else {
|
|
$this->item['template_name'] = null;
|
|
}
|
|
|
|
// Appeals
|
|
$appeals_tbl = self::APPEALS_TABLE;
|
|
|
|
$query = "SELECT plea, plea_history FROM $appeals_tbl WHERE no = $id LIMIT 1";
|
|
|
|
$res = mysql_global_call($query);
|
|
|
|
if ($res && mysql_num_rows($res) === 1) {
|
|
$row = mysql_fetch_assoc($res);
|
|
|
|
if ($row['plea_history']) {
|
|
$appeals = json_decode($row['plea_history'], true);
|
|
|
|
$appeals = array_reverse($appeals);
|
|
}
|
|
else {
|
|
$appeals = array();
|
|
}
|
|
|
|
if ($row['plea'] !== $appeals[0]['plea']) {
|
|
array_unshift($appeals, array('plea' => $row['plea']));
|
|
}
|
|
}
|
|
else {
|
|
$appeals = null;
|
|
}
|
|
|
|
$this->item['appeals'] = $appeals;
|
|
}
|
|
else {
|
|
$this->item = null;
|
|
}
|
|
|
|
$this->renderHTML('bans-update');
|
|
}
|
|
|
|
/**
|
|
* Search
|
|
*/
|
|
public function search() {
|
|
if (count($_GET) <= 1) {
|
|
$this->ban_templates = $this->get_ban_templates();
|
|
$this->renderHTML('bans-search');
|
|
return;
|
|
}
|
|
|
|
require_once 'lib/geoip2.php';
|
|
|
|
$this->search_mode = true;
|
|
|
|
$tbl = self::TABLE_NAME;
|
|
|
|
$lim = self::PAGE_SIZE + 1;
|
|
|
|
if (isset($_GET['offset'])) {
|
|
$offset = (int)$_GET['offset'];
|
|
}
|
|
else {
|
|
$offset = 0;
|
|
}
|
|
|
|
$url_params = array();
|
|
$sql_clause = array();
|
|
|
|
$this->items = array();
|
|
|
|
$valid_fields = array(
|
|
'active' => array('bool', 'active'),
|
|
'ip' => array('string', 'host', true),
|
|
'hostname' => array('text', 'reverse'),
|
|
'name' => array('text', 'name'),
|
|
'tripcode' => array('text', 'tripcode'),
|
|
'board' => array('string', 'board'),
|
|
'post_id' => array('int', 'post_num'),
|
|
'password' => array('string', 'password'),
|
|
'md5' => array('string', 'md5'),
|
|
'banned_by' => array('string', 'admin'),
|
|
'tpl' => array('int', 'template_id'),
|
|
'reason' => array('text', 'reason'),
|
|
'ds' => array('date', 'now'),
|
|
'pass_ref' => array('pass_ref', '4pass_id'),
|
|
'country' => array('post_json', 'country', 'string'),
|
|
'ua' => array('post_json', 'ua', 'text'),
|
|
'sub' => array('post_json', '%sub', 'text') // rel_sub or sub
|
|
);
|
|
|
|
if (isset($_GET['tpl'])) {
|
|
if (!$this->is_template_valid($_GET['tpl'])) {
|
|
$this->error('Invalid Ban Template.');
|
|
}
|
|
}
|
|
|
|
if ($this->is_manager) {
|
|
$valid_fields['pass_id'] = array('string', '4pass_id');
|
|
}
|
|
|
|
foreach ($valid_fields as $field => $meta) {
|
|
if (!isset($_GET[$field])) {
|
|
continue;
|
|
}
|
|
|
|
$value = $_GET[$field];
|
|
|
|
if ($value === '') {
|
|
continue;
|
|
}
|
|
|
|
$type = $meta[0];
|
|
$col = $meta[1];
|
|
$extra = isset($meta[2]) ? $meta[2] : null;
|
|
|
|
if ($type === 'bool') {
|
|
$value = $value ? 1 : 0;
|
|
$url_params[] = "$field=$value";
|
|
$sql_clause[] = "$col=$value";
|
|
}
|
|
else if ($type === 'string') {
|
|
$url_params[] = $field . '=' . urlencode($value);
|
|
|
|
if ($extra && preg_match('/\*$/', $value)) {
|
|
$value = str_replace(array('%', '_'), array("\%", "\_"), $value);
|
|
|
|
$value = preg_replace('/\*+$/', '%', $value);
|
|
|
|
if (strlen($value) < 2) {
|
|
$this->error('Empty Query.');
|
|
}
|
|
|
|
$sql_clause[] = $col . " LIKE '" . mysql_real_escape_string($value) . "'";
|
|
}
|
|
else {
|
|
$sql_clause[] = $col . "='" . mysql_real_escape_string($value) . "'";
|
|
}
|
|
|
|
if ($col === '4pass_id' && !isset($_GET['active'])) {
|
|
$sql_clause[] = '(active = 1 OR active = 0)';
|
|
}
|
|
}
|
|
else if ($type === 'text') {
|
|
$url_params[] = $field . '=' . urlencode($value);
|
|
$value = str_replace(array('%', '_'), array("\%", "\_"), $value);
|
|
$sql_clause[] = $col . " LIKE '%" . mysql_real_escape_string($value) . "%'";
|
|
}
|
|
else if ($type === 'int') {
|
|
$value = (int)$value;
|
|
$url_params[] = "$field=$value";
|
|
$sql_clause[] = "$col=$value";
|
|
}
|
|
else if ($type === 'date') {
|
|
$url_params[] = "$field=$value";
|
|
|
|
$date_start = $this->str_to_sql_time($value);
|
|
|
|
if (!$date_start) {
|
|
$this->error('Invalid start date.');
|
|
}
|
|
|
|
$date_start = "'$date_start'";
|
|
|
|
if (isset($_GET['de']) && $_GET['de'] !== '') {
|
|
$date_end = $this->str_to_sql_time($_GET['de']);
|
|
|
|
if (!$date_end) {
|
|
$this->error('Invalid end date.');
|
|
}
|
|
|
|
$url_params[] = "de=" . $_GET['de'];
|
|
|
|
$date_end = "'$date_end'";
|
|
}
|
|
else {
|
|
$date_end = 'NOW()';
|
|
}
|
|
|
|
$sql_clause[] = "$col BETWEEN $date_start AND $date_end";
|
|
}
|
|
else if ($type === 'pass_ref') {
|
|
$url_params[] = $field . '=' . urlencode($value);
|
|
$pass_id = $this->get_pass_id_from_ref($_GET['pass_ref']);
|
|
$sql_clause[] = "(" . $col . "='" . mysql_real_escape_string($pass_id) . "' AND (active = 1 OR active = 0))";
|
|
}
|
|
else if ($type === 'post_json') {
|
|
$url_params[] = $field . '=' . urlencode($value);
|
|
$value = mysql_real_escape_string($value);
|
|
|
|
if ($extra === 'text') {
|
|
$value = "%$value%";
|
|
}
|
|
|
|
$sql_clause[] = "post_json LIKE '%\"$col\":\"$value\"%'";
|
|
|
|
if ($field === 'country') {
|
|
// Country search needs to skip bans for reports
|
|
$sql_clause[] = "template_id != " . self::REPORT_TEMPLATE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (empty($sql_clause)) {
|
|
$this->error('Empty Query.');
|
|
}
|
|
|
|
set_time_limit(self::EXEC_LIMIT);
|
|
|
|
$sql_clause = implode(' AND ', $sql_clause);
|
|
|
|
$query = <<<SQL
|
|
SELECT no, board, post_num, global, zonly, active, UNIX_TIMESTAMP(now) as created_on,
|
|
name, tripcode, host, reverse, 4pass_id, reason, UNIX_TIMESTAMP(length) as expires_on,
|
|
admin as created_by, post_json
|
|
FROM $tbl WHERE $sql_clause ORDER BY no DESC LIMIT $offset,$lim
|
|
SQL;
|
|
|
|
$res = mysql_global_call($query);
|
|
|
|
if (!$res) {
|
|
$this->error('Database Error.');
|
|
}
|
|
|
|
$this->offset = $offset;
|
|
|
|
$this->previous_offset = $offset - self::PAGE_SIZE;
|
|
|
|
if ($this->previous_offset < 0) {
|
|
$this->previous_offset = 0;
|
|
}
|
|
|
|
if (mysql_num_rows($res) === $lim) {
|
|
$this->next_offset = $offset + self::PAGE_SIZE;
|
|
}
|
|
else {
|
|
$this->next_offset = 0;
|
|
}
|
|
|
|
$_loc_cache = [];
|
|
|
|
while ($row = mysql_fetch_assoc($res)) {
|
|
$row = $this->format_entry($row);
|
|
|
|
// Get geolocation
|
|
if (isset($_loc_cache[$row['host']])) {
|
|
$row['location'] = $_loc_cache[$row['host']];
|
|
}
|
|
else {
|
|
$geoinfo = GeoIP2::get_country($row['host']);
|
|
|
|
if ($geoinfo && isset($geoinfo['country_code'])) {
|
|
$geo_loc = array();
|
|
|
|
if (isset($geoinfo['city_name'])) {
|
|
$geo_loc[] = $geoinfo['city_name'];
|
|
}
|
|
|
|
if (isset($geoinfo['state_code'])) {
|
|
$geo_loc[] = $geoinfo['state_code'];
|
|
}
|
|
|
|
$geo_loc[] = $geoinfo['country_name'];
|
|
|
|
$_ret = htmlspecialchars(implode(', ', $geo_loc), ENT_QUOTES);
|
|
|
|
$row['location'] = $_ret;
|
|
|
|
$_loc_cache[$row['host']] = $_ret;
|
|
}
|
|
}
|
|
|
|
$this->items[] = $row;
|
|
}
|
|
|
|
if ($this->next_offset) {
|
|
array_pop($this->items);
|
|
}
|
|
|
|
$url_params = htmlspecialchars(implode('&', $url_params), ENT_QUOTES);
|
|
|
|
$this->search_qs = 'action=search&' . $url_params . '&';
|
|
|
|
$this->renderHTML('bans');
|
|
}
|
|
|
|
/**
|
|
* Index
|
|
*/
|
|
public function index() {
|
|
$tbl = self::TABLE_NAME;
|
|
$lim = self::PAGE_SIZE + 1;
|
|
|
|
if (isset($_GET['offset'])) {
|
|
$offset = (int)$_GET['offset'];
|
|
}
|
|
else {
|
|
$offset = 0;
|
|
}
|
|
|
|
$query =<<<SQL
|
|
SELECT SQL_NO_CACHE no, board, post_num, global, zonly, active, UNIX_TIMESTAMP(now) as created_on,
|
|
name, tripcode, host, reverse, reason, UNIX_TIMESTAMP(length) as expires_on,
|
|
admin as created_by, post_json
|
|
FROM $tbl ORDER BY no DESC LIMIT $offset,$lim
|
|
SQL;
|
|
|
|
$res = mysql_global_call($query);
|
|
|
|
if (!$res) {
|
|
$this->error('Database Error');
|
|
}
|
|
|
|
$this->offset = $offset;
|
|
|
|
$this->previous_offset = $offset - self::PAGE_SIZE;
|
|
|
|
if ($this->previous_offset < 0) {
|
|
$this->previous_offset = 0;
|
|
}
|
|
|
|
if (mysql_num_rows($res) === $lim) {
|
|
$this->next_offset = $offset + self::PAGE_SIZE;
|
|
}
|
|
else {
|
|
$this->next_offset = 0;
|
|
}
|
|
|
|
$this->items = array();
|
|
|
|
while ($row = mysql_fetch_assoc($res)) {
|
|
$this->items[] = $this->format_entry($row);
|
|
}
|
|
|
|
if ($this->next_offset) {
|
|
array_pop($this->items);
|
|
}
|
|
|
|
$this->search_qs = null;
|
|
|
|
$this->renderHTML('bans');
|
|
}
|
|
|
|
/**
|
|
* Main
|
|
*/
|
|
public function run() {
|
|
$method = $_SERVER['REQUEST_METHOD'] === 'POST' ? $_POST : $_GET;
|
|
|
|
if (isset($method['action'])) {
|
|
$action = $method['action'];
|
|
}
|
|
else {
|
|
$action = 'index';
|
|
}
|
|
|
|
if (in_array($action, $this->actions)) {
|
|
$this->$action();
|
|
}
|
|
else {
|
|
$this->error('Bad request');
|
|
}
|
|
}
|
|
}
|
|
|
|
$ctrl = new App();
|
|
$ctrl->run();
|